v0.1.2
Reformat what the strictness pass reflowed
Two files-worth of blank lines and one long call the formatter wraps
differently now that a helper sits above them. CI resolves biome from a
caret range and installs a newer one than the lockfile pins.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01CGEy3LzUWzMGAruWS7JEDP
Turn on noUncheckedIndexedAccess
It was not missing here — it was explicitly false, in sixteen of the
seventeen tsconfigs. eon alone had it on, which is why nobody had seen
what it finds.
It stays a named deviation from upstream: @adonisjs/tsconfig sets
strictNullChecks and noImplicitAny but not this one. We keep it because
turning it on is what caught an as asserting a possibly-absent regex
group was a known value — the exact shape the flag exists to find. Doing
better than upstream is kept and written down, not reverted to parity.
Every site is restated rather than silenced: no !, no cast, no ?? 0
standing in for a branch that cannot happen. A reversed copy read by
value where an index walked a callback list backwards, the winner of a
scan kept as the value it found rather than its position, destructuring
where a length check was doing the proving, and an explicit break where a
loop condition already bounds the read.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01CGEy3LzUWzMGAruWS7JEDP
Say what container.make() returns for the tokens this package binds
ream declares ContainerBindings open on purpose: it registers its own
entries and expects each package to contribute the ones it owns — its
comment on the interface names auth (warden), logger (spectrum) and db
(atlas) as exactly this. None of them did, and every other package that
binds a string token was in the same state, so container.make('cache'),
make('mail'), make('hash') and the rest all answered unknown and
every call site had to assert a type it could not prove.
Loaded from the barrel AND from the provider, the second of which is where
AdonisJS puts its own (providers/redis_provider.ts carries the
declare module for redis, database_provider.ts for lucid.db).
Verified live rather than assumed: a declare module naming a specifier
that does not resolve is silently inert, so renaming the member has to
break the compile. It does.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01CGEy3LzUWzMGAruWS7JEDP
Check the claims instead of asserting them
assertIdTokenClaims ended on a cast that promised iss, sub, aud
and exp were the types its checks had established. Three of the four
were; iss was only ever compared, never type-checked, and aud was
accepted whenever the expected audience appeared anywhere in it — so a
list holding the right string beside arbitrary junk passed, and the claim
went on described as string[] while carrying something else. Both are
checked now, and the return value is built from the narrowed locals
rather than asserted over the bag they came from.
The three remaining as unknown as / as never casts turned out to
assert nothing the compiler did not already accept, and are gone.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01CGEy3LzUWzMGAruWS7JEDP
Release 0.1.2
Try every certificate the provider's metadata lists
A SAML signature was verified against the first listed certificate and no
other, so an assertion signed with the second was rejected with "the signature
does not verify".
That is precisely a key rotation. A provider preparing one publishes the
outgoing and the incoming certificate together and may sign with either, which
is why certificates is a list and why its own doc-comment says several are
accepted. Every sign-in would have failed for the length of the rotation
window, blaming the signature rather than the key lookup.
It only bites when the document carries no of its own —
KeyInfo is optional in XML-DSIG and plenty of providers omit it. With one
embedded, the certificate still has to match a listed one exactly and that one
alone is used, unchanged. Every existing test signed with KeyInfo present, so
the whole suite exercised the matching path and none of it reached this one.
JwksCache already handles the OIDC side of the same problem by refetching, so
the two halves of this package now agree.
Also compares the OAuth state with timingSafeEqual. The state is what stands
between an attacker's authorization code and the victim's session, and !==
stops at the first differing byte. Every other secret comparison in the
framework is already constant-time; this one was the outlier.
Changes since v0.1.1.