Summary
kv-oauth returns real 302 responses whose Set-Cookie headers carry first the OAuth transaction id
and then the session id. Both auth handlers throw those responses away and keep only the Location
value. The contract makes this unfixable at the handler level: none of the five auth routes declares
outputStructure: 'detailed', so no procedure in authContractV1 can emit a response header at all.
The result is that the only interactive backend NetScript ships cannot complete a browser sign-in
through its own published API — the transaction cookie never reaches the browser, so the callback
fails with oauth_cookie_missing, and the session cookie never reaches the browser, so GET /session
and GET /me cannot see it afterwards.
Evidence
- Corpus:
research/repo-audit/auth.md §4.1, gap G5.
plugins/auth/services/src/routers/v1-handlers.ts:95-107 — signin calls interactive.signIn(...)
and keeps only responseLocation(response); the Response object is discarded.
plugins/auth/services/src/routers/v1-handlers.ts:162-169 — callback builds
{ completed, sessionId, redirectTo, subject } from result and discards result.response.
plugins/auth/services/src/routers/v1-helpers.ts:82-84 — responseLocation(response) returns
response.headers.get('location'); there is no Set-Cookie reader anywhere in the helpers.
packages/plugin-auth-core/src/contracts/v1/auth.contract.ts:437-461 — all five routes are
baseContract.route({ method, path }).input(...).output(...); no outputStructure: 'detailed'
on any of them.
packages/auth-kv-oauth/src/flow.ts:143 (transaction cookie on the signin redirect) and :220-224
(session cookie on the callback redirect) — the backend does emit both.
packages/auth-kv-oauth/src/flow.ts:155-159 — handleCallback throws oauth_cookie_missing
unless the txn cookie or an explicit txn parameter is present; v1-handlers.ts:157-160 forwards
only providerId, code and state, never txn.
- The inbound half already works:
v1-helpers.ts:40-60 (toRequest) forwards inbound headers into
the backend Request, and toAuthnRequest (:64-78) reads the session cookie. The defect is
strictly the response half.
- The server-side write-back path exists and is unused by this surface:
AuthnResult.setCookies / responseHeaders applied by applyAuthnResponse
(packages/service/src/auth/auth-middleware.ts:166-177).
Current surface
signin returns { started, providerId, redirectUrl, state }; the caller is expected to follow
redirectUrl in a browser that never received the transaction cookie. callback returns
{ completed, sessionId, redirectTo, subject } — the session id is handed to the caller in the JSON
body, which is the only way it can be observed, and which is itself a credential leak into logs and
client state. Docs assert the opposite behaviour
(docs/site/identity-access/how-to/add-authentication.md:243,273).
Target contract
signin, callback and signout declare outputStructure: 'detailed' in
auth.contract.ts so their handlers can return headers alongside the body.
- The handlers propagate the backend response's
Set-Cookie headers verbatim — including the
__Host- prefix, Path=/, Secure, HttpOnly and SameSite attributes the backend already
asserts (packages/auth-kv-oauth/src/cookies.ts:105-121) — without re-encoding or re-signing them.
callback forwards the txn parameter when present so the documented explicit-transaction path
works for non-cookie callers.
- The session id stops being returned in the
callback response body once the cookie path works,
or is retained only behind an explicit non-browser flag; either way the change is recorded as
contract-breaking.
signout returns the session-clearing Set-Cookie the docs already promise.
Acceptance
Boundaries
Docs/consumer proof
docs/site/identity-access/how-to/add-authentication.md:243 ("Revoke the current session and clear
the session cookie") and :273 ("After completing the browser sign-in, the session cookie is set")
are currently unverifiable claims. Adoption is proven when the documented curl -c cookies.txt /
curl -b cookies.txt sequence in that file executes end to end against a generated project, and when
the tutorial docs/site/tutorials/workspace/02-auth.md no longer needs to pass a session id by hand.
Provenance
Seed run plan-fable5-remediation-roadmap--seed, PR #1347, 2026-08-08. Sourced from
research/repo-audit/auth.md gap G5; all cited line numbers re-verified against worktree
fac9e339042c on 2026-08-08. Split from the TA-03 cluster so each session-lifecycle defect carries
its own acceptance and negative tests.
Filed from planning seed PR #1347 · source Draft-ID TA-03b · live issue #1385.
Summary
kv-oauthreturns real 302 responses whoseSet-Cookieheaders carry first the OAuth transaction idand then the session id. Both auth handlers throw those responses away and keep only the
Locationvalue. The contract makes this unfixable at the handler level: none of the five auth routes declares
outputStructure: 'detailed', so no procedure inauthContractV1can emit a response header at all.The result is that the only interactive backend NetScript ships cannot complete a browser sign-in
through its own published API — the transaction cookie never reaches the browser, so the callback
fails with
oauth_cookie_missing, and the session cookie never reaches the browser, soGET /sessionand
GET /mecannot see it afterwards.Evidence
research/repo-audit/auth.md§4.1, gap G5.plugins/auth/services/src/routers/v1-handlers.ts:95-107—signincallsinteractive.signIn(...)and keeps only
responseLocation(response); theResponseobject is discarded.plugins/auth/services/src/routers/v1-handlers.ts:162-169—callbackbuilds{ completed, sessionId, redirectTo, subject }fromresultand discardsresult.response.plugins/auth/services/src/routers/v1-helpers.ts:82-84—responseLocation(response)returnsresponse.headers.get('location'); there is noSet-Cookiereader anywhere in the helpers.packages/plugin-auth-core/src/contracts/v1/auth.contract.ts:437-461— all five routes arebaseContract.route({ method, path }).input(...).output(...); nooutputStructure: 'detailed'on any of them.
packages/auth-kv-oauth/src/flow.ts:143(transaction cookie on the signin redirect) and:220-224(session cookie on the callback redirect) — the backend does emit both.
packages/auth-kv-oauth/src/flow.ts:155-159—handleCallbackthrowsoauth_cookie_missingunless the txn cookie or an explicit
txnparameter is present;v1-handlers.ts:157-160forwardsonly
providerId,codeandstate, nevertxn.v1-helpers.ts:40-60(toRequest) forwards inbound headers intothe backend
Request, andtoAuthnRequest(:64-78) reads the session cookie. The defect isstrictly the response half.
AuthnResult.setCookies/responseHeadersapplied byapplyAuthnResponse(
packages/service/src/auth/auth-middleware.ts:166-177).Current surface
signinreturns{ started, providerId, redirectUrl, state }; the caller is expected to followredirectUrlin a browser that never received the transaction cookie.callbackreturns{ completed, sessionId, redirectTo, subject }— the session id is handed to the caller in the JSONbody, which is the only way it can be observed, and which is itself a credential leak into logs and
client state. Docs assert the opposite behaviour
(
docs/site/identity-access/how-to/add-authentication.md:243,273).Target contract
signin,callbackandsignoutdeclareoutputStructure: 'detailed'inauth.contract.tsso their handlers can return headers alongside the body.Set-Cookieheaders verbatim — including the__Host-prefix,Path=/,Secure,HttpOnlyandSameSiteattributes the backend alreadyasserts (
packages/auth-kv-oauth/src/cookies.ts:105-121) — without re-encoding or re-signing them.callbackforwards thetxnparameter when present so the documented explicit-transaction pathworks for non-cookie callers.
callbackresponse body once the cookie path works,or is retained only behind an explicit non-browser flag; either way the change is recorded as
contract-breaking.
signoutreturns the session-clearingSet-Cookiethe docs already promise.Acceptance
signin,callbackandsignoutdeclareoutputStructure: 'detailed'.Set-CookiefromsignInreaches the HTTP response unmodified.Set-CookiefromhandleCallbackreaches the HTTP response unmodified.signoutemits a session-clearingSet-Cookie.callbackforwards an explicittxnparameter to the backend when supplied.__Host-,Path=/,Secure,HttpOnly,SameSite) survive the round trip.signin→ provider →callbackround trip without the txn cookie still fails, and fails withoauth_cookie_missingrather than silently succeeding.GET /sessionafter a completed callback resolves the session from the cookie only, with no session id in the request body or query.SecureorHttpOnlyis rejected by the backend's cookie-policy assertion./api/v1/auth/*) and RPC (/api/rpc/v1/auth/*) projections of each route.docs/site/identity-access/how-to/add-authentication.md:243,273are true against the shipped code.Boundaries
POST /api/v1/auth/signoutrevokes any session id an unauthenticated caller supplies #1384 owns it.origin: '*', which makes credentialed browser calls impossible on every generated and plugin service #1386 owns both; thisissue makes the server emit correct cookies, fix(service): the default CORS policy is
origin: '*', which makes credentialed browser calls impossible on every generated and plugin service #1386 makes a browser able to keep them.accepted debt (
.llm/harness/debt/arch-debt.md:1313,auth-single-active-backend-boundary) and[enterprise-auth S3] Compose multiple auth backends with tenant-aware routing #874 owns the routing gap.
Docs/consumer proof
docs/site/identity-access/how-to/add-authentication.md:243("Revoke the current session and clearthe session cookie") and
:273("After completing the browser sign-in, the session cookie is set")are currently unverifiable claims. Adoption is proven when the documented
curl -c cookies.txt/curl -b cookies.txtsequence in that file executes end to end against a generated project, and whenthe tutorial
docs/site/tutorials/workspace/02-auth.mdno longer needs to pass a session id by hand.Provenance
Seed run
plan-fable5-remediation-roadmap--seed, PR #1347, 2026-08-08. Sourced fromresearch/repo-audit/auth.mdgap G5; all cited line numbers re-verified against worktreefac9e339042con 2026-08-08. Split from the TA-03 cluster so each session-lifecycle defect carriesits own acceptance and negative tests.
Filed from planning seed PR #1347 · source Draft-ID TA-03b · live issue #1385.