Skip to content

chore(core,express): post-release audit follow-ups - #113

Merged
Bccorb merged 1 commit into
mainfrom
chore/post-release-followups
Jul 21, 2026
Merged

chore(core,express): post-release audit follow-ups#113
Bccorb merged 1 commit into
mainfrom
chore/post-release-followups

Conversation

@Bccorb

@Bccorb Bccorb commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the post-release follow-up items from the pre-release audit (#101). Each item was re-verified against current main (some had comment/ordering context added by intervening PRs, but all were still present).

Closes #101.

Substantive

  • Bounded the refresh-result cache (packages/core/src/refreshAccessToken.ts). Entries were keyed by the rotating refresh cookie and only deleted on a later lookup of the same key, which never happens once a token rotates, so the map grew without bound and retained access tokens, refresh tokens, email, phone, and roles for the process lifetime. It now sweeps expired entries (throttled to at most once per TTL window, off the hot path) and caps total size at 10k as a burst backstop. The dedup behavior the cache exists for is unchanged and still covered by the existing test.
  • Memoized the JWKS key set (packages/core/src/verifySignedAuthResponse.ts). createRemoteJWKSet was constructed inside the function, so jose's per-instance key cache and refetch cooldown never engaged and every login, OAuth callback, OTP verify, and magic-link poll made an extra request to /.well-known/jwks.json. Now memoized in a module-level map keyed by JWKS URL (one entry per auth server).

Type accuracy (minor breaking)

  • SeamlessAuthUser.email is now optional and phone is string | null, matching CookiePayload and the upstream /users/me schema (phone: z.string().nullable()). requireAuth copies these straight from the verified cookie, so the previous string/string types were unsound. README updated to match. Consumers that read phone as a non-null string will get a type error and need to handle null.

Redaction wired in (was dead but tested)

  • redactSensitiveText existed in core with a passing test but was exported from nowhere and called by nothing, leaving the false impression of a logging safety net. Rather than delete tested code, it is now exported from core (added to the public-exports test and the core README) and used in the Express router error handler to mask tokens and secrets in the logged 5xx detail. Added a test asserting a bearer token in an error message is redacted.

Small correctness / hygiene

  • Reordered /magic-link/check above /magic-link in COOKIE_REQUIREMENTS so it is no longer shadowed by the prefix match (identical config today, so behavior is unchanged, but it would have silently stopped applying if either diverged).
  • routeParam now throws a clear error on a missing parameter instead of forwarding the literal string "undefined" upstream.
  • Fixed the Missing cookieSecret error that named the removed COOKIE_SIGNING_KEY env var.
  • Dropped a redundant .end() after res.status(...).json(...) in finishLogin.

Deliberately not in this PR

  • The as any option-object casts (audit bullet 3) are tracked separately as Remove redundant option-object casts in the Express adapter #106; left out to keep that its own reviewable change.
  • The /internal/bootstrap/admin-invite question is server-side (seamless-auth-api), not this repo. I can verify it against the API separately.

Checks

core:    18 suites, 84 tests passed
express: 25 suites, 133 tests passed
build:   both packages Done

No em dashes. Changeset included (minor, both packages).

Bound the refresh-result cache: entries were keyed by the rotating
refresh cookie and never looked up again, so the map grew unbounded and
retained tokens for the process lifetime. Sweep expired entries (throttled
off the hot path) and cap total size as a backstop.

Memoize the JWKS key set per auth-server URL in verifySignedAuthResponse.
It was rebuilt inside the function on every call, so jose's key cache and
refetch cooldown never engaged and every verification made an extra
request to the auth server's JWKS endpoint.

Make SeamlessAuthUser.email optional and phone nullable to match the
cookie payload and the upstream users/me shape.

Export redactSensitiveText from core and use it to mask tokens and secrets
before the Express router logs an unhandled 5xx error. The utility was
tested but wired nowhere, which left a redaction safety net that did not
exist.

Reorder the magic-link/check cookie requirement out from under the
magic-link prefix, throw on a missing route parameter instead of
forwarding the literal string undefined, correct the cookieSecret error
message that named a removed environment variable, and drop a redundant
res.json(...).end() call.
@Bccorb
Bccorb merged commit d3e9274 into main Jul 21, 2026
1 of 2 checks passed
@Bccorb
Bccorb deleted the chore/post-release-followups branch July 21, 2026 20:02
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.

chore: post-release follow-ups from the pre-release audit

1 participant