oficina / home Public

Record what the SSO session caught, and one trap it points at for step 3

Coordinated with the rastrillo SSO/OIDC session to check whether that
work supersedes the assertion contract. It does not — separate plugins,
different credential, same sessions.SignIn seam — and nobody has picked
up the implementation. Three things came back worth keeping.

A GAP IN THE MERGED DESIGN. It has no browser binding at all; verified
by searching it for browser, cookie, bound, transaction and CSRF. Its
replay controls are jti single-use plus short expiry, and neither stops
the attack: an attacker completes their own handoff, does not use the
assertion, hands it to a victim, and the victim is signed in as the
attacker. Single use prevents a second use, not a first use by the
wrong person.

Home §5.6 already requires a browser-bound state nonce for exactly this
reason. rastrillo's design does not, and rastrillo's is what would be
implemented, so every consumer would inherit it. Third time this same
attack has surfaced here, after the GET-redeemed magic link and the
keymail callback.

NEVER KEY ENFORCEMENT ON sessions.Session.Method. passkey rewrites it
three ways — "<first>+passkey", "<first>+recovery", and bare "passkey"
— so any "was this a strong sign-in?" check silently changes answer
after a step up. Their spec adds Provider, "<method>:<tenant>", for
enforcement to read instead. Home has no such bug today: it only ever
writes Method and never reads it for a decision, verified by grep. Step
3 is where one would appear.

A SEQUENCING DEPENDENCY. Provider and sessions.Handle/Lookup/Revoke
land on their sso-saml branch first, so this step waits on that branch
as well as on §9.

Q3 is also settled from their side: they are not claiming the `identity`
package name, so `assertion` is free.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 7d68e870a07e0e11b951ec678628487a0e2040b5 parent 4a442a1
1 file changed, +45 −0
  • docs/superpowers/plans/2026-09-07-home-assertion.md +45 −0

CI passed — run details

diff --git a/docs/superpowers/plans/2026-09-07-home-assertion.md b/docs/superpowers/plans/2026-09-07-home-assertion.md
index 05e3dc7..33638ac 100644
--- a/docs/superpowers/plans/2026-09-07-home-assertion.md
+++ b/docs/superpowers/plans/2026-09-07-home-assertion.md
@@ -88,6 +88,30 @@ executor that has decided something it should not.
## Three things to settle before Task 2 — not the executor's to decide
+### Two things to adopt from the SSO plugin work, not re-derive
+
+From the rastrillo SSO session, 2026-09-07, and verified here:
+
+**Never key an enforcement decision on `sessions.Session.Method`.**
+`passkey` rewrites it: `SignInFinish` writes `"<first factor>+passkey"`,
+`SignInRecovery` writes `"<first factor>+recovery"`, and `StepUpFinish`
+writes bare `"passkey"` (verified at the v0.22.0 pin). So a check like
+"was this a magic-link session?" silently changes answer after a step
+up. Their spec adds `sessions.Session.Provider`, written
+`"<method>:<tenant>"` — an assertion plugin would write
+`"assertion:<issuer>"` — precisely so enforcement has something stable
+to read.
+
+**Home has no such bug today**: `grep` shows it only ever WRITES
+`Method` and never reads it for a decision. Step 3 is exactly where one
+would appear, since the design's §6 mints `Method: "assertion"` and the
+obvious next thing anybody writes is "was this a strong sign-in?".
+
+Also worth importing rather than rebuilding: `sessions.Handle/Lookup/
+Revoke`, which is what lets a re-authentication revoke the session that
+started it. Both land on their `sso-saml` branch first, so **this step
+has a sequencing dependency on that branch**, not just on §9.
+
### Ruling A: SUPERSEDED — rastrillo already has a merged design
**Read this before anything below it.** Checked 2026-09-07:
@@ -120,6 +144,27 @@ claim; nothing before the signature check may have a side effect.
app to hand-roll assertion verification differently, which is the exact
outcome discussion #1 exists to prevent.
+**A GAP IN THE MERGED DESIGN, found by the rastrillo SSO session and
+verified here.** The design has **no browser binding at all** — searched
+for browser, cookie, bound, transaction and CSRF, and its only replay
+controls are `jti` single use plus short expiry (§4). Neither stops this:
+an attacker completes their own handoff, does not use the assertion, and
+hands it to a victim, who is signed in as the attacker. Single use
+prevents a SECOND use, not a first use by the wrong person; short expiry
+only shortens the window.
+
+Home §5.6 already requires "a browser-bound state nonce" and gives this
+exact reason. rastrillo's design does not, and rastrillo's is the one
+that would be implemented — so every consumer would inherit the gap.
+Their fix on the SSO side is a transaction cookie set on the outbound
+leg with its hash on the request row; the shape home already uses for
+the keymail pending cookie.
+
+This is the third time this one attack has surfaced on this project:
+the GET-redeemed magic link, the keymail callback, and codex naming it
+unprompted as the most commonly forgotten thing when asked about
+assertions.
+
**Two conflicts for Paul, both between specs rather than within one:**
1. Home §5.3 says "Signature: Ed25519". rastrillo's design says ECDSA