Repository navigation
fix(ses): verify AWS SNS signatures on the SES callback - #466
polylane[bot] wants to merge 2 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedBot user detected. To trigger a single review, invoke the
|
Deploying usesend with
|
| Latest commit: |
01cffe7
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://6c05eb55.usesend.pages.dev |
| Branch Preview URL: | https://polylane-autofix-p5ssbljl4ng.usesend.pages.dev |
Co-authored-by: polylane[bot] <277585245+polylane[bot]@users.noreply.github.com>
…n rollout - add genuine AWS-signed SNS/SES fixtures (SHA1, SHA256, with and without Subject) - log every rejected SNS message with topic, message id and cert URL - add SNS_SIGNATURE_VERIFICATION=log-only escape hatch (default enforce) - reject signing cert URLs with query, port or credentials; bound the cert cache - time out the signing cert fetch after 5s
6b8aab8 to
01cffe7
Compare
Verification reviewThe canonical string is correct, and it accepts genuine AWS signatures. It builds The original tests were self-confirming: they signed with the same canonical builder they were testing. The new commit adds 6 real AWS-signed messages, all signed by AWS's
All 6 verify. Reintroducing the #440 bug makes all 6 fail, so the tests now catch it. Tampering with any signed field, adding or removing Subject, or relabelling a SHA256 signature as v1 is rejected. Cert URLs that are non-https, on a lookalike or wrong-region host, not ending in Not covered by a real fixture: SubscriptionConfirmation. I couldn't find a published one. It uses the same code path with the documented field list, but please watch the first new SES setup after merge. Changes in 01cffe7 (+47/−7 outside tests)
Tests: typecheck clean; unit 166/166, api 33/33, trpc 10/10. Verdict: safe to merge. After deploy, check that |
|
@KMKoushik I can help with this once you have access to the koushik's Workspace workspace. Already have a Polylane account? Link your GitHub account and I'll pick this up. |
Fixes: useSend SES callback accepts forged SNS events without signature verification
The public SES notification callback trusted any request that named a configured SNS topic ARN, because it never checked the AWS SNS message signature. A forged request could mark email as bounced or complained, unsubscribe contacts that share a recipient address, skew usage counters, and make the server fetch an attacker-controlled URL. The callback now verifies the SNS signature before it processes a message and only follows subscription URLs on the AWS SNS host.
flowchart TD A["POST /api/ses_callback"] --> B{"checkEventValidity"} B -->|"TopicArn not configured"| R1["401 rejected"] B -->|"TopicArn matches"| C{"verifySnsMessageSignature (new)"} C -->|"invalid or untrusted cert URL"| R1 C -->|"valid signature"| D{"Type == SubscriptionConfirmation"} D -->|"yes"| E{"isTrustedSnsUrl SubscribeURL (new)"} E -->|"untrusted"| R2["400 rejected, no fetch"] E -->|"trusted"| F["fetch SNS confirm URL, update setting"] D -->|"no"| G["SesHookParser.queue"]Detection evidence
Affected:
int_13ee3c91c001c2c8xfdj06a9Why this fix
The route is reachable in production and its only gate was a
TopicArnmembership check, which is an identifier rather than a secret. AWS requires an HTTPS SNS endpoint to verify the message signature before trusting the payload, and the unverified path reached the suppression and unsubscribe logic inses-hook-parser.ts. The change adds signature verification and host allowlists at the callback entry point. The one remaining item is optional:getTopicArnsstill returns every configured ARN, so scoping it per team stays open.Causal chain
5 files changed (+559/-12)
apps/web/src/app/api/ses_callback/route.api.test.ts: added, +156/-0apps/web/src/app/api/ses_callback/route.ts: modified, +30/-12apps/web/src/server/aws/sns-message-validator.ts: added, +177/-0apps/web/src/server/aws/sns-message-validator.unit.test.ts: added, +194/-0apps/web/src/types/aws-types.ts: modified, +2/-0Repository lint:
pnpm run lint(declared in package.json) fails over the whole repository (the declared target takes no file arguments), so the failure is not attributed to this change and did not block it; run it before merging.Lint output
Generated by Polylane. You can ask follow-ups by mentioning @polylane in a comment.
Summary by cubic
Verifies AWS SNS message signatures on the SES callback route so forged events are no longer trusted. Previously the route only checked that the TopicArn matched a configured value, so any request naming the right topic could trigger bounce/complaint handling, unsubscribe contacts, skew usage metrics, and fetch an attacker-controlled URL. The callback now verifies the SNS signature and only fetches SubscribeURL values pointing at the AWS SNS host for the message's region. Verification is still skipped in development.
Written for commit 6b8aab8. Summary will update on new commits.