Bump @simplewebauthn/server 13.3.3 -> 14.0.2 and @simplewebauthn/browser
13.3.0 -> 14.0.0, and keep React on an exact pin at 19.3.0 with react-dom
and both type packages moved alongside.
SimpleWebAuthn 14 changes one default the server relied on: the verifier
used to accept every algorithm the library knew (EdDSA, ES256, ES512,
PS256/384/512, RS256/384/512, RS1) and now accepts only the three it
offers. Pass both lists explicitly so a registration that succeeded
before still succeeds, and so the offered list stays EdDSA/ES256/RS256
on a runtime that would otherwise prepend ML-DSA-44. The option JSON of
all four ceremonies, and verification against stored credentials, match
the 13.x snapshots unchanged.
nodemailer 10 is a TypeScript rewrite and ships its own declarations, so
the hand-added @types/nodemailer package now shadows them with the types
of a release two majors old. The email sender keeps its default import
and its Transporter type from the package itself.
The new floor is Node 20; the image and the engines field are on 22.
The bump raised the CLI to 7.10.0 but left @prisma/client at 7.8.0 and
the migration CLI the image installs under /opt/prisma-cli at 7.8.0, so
the generated client, the one that ships and the one that migrates would
have come from two releases. All three are 7.10.0 now.
pnpm-workspace.yaml carried two overrides for the same packages: an older floor (hono < 4.12.25, js-yaml >= 4.0.0 < 4.3.1) and the newer one added for the scanner findings. The older selector won the resolution, so the lockfile kept hono 4.13.3 and js-yaml 4.3.1 and four of the seventeen findings stayed open after v1.38.12. The stale bounds are gone; the lockfile now resolves hono 4.13.7 and js-yaml 4.3.2.
Trivy flags twelve distinct advisories on the lockfile and the image, none
in this code: next 16.2.11 (two critical, fixed in 16.3.3), sharp 0.35.3
pulled by next (high), nodemailer 9.0.3 (one high, three medium), js-yaml
4.3.1 (high), hono 4.13.3 under the MCP SDK's node server (three medium),
and the vitest mocker and baseline-browser-mapping (medium). Dependabot
lists none of them, which is why they sat.
next moves to 16.3.4 and nodemailer to 9.1.1 as direct pins; vitest to
4.1.11. js-yaml, hono, baseline-browser-mapping and sharp are transitive
and move through pnpm overrides bounded to the vulnerable ranges, so the
copies the scanner names change and nothing else does. Typecheck, lint,
the unit suite and the production build pass locally; the release gate
runs the integration and end-to-end suites on the minor Next bump.
The off-host job was said to benefit from the streaming work the weekly
in-database backup got, and it did not. Only the payload builder was
shared: `runOffhostBackup` called `buildFullBackupJson` for the whole
document, `gzipSync` over that whole string, a whole-buffer
`createCipheriv` pass over the result, and handed the finished buffer to
a single `PutObject`. Four full copies of the record alive at once, on
the same arithmetic the weekly pass had already been fixed for.
Configuring an S3 target on a long-lived record therefore restarted the
container. On an account of 445 000 measurements the JSON alone is
242 MB, and against a 1 GB container's 524 MB heap the run died of heap
exhaustion seventeen seconds in — taking every signed-in session with
it, because the job shares the app process.
The write path is a stream end to end now: `streamFullBackupJson`
produces the document a page at a time, gzip and an incremental
AES-256-GCM writer consume it as it arrives, and `@aws-sdk/lib-storage`
puts it up in two 8 MB parts. What the process holds is fixed by the
pipeline's shape rather than by the record. Measured on that same
445 000-measurement account under `--max-old-space-size=450`, with the
heap pre-loaded to what a warm server holds: the old path dies with
`Ineffective mark-compacts near heap limit`, the new one finishes with a
peak 79 MB above its baseline and writes a 9.1 MB object.
The stored envelope gains version 3, which moves the GCM tag from in
front of the ciphertext to the end. That is the only change the stream
required — the tag exists only once the last block is in, so a leading
one means the whole object must exist before its first byte can be sent.
Nothing about the authentication moves: the tag still covers every
ciphertext byte and is verified before a byte of plaintext comes back.
Versions 1 and 2 still read, so every object already in a bucket
restores through the same reader with no flag to tell them apart.
The incremental writer is the crypto module's, split into its byte-level
half so the base64 form the in-database blob uses and the raw form an
object needs are one implementation under two framings. The off-host key
handling is unchanged: a separate `BACKUP_ENCRYPTION_KEY`, 64 hex or
32-byte base64, passed in rather than looked up.
A run that uploaded nothing for anybody is a failed job now, not
`ok: true` with an empty bucket behind it. Wrong credentials, a missing
bucket and an unreachable endpoint all fail every account rather than
one, and the target's own sentence rides out as the cause. A run where
some account got a copy still succeeds; retrying the whole cohort over
one object would re-upload everybody's.
The one ceiling left is structural rather than a memory bound, and it is
counted rather than discovered: a multipart upload carries 10 000 parts,
so an object past 80 GB is refused for that account with a clear message
instead of failing halfway through with an SDK error about part numbers.
`tests/integration/offhost-backup-streaming-memory.test.ts` pins the
budget from both sides — what the streaming uploader holds, and that the
materialising path does not fit the same budget on the same fixture in
the same process, so the number cannot pass by measuring nothing.
Three advisories stood open, all development-scope. Two are closed with
version floors in the workspace overrides, following the existing pattern:
@humanfs/node for GHSA-p498-v437-472g and esbuild for GHSA-g7r4-m6w7-qqqr.
Both patched lines stay inside the same minor, so nothing downstream moves.
The third, GHSA-jmr9-qjv8-65gv in extract-zip, had no patched release to
float to. It arrived through @openai/codex-security, which nothing in the
tree imports: the package was referenced only by its own declaration, a
knip ignore entry, and a release-age exclusion. Removing it closes the
advisory and takes about 340 MB of node_modules with it. The knip ignore
and the release-age exclusion go with it rather than being left to rot.
pnpm audit now reports no known vulnerabilities.
Four advisories landed against the URI parser that arrives under ajv,
including the copy the connector SDK pulls, all of them host confusion
or request forgery through percent-encoded schemes, skipped IDN
canonicalisation and malformed authorities. The floor set yesterday was
one patch below the fix. A query-string parser needs 6.16.0 for an
array-limit bypass and a denial of service.
The audit reads no known vulnerabilities after the bump.
Three overrides had drifted behind the advisories they were written for,
and the scan found the packages at versions the pins were supposed to
exclude.
dompurify moves from <=3.4.10 to <3.4.13. GHSA-55q2-fjhq-7xh7 is fixed
in 3.4.13 and the tree carried 3.4.12. The copy is jspdf's optional HTML
renderer, an optionalDependency at ^3.3.1, so the raise stays inside the
range jspdf declares.
postcss moves from <8.5.10 to <8.5.23 for CVE-2026-69153. next declares
an exact postcss@8.4.31, so nothing but this bound moves it; the build
has run on the 8.5 line since the first bump and stays there.
valibot gets a floor it never had, <1.4.2, for CVE-2026-59952. Two
copies arrive and neither is ours to call: an optional peer of
@hookform/resolvers, which this app does not use because it validates
with zod, and prisma's local dev server. 1.4.2 is the same major, so no
consumer is moved onto a new one.
@hono/node-server moves from <1.19.13 to <1.19.15 for
GHSA-frvp-7c67-39w9. The app tree already resolved 1.19.17, so this
changes nothing here; it exists so the bound states the real floor and
so the Dockerfile mirror has something correct to match.
The floor test in ci-determinism asserted the exact key and replacement
string, which meant every legitimate raise failed it while a floor gone
stale against a newer advisory passed it unchanged. It now reduces each
key to a package name and asserts the pin is at or above the fixed
version, with a case proving the comparison can return false.
The dependency audit went red on the release branch: mysql2 below 3.22.0
downgrades its auth plugin to mysql_clear_password and leaks the
credential. It reaches the tree only through prisma's MySQL driver, which
this app never opens, but the gate does not distinguish reachable from
present. Pinned to ^3.22.0 in the workspace overrides and mirrored with
npm's own override for the /opt/prisma-cli install, the pair the
lockstep guard holds together. The audit reads three moderates, below
the gate.
The Dockerfile asserts that the standalone image can resolve pg, because
pg-boss loads it through native Node resolution. That assertion has been
passing on a coincidence: pg sat in devDependencies, and the version our own
entry pinned happened to match the one @prisma/adapter-pg pulled in, so both
names resolved to a single copy in the store and Next's file tracer packed it
whole.
Bumping the adapter to 7.10.0 moves its transitive pg to 8.23.0 while ours
stayed at 8.22.0. The tracer packs only the copy the adapter reaches, the
top-level link points at the other one, and the image fails to build on both
architectures:
Error: Cannot find module '/app/node_modules/pg/lib/index.js'
Move pg into dependencies at ^8.23.0. The lockfile now carries one pg version
instead of two, so the two resolutions cannot drift apart again on the next
adapter release. Carries the adapter bump from #865, which cannot build alone.