Skip to content

fix(api): return 403 instead of 500 for a malformed API key - #468

Merged
KMKoushik merged 1 commit into
mainfrom
polylane/autofix/1va4dcqf9ocd
Oct 17, 2026
Merged

KMKoushik merged 1 commit into
mainfrom
polylane/autofix/1va4dcqf9ocd

Conversation

@polylane

@polylane polylane Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes: useSend public API returns HTTP 500 for a malformed Bearer token instead of 401

The public API replied with an internal server error when a caller sent a Bearer token that is not in the useSend key format, and logged each one as an internal failure. One automated client sends those requests about once a minute, roughly 1,270 a day. The malformed value now gets the 403 rejection that an unknown key already gets, and no longer raises an internal error.

flowchart TD
  A["Automated client: about 1 request per 60 s"] --> B["getTeamFromToken: bearer token present"]
  B --> C["getTeamAndApiKey: split the key on underscore"]
  C --> D["clientId undefined when the token has no underscore"]
  D --> E["Prisma rejects the lookup before the query"]
  E --> F["middleware logs and answers HTTP 500"]
  C -.->|"proposed fix: guard clientId and token"| G["return null, then 403 FORBIDDEN"]
  A -.->|"unidentified: edge HTTP logs are not readable"| H["caller identity unknown"]
Loading
Detection evidence

Affected: acc_13ee50413001wypxc1ww20ft

Why this fix

Production telemetry pairs each 500 with the same Prisma validation error within milliseconds, and the invocation dump shows the lookup running with an undefined client id, so every observed event is this one defect. The same client polls about once every 68 seconds, roughly 1,270 requests a day, continuous since at least 17 September, so the earlier count of 201 was a query-limit floor. Keys the product generates always have three non-empty parts, so no working key is affected. The caller's credential remains a separate config item outside this repository, and it now receives a 403 instead of a 500.

Fix walkthrough

Watch the fix walkthrough

Watch the fix explained (2:27). Recorded for commit 4bc016625b3d76a424c4ecf8434af347036ea58a.

Out of scope
  • The caller's invalid credential: it belongs to a client or monitor outside this repository, and its check keeps failing after this change until a person fixes or removes that header.
Causal chain
  • Signal (log template): Error in getTeamFromToken middleware
  • Surfacing site: railway.service unsend (production) at apps/web/src/server/public-api/hono.ts#getTeamFromToken middleware catch block
  • Mechanism: apiKey.split('_') yields no second element when the token has no underscore, so clientId is undefined; db.apiKey.findUnique({ where: { clientId: undefined } }) has neither id nor clientId, so Prisma raises a validation error before the query, which escapes the function because its try/catch starts after the call.
  • Producer: useSend public API auth path (next.js web app on railway.service unsend), at apps/web/src/server/service/api-service.ts#getTeamAndApiKey
  • Trigger: An internet request to /api/v1/* with Authorization: Bearer <value> where the value contains no underscore.
  • What happens to the failed unit today: The middleware answers UNAUTHORIZED for a missing header and FORBIDDEN for an unknown but well-formed key, so the contract for a bad credential is a 401/403-class answer. The catch block downgrades any other error to INTERNAL_SERVER_ERROR.
  • Cadence check: In the 24h to 2026-10-16T21:12Z, 201 middleware-error lines and 201 Prisma validation lines, paired to the microsecond; bursts recur at roughly one-minute intervals.
  • Blast radius: unknown other resource(s), unknown other tenant(s); data at risk: none named
  • Producer evidence:
    • apps/web/src/server/service/api-service.ts:59-77 shows the unguarded findUnique with clientId from apiKey.split('_').
    • apps/web/src/server/public-api/hono.ts:30-50 maps the thrown non-UnsendApiError to INTERNAL_SERVER_ERROR.
    • railwayGetLogs pairs the middleware error with the Prisma validation message at identical timestamps.
3 files changed (+160/-1)
  • apps/web/src/server/public-api/auth.api.test.ts: added, +81/-0
  • apps/web/src/server/service/api-service.ts: modified, +5/-1
  • apps/web/src/server/service/api-service.unit.test.ts: added, +74/-0

View thread View autofix


Generated by Polylane. You can ask follow-ups by mentioning @polylane in a comment.

Co-authored-by: polylane[bot] <277585245+polylane[bot]@users.noreply.github.com>
@polylane polylane Bot added polylane severity:low Polylane autofix severity: low labels Oct 16, 2026
@polylane
polylane Bot requested a review from KMKoushik October 16, 2026 21:19
@vercel

vercel Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
unsend-marketing Ready Ready Preview Oct 16, 2026 9:20pm UTC

@coderabbitai

coderabbitai Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: db45704f-5390-4dce-bac2-fee042d45ed9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying usesend with  Cloudflare Pages  Cloudflare Pages

Latest commit: 4bc0166
Status: ✅  Deploy successful!
Preview URL: https://f5292bd1.usesend.pages.dev
Branch Preview URL: https://polylane-autofix-1va4dcqf9oc.usesend.pages.dev

View logs

@polylane polylane Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested observability improvements for code this pull request changes.

Comment on lines +62 to +64
if (!clientId || !token) {
return null;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch replaces two error lines per malformed request with silence; one structured line keeps that traffic countable.

Suggested change
if (!clientId || !token) {
return null;
}
if (!clientId || !token) {
logger.info(
{ hasClientId: Boolean(clientId), hasToken: Boolean(token) },
"Rejected API token outside the useSend key format",
);
return null;
}

@polylane

polylane Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Tip

Expected to resolve the linked issue.

Linked issue: useSend public API returns HTTP 500 for a malformed Bearer token instead of 401

I read unsend in production. Each of the 708 getTeamFromToken 500s in the retained Railway log stream pairs within 5 ms with a Prisma where needs clientId error. Generated keys always have three non-empty parts, so the guard rejects no working key.

View the full analysis →

Also considered · 3 refuted
  • Refuted · Malformed-key guard rejects an API key that works today · The guard condition is !clientId || !token, and a generated key always yields both.
  • Refuted · Early return skips a side effect or breaks another caller of getTeamAndApiKey · No second caller exists in the tree, and the work the early return skips is the malformed Prisma lookup itself plus a lastUsed write that only fires for an authenticated key.
  • Refuted · Guard misses the shapes production sends, so the 500s continue after merge · Coverage holds for both malformed shapes: a value with no underscore fails on clientId, and a value with a client id but no secret fails on token, which already returned 403 before this change.

unsend · malformed-key auth errors per hour

Analysed against 1 Service and 1 repository

View in Polylane Disable reviews

Polylane analysed 4bc0166 for production impact. You can ask follow-ups by mentioning @polylane in a comment.

Did this help? React 👍 or 👎 so the next review is sharper.

@KMKoushik
KMKoushik merged commit 51d51e9 into main Oct 17, 2026
6 checks passed
@KMKoushik
KMKoushik deleted the polylane/autofix/1va4dcqf9ocd branch October 17, 2026 07:07

This branch was successfully deployed

1 active deployment
Preview — 4bc01662 Deployed Oct 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

polylane severity:low Polylane autofix severity: low

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant