Repository navigation
Conversation
POST /api/discord/send-message took {discordIds, message} with no session,
so anyone could make the project's Discord bot DM any user any text — a
ready-made phishing relay in the project's name. GET /api/discord/get-user
interpolated an unchecked id into a bot-authenticated Discord URL and echoed
Discord's error body.
send-message now:
- requires a wallet session, and the caller must be a signer or owner of
`walletId` (same rule as assertWalletAccess)
- only reaches Discord ids registered to that wallet's signers (403 otherwise)
- writes the text server-side from four fixed templates (`kind`), with the
wallet name in inline code so it can carry no link or markup; links use
getSiteUrl() like email notifications
- is rate-limited per IP (15/min) and per signer (10/min), since anyone can
create a wallet listing a stranger's public address as a signer
get-user validates the id as a snowflake, rate-limits, and returns only
id/avatar/discriminator. voteCC's notification is now best-effort like
new-transaction's, so a Discord failure no longer skips the redirect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLLy41afbDVhpZUhmc61mX
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Re-run needed: I can't re-run the job from here; the API returned 403. A maintainer re-run of the failed job would settle it. If it fails the same way again, I'll treat it as a real failure and look into it. Generated by Claude Code |
|
I tried to re-run the failed job and got a 403, so this integration can't re-run it. A maintainer needs to click "Re-run failed jobs" on run 39548422012. If the test fails again, treat it as a real failure. Generated by Claude Code |
What and why
POST /api/discord/send-messageaccepted{ discordIds, message }with no session, no validation and no rate limit. Anyone could have the project's Discord bot DM any Discord user any text. That makes it a phishing relay in the project's name.GET /api/discord/get-userput an uncheckeddiscordIdinto a bot-authenticated Discord URL (for example@me/guilds) and passed Discord's error body back to the caller.send-messagewalletId, the same rule asassertWalletAccess. Otherwise 403. An unknown wallet gets 404.kind(newTransaction,ccVote,transactionReminder,datumReminder), and any text the caller sends is ignored. This matters because "co-signer only" is not enough on its own: anyone can create a wallet that lists strangers' public addresses as signers.getSiteUrl(), the same as email notifications.walletIdmust match[A-Za-z0-9_-]{1,64}get-userid,avataranddiscriminator.Callers
(walletId, kind, discordIds). The old message texts and link paths are kept, now as the server templates.voteCCnow treats the notification as best-effort, likenew-transaction. A Discord failure no longer blocks the redirect after the vote transaction has been created.Tests
src/__tests__/discordRelay.test.ts: 33 tests, all passing. Against the old handlers, 16 of the first 23 failed.npx tsc --noEmit: clean.node scripts/run-tests.mjs:next build --webpackandcheck-bundle-budget --report: within budget, no change to light routes.Not verified / for the maintainer
window.location.originand now depend onNOTIFICATION_LINK_BASE_URL(orNEXT_PUBLIC_SITE_URL). If neither is set on preprod, preprod DMs will link to production. Email notifications already depend on this.wallet.createWalletaccepts anysignersAddresses. Someone can still create a wallet that lists a victim and send them up to 10/min of fixed-text "new transaction" DMs that link to our own site. Closing this fully would mean checking that a pending transaction exists, or requiring signers to opt in. That is a product decision, so it is not in this PR.Generated by Claude Code