Skip to content

fix: v0.21 security review follow-ups for the API - #567

Merged
Bccorb merged 13 commits into
mainfrom
fix/v021-security-followups
Nov 3, 2026
Merged

Bccorb merged 13 commits into
mainfrom
fix/v021-security-followups

Conversation

@Bccorb

@Bccorb Bccorb commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Fixes the six API findings from the v0.21 security review (epic #558). Each was reproduced on main first: every issue has a test(...) commit that fails there, followed by its fix. The test commits were made with --no-verify, because the pre-commit hook runs the full coverage suite and a deliberately failing test cannot pass it. Lint, format, typecheck and commitlint were run on them by hand, and every fix commit went through the hook.

Closes #552
Closes #554
Closes #555
Closes #557
Refs #553 (partly: the shared rate-limit store stays open)
Refs #556 (parts 1 and 3: the delivery token subject stays open)

The release PR #566 is untouched. This merges to main and ships with v0.23.0 when the milestone is done.

Review first

  • Derive hardware-bound passkey assurance from the registered credential #552 changes token claim values. Device binding now comes from the stored deviceType, and counts only when attestation was verified. Deployments on the default none attestation, and credentials registered before the device type was stored, now get acr=phr / amr=swk instead of phrh / hwk. A relying party that requires phrh stops being satisfied unless the deployment verifies attestation. An assertion whose backup-eligible flag differs from registration gets 401.
  • Keep application notice text from carrying its own links and attribution #554 refuses filename-like words. Notice text with a URL scheme, www., a dotted domain (including fullwidth dots) or an email is refused with 400. That also refuses Node.js or file.txt. @seamless-auth/types does not carry the rule yet, so SDK-side validation will not catch it.

The rest

Changesets: #552, #554, #555 and #557 are minor because they change existing behavior. The others are patch.

Checks

Unit suite 2833 passed (1 expected fail, 1 todo). Postgres suite 2891 passed. Coverage 99.03% statements, 95.82% branches. format, lint, typecheck, build pass, and generate:api shows no drift.

Bccorb added 13 commits November 2, 2026 21:27
…credential

Device binding now comes from the deviceType stored at registration, and
counts only when that registration's attestation was verified. An assertion
whose backup eligibility flag differs from the stored value is refused at
sign-in and step-up.

Refs #552
The schema accepts upper-case UUIDs and Postgres matches them
case-insensitively, so case variants of one id each got a bucket of their
own. The multi-instance note in production operations now names the notice
limits, which stay per instance until a shared store exists.

Refs #553
…tribution

Notice text that contains a URL, a www address, a domain name or an email
address is refused, so actionUrl stays the only link. The attribution moves
to the first line, where the text cannot precede it with an imitation.

Refs #554
findActiveOidcClient now treats a client that is not first-party as
inactive while third_party_clients_enabled is off, so client metadata,
secret checks, /oidc/sign, notice tokens and back-channel logout all fail
closed, the same pattern verifyOidcClientSecret uses for
allow_client_secrets.

Refs #555
…es in every mode

Every response now starts with Cache-Control: no-store, which routes that
publish public material (JWKS, hashed dashboard assets) override. Provider
requests use the guarded socket in every mode; outside production any host
may be private, but link-local and unspecified addresses stay refused,
whether named or resolved. The socket leg moves to providerTransport so the
suite can keep answering provider calls from a stubbed fetch.

Requiring the external delivery subject is left for a coordinated change
with the seamless-cli conformance harness; its reproducing test is marked
as an expected failure.

Refs #556
Under external delivery, a notice whose kind the caller does not list in
x-seamless-auth-delivery-kinds is now sent through the API's own transport
for its channel when one is configured, instead of being recorded as
not_sent_external_delivery and dropped.

Refs #557
@Bccorb
Bccorb merged commit 25c4cc6 into main Nov 3, 2026
7 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

1 participant