Security: newsletter unsubscribe requires a signed token
Anyone could remove someone else from your newsletter knowing only their email. Gmail's one-click unsubscribe works again too, and the Claude format hook stops reformatting the whole repo.
The second security release of the day. It closes a smaller hole than 2.1.2, but one worth applying: nobody could read data, yet anyone could remove subscribers from your newsletter.
Unsubscribing requires a token
POST /api/newsletter/unsubscribe unsubscribed any email it received, without checking anything.
Knowing someone's email was enough to take them off your list.
It now requires a token signed with BETTER_AUTH_SECRET, the same one the unsubscribe page links
already carried. Without a token it answers 400, with an invalid token or one for another email
403, and with a valid one it always gives the same answer, so it does not reveal who is subscribed.
If the secret is missing, every unsubscribe is rejected instead of signing with an empty key.
Gmail's unsubscribe button works
The List-Unsubscribe headers pointed to the endpoint without an email or a token, and the Origin
protection rejected the one-click request mail providers send, which carries no such header.
Gmail's "Unsubscribe" button never worked. The headers now carry the email and the token, and the
endpoint skips the Origin check: the token is the proof.
The Claude format hook
The hook that formats every file Claude edits read a variable Claude Code never sets. With an empty path, Prettier reformatted the whole repo on every edit, and ESLint errors were lost. It now formats only the edited file and sends back to Claude the errors that do not fix themselves, so they get fixed in the same turn.
If you already have a project
- Copy
src/lib/email/unsubscribe-links.tsand usebuildUnsubscribeLinks()insrc/app/api/newsletter/subscribe/route.tsfor the headers and the email link. - In
src/app/api/newsletter/unsubscribe/route.ts, reademailandtokenfrom the query or the body (one-click arrives as a form, not JSON). If either is missing, 400. IfverifyUnsubscribeTokenfails, 403. - Add
if (!getSecret()) return false;at the top ofverifyUnsubscribeToken(). - Add
'/api/newsletter/unsubscribe'toORIGIN_VALIDATION_BYPASS_PREFIXESinsrc/proxy.ts. - Check your own emails with
grep -rn "unsubscribe" src/lib/email src/app/api: an unsubscribe link without a token stops working, and a subscriber who cannot unsubscribe is a legal problem. - For the hook, copy
.claude/hooks/format-lint.tsand thePostToolUseblock of.claude/settings.json.
No migrations and no new environment variables. Note: rotating BETTER_AUTH_SECRET invalidates the
unsubscribe links already sitting in inboxes.