G10 waits on custody and platform owners and cannot close from here. What was
doable: verify the handoffs actually went out, replace a remembered live state
with an observed one, and find out whether main is safe to deploy. The last
question found a defect in this repository's own recent work.
Handoffs verified independently rather than trusted: all seven messages are in
the hub with receipt ids. This gap was reopened once for claimed-but-unsent
delivery, so the claim deserved the same scrutiny.
Live state read from the cluster read-only: image main-153258b, only the Qonto
secret materialized so the approval clients remain unprovisioned, four registered
clients, no tenantEngine block. That also corrects an earlier claim of mine --
the deployed config sets userOU explicitly, so the KEY-WP-0023 default fix was
never a production issue.
The precondition: KEY-WP-0019 discovers the expected issuer from
authelia.tokenBaseURL, and the deployed Authelia derives its advertised issuer
from the request Host, advertising the in-cluster address to KeyCape and the
browser-facing one to browsers. Verification fails closed, so a mismatch breaks
every human login and looks like a broken login rather than a misconfiguration.
Which value the token carries needs a real login against production to settle and
was not determined here.
Two mitigations: docs/operations.md documents pinning authelia.issuer and
jwksUrl, with the curl that reveals what the provider advertises for a given
Host; and the authentication failure event now carries a specific reason, so
id_token_issuer_mismatch is distinguishable from a signature failure or an
unreachable key set. The browser still learns nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 713576@bnt-lap001
Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
Closes the remaining half of gap G01. The adapter decoded upstream ID-token
claims without verifying anything, justified in a comment by a server-to-server
TLS boundary that nothing enforced.
Operator decision: verify the token rather than police the transport. The hop is
to be HTTPS as defence in depth, but KeyCape does not monitor, check or gate on
that -- a transport check helps only when it is configured correctly, which is
the assumption it was meant to remove. Verification holds regardless of how the
token arrived, so no HTTPS validation or opt-in flag is added.
HandleCallback now verifies the RS256 signature against Authelia's published
keys, the issuer Authelia advertises, KeyCape's own client ID in the audience,
and a sane validity window, before any claim is trusted. It fails closed: an
unreachable or unparseable key set denies the login. The advertised jwks_uri
path is rebased onto the server-side token base URL so split-horizon deployments
resolve, with config overrides where that inference is wrong, and an unknown key
id triggers one refresh so provider rotation needs no restart.
The reusable half lives in internal/jose rather than being copied from
authclient's verifier, since duplicated verification is how two copies drift and
one misses a fix. Migrating authclient onto it is tracked as KEY-WP-0019-T05,
kept separate so it does not destabilise a tested path in this change.
Thirteen rejection cases plus algorithm and rotation coverage; with the
unverified parse restored all fifteen fail, so they test the fix rather than
merely passing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 713576@bnt-lap001
Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
The adapter was forwarding the downstream client's client_id and
redirect_uri to Authelia, which would always be rejected — Authelia
only recognises client_id=keycape and its registered callback URI.
Also removed downstream PKCE forwarding: KeyCape is a confidential
OIDC client to Authelia and authenticates via client_secret instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>