Skip to content

fix(api): accept the Bearer scheme in any casing - #84

Merged
ralyodio merged 1 commit into
moshcoder:mainfrom
clawedassistant26:fix/bearer-scheme-case-insensitive
Jul 30, 2026
Merged

fix(api): accept the Bearer scheme in any casing#84
ralyodio merged 1 commit into
moshcoder:mainfrom
clawedassistant26:fix/bearer-scheme-case-insensitive

Conversation

@clawedassistant26

Copy link
Copy Markdown
Contributor

The bug

bearer() in apps/pwa/src/lib/apikey.mjs matched the literal string "Bearer ":

return h.startsWith("Bearer ") ? h.slice(7).trim() : null;

RFC 7235 §2.1 makes the auth-scheme name case-insensitive, so authorization: bearer mck_… is a valid way to present a moshcode API key. The exact-string check turned those requests away with 401 {"error":"invalid or missing API key"} — the same error a revoked or mistyped key gets, so it reads as a key problem rather than a casing problem.

Every API-key endpoint is affected, because they all go through bearer():

  • POST /api/approvals — CLI ingest (routes/approvals.mjs:26)
  • GET /api/approvals/:id — CLI long-poll (routes/approvals.mjs:97)
  • GET /api/me (routes/cli.mjs:98)

Scope, stated honestly

The bundled CLI is not affected — src/auth.mjs:140 and src/notify.mjs:37,71 both send Bearer, so nothing in this repo hits it. This shows up for third-party and hand-rolled clients: curl one-liners, CI scripts, and HTTP libraries that normalise or lowercase the scheme. The settings page tells people to "send Authorization: Bearer <key>", and a client that follows the spec rather than the exact casing gets a 401 with nothing to go on.

Reproduction

Against unmodified main (df9d1e0), real cliRouter + approvalsRouter on a throwaway libsql database, one real API key minted through createApiKey. No stubs, no fault injection:

Request Before Expected
Bearer <key>/api/me 200 200
bearer <key>/api/me 401 200
BEARER <key>/api/me 401 200
bearer <key>POST /api/approvals 401 201

The fix

One line, plus a comment explaining why:

return /^Bearer /i.test(h) ? h.slice(7).trim() : null;

Only the scheme is matched loosely. The token is still sliced and compared byte-exact, so nothing about key verification changes.

Tests

New apps/pwa/test/apikey-bearer-scheme.test.mjs, 6 tests, built on the existing cli-token.test.mjs harness (skip-guard for missing PWA deps + throwaway libsql).

Three of them are the bug: lowercase and uppercase schemes on /api/me, and a lowercase scheme on POST /api/approvals. The other three are controls that pass both before and after the patch, which is what shows the scheme match was widened rather than auth being loosened:

  • the canonical Bearer path still authenticates
  • a wrong key still 401s under Bearer, bearer and BEARER
  • a missing header, a Basic scheme, a bare token with no scheme, and Bearer<key> with no space all still 401

Fail-before verified by restoring the pristine apikey.mjs and re-running: 3 fail / 33 pass unpatched, 36/36 patched. apps/pwa goes 30 → 36 tests, root npm test 204 → 210, zero failures either way.

RFC 7235 section 2.1 makes the Authorization auth-scheme name
case-insensitive, but bearer() matched the literal string "Bearer ".
A client sending `authorization: bearer mck_...` was turned away with
401 "invalid or missing API key" on every API-key endpoint: the CLI
ingest POST /api/approvals, the poll GET /api/approvals/:id, and
GET /api/me. The key was valid; only its scheme casing was not.

Match the scheme with a case-insensitive test. The token itself is
still taken byte-exact, so a wrong key, a missing header, a different
scheme, and a scheme with no space after it all still 401.

Adds apps/pwa/test/apikey-bearer-scheme.test.mjs (6 tests) covering
lowercase and uppercase schemes on /api/me and /api/approvals, plus
the canonical Bearer path and the rejection cases as controls.
@ralyodio
ralyodio merged commit d3b363c into moshcoder:main Jul 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants