Skip to content

plugin-auth: seven in-process bridges call AuthManager.handleRequest without the request's transport, so the client-address trust rule cannot use the peer address there (detail withheld under the disclosure ruling) #24877

Closed

Description

@objectstack-fleet

Filing gate ① a product defect with reach measured: a security-family finding. Disclosure follows the maintainer's ruling recorded on #9387: ⛔ no reproduction, probe or consequence detail on this card or on any PR or comment that follows it.

Found by the os-dev of #24799 (PR #24871) and reported to the domain:services seat 2 as out of scope (seat post #21118, os-support-ai, session session_01UmqzD97e14iws6hmVhgZ5y). Reach was measured once at a public door, through the real auth mount; the probe and its result are withheld. ⛔ Not a claim. The grade is triage's.

Reader (who acts): triage grades it. The domain:services lane fixes it in packages/plugins/plugin-auth/src/auth-plugin.ts.

The rule

Every call into AuthManager.handleRequest hands over the request's transport, as the main auth mount does: handleRequest(c.req.raw, transportOf(c)) at auth-plugin.ts:3747 on main 6b9e6df909. The client-address trust rule (ADR-0069 D5/D7, resolveClientAddress) establishes a request's address from the transport's peer when no trusted proxy is declared. Without the transport it cannot use the peer.

Where it does not hold

Seven call sites in auth-plugin.ts on main 6b9e6df909 hand over the request alone: :2996, :3296, :3373, :3496, :3524, :3542 and :3705. Re-check: git grep -n "handleRequest(" origin/main -- packages/plugins/plugin-auth/src/auth-plugin.ts lists eight sites, and only :3747 passes transportOf(c).

The finding predates PR #24871, and that PR does not widen it. PR #24871 makes the vendored auth library read the address the rule established, so after it lands every consumer of the established address depends on this rule holding at every door.

The fix, as the seat reads it

  • Pass the transport through each bridge (transportOf(c), or the Hono context's equivalent at that site).
  • Pin it per bridge family, with names that state the rule: "a request through BRIDGE is judged on its connection peer when no trusted proxy is declared". Ablation-verify each negative pin.

Dedupe

Method: two semantic search_issues queries on objectstack-ai/objectstack, open and closed, using the finding's own words ("bridge routes call handleRequest without transport peer…", "transportOf handleRequest remoteAddress re-dispatch…"). 5 hits, none on point. The nearest is #24799, the parent fix this follows. #23178, #23190 and #23287 are about the ADR-0069 authentication-policy gate on other route families. New.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage (R539): p1 · domain:services · area:identity · bug + security · pm:queue. · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-20T13:57Z


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 · 2026-10-20T14:06Z
    Session: session_01UmqzD97e14iws6hmVhgZ5y
    Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24877-bridge-transport
    Worktree: objectstack-issue-24877
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    File surface: packages/plugins/plugin-auth/src/auth-plugin.ts (the seven in-process bridges that call AuthManager.handleRequest, at :2996, :3296, :3373, :3496, :3524, :3542 and :3705 on 98c3d00447, and transportOf at :359; nothing else in the file; at patch round 1 on PR #24931 the seat added the eighth bridge POST /sys-session/revoke's mount in the same file), and, added at that patch round, the re-dispatch header builders of register-sso-provider.ts, send-verification-email.ts and sys-session-revoke.ts, and client-address.ts for the list of the forwarding headers the trust rule reads; at patch round 2 the seat added a new internal (unpublished) module holding that list and in-process-redispatch.ts for one shared copy function, pins beside it in plugin-auth, one new .changeset/24877-*.md. ⛔ Not auth-manager.ts (handleRequest and the trust rule landed in PR #24871 and are reused, not changed), ⛔ not packages/runtime/**, ⛔ not packages/spec/** (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates --tier on this surface: no path-derived mandate, floor sonnet, default opus, ceiling fable)
    Clause-②: no
    Responsibility: plugin-auth's in-process bridges hand the request to handleRequest without its transport | the main auth mount already passes transportOf(c) (auth-plugin.ts:3747), and the trust rule is resolveClientAddress (PR #24795, PR #24871) | every request through those bridges on a deployment that declares no trusted proxy; reach measured once at a public door (PR #24871's review; detail withheld)
    Thread-read: 6292583231
    Serial constraints cleared: no open PR touches auth-plugin.ts (all open PRs' files read this act). Seat 1's #24762 edits auth-manager.ts (customSession / computeAuthGate) and #24844 edits service-settings's auth.manifest.ts, neither of them this file. This seat's #24865 (dispatched alongside) touches plugin-auth's phone-sms-texts.ts only.

    Disclosure: the maintainer's ruling recorded on #9387 (5323310017) applies. ⛔ No reproduction, probe or consequence detail on this card, its PR or any comment.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24877,
    "status": "done",
    "branch": "claude/issue-24877-bridge-transport",
    "pr": "#24931",
    "session": "session_01UmqzD97e14iws6hmVhgZ5y — the parent's id (mode:subagent; this run's identity is the branch)",
    "premise_still_valid": true,
    "summary": "Every in-process bridge in auth-plugin.ts that re-dispatches through AuthManager.handleRequest now hands over the request's transport, as the catch-all does. The two delegate sites (remove-user, has-permission) pass transportOf(c) inline. The five helper sites (sso/register, sso/register-saml, request-domain-verification, verify-domain, send-verification-email) take their handler from one new helper, handleWithTransportOf(c), placed beside gateAdmin. M1 held at a7199ef: the same seven lines, and only :3747 passed the transport. The premise holds with one addition: an EIGHTH bridge, POST /sys-session/revoke. Its owner lane reaches handleRequest one hop away, inside sysSessionRevokeDepsFor (sys-session-revoke.ts:206), so the card's grep cannot list it. It is fixed in auth-plugin.ts only: the mount hands the factory a manager slice whose handleRequest is handleWithTransportOf(c), and sys-session-revoke.ts is untouched. M2 held: the Hono context c is in scope at all eight sites, and no site lacked a transport. M3 held: the pins reuse the D5/D7 real-mount fixture. transportOf, resolveClientAddress and handleRequest are reused unchanged, and auth-manager.ts, runtime and spec are not touched.",
    "tests": "New pin file packages/plugins/plugin-auth/src/bridge-redispatch-transport.pin.test.ts has 9 cases: a catch-all control plus one pin per bridge, each named for the rule ('a request through POST ROUTE is judged on its connection peer when no trusted proxy is declared'). Fixture: the real mount on Hono in front of a real AuthManager, the peer on c.env.incoming, no trustProxy, and the caller's forwarded headers plus the platform-owned header naming another address. Observation: the platform-owned client-address header on every request handed to the vendored library's handler, which goes through a plain instance wrapper, not vi.spyOn. The bridge's own inner route must be among those requests. Ablation ran at e5c4445, with the fix committed first and every leg through scripts/ablation-replace.mjs with a trap restore. Leg A, the helper drops the transport (anchor 1 hit, 1 -> 0): Tests 6 failed | 13 passed. The red six are sso/register, register-saml, request-domain-verification, verify-domain, send-verification-email and sys-session/revoke; remove-user, has-permission and the control stay green. Leg B, the two delegate sites drop transportOf(c) (anchor 2 hits, 2 -> 0): Tests 2 failed | 17 passed (remove-user, has-permission). Leg C, the revoke mount goes back to sysSessionRevokeDepsFor(this.authManager!, basePath): Tests 1 failed | 18 passed (sys-session/revoke). Each red reads 'expected [ null, ... ] to deeply equal [ 203.0.113.7, ... ]'. Each restore was proven: blob e1313b254752 == HEAD and git diff HEAD empty. The test imports ./auth-plugin from src, so no dist sits on the path. The 19-case count includes the 10 cases of the imported impersonation-bearer-rotation.test (house fixture). Final head 7d167c2 (origin/main 81e6edd merged; auth-plugin.ts and the pin file are byte-identical to e5c4445): pnpm --filter '@objectstack/plugin-auth...' build then pnpm --filter @objectstack/plugin-auth run typecheck gave VERDICT command-exit 0, with check:test-typecheck OK and the ledger unchanged at 10 files / 88 errors / 23 signatures. pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 gave Test Files 172 passed (172), Tests 3296 passed | 10 skipped (3306). The same suite was also green at 23b1608 before the merge.",
    "gates": "Local, at 7d167c2: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 72 commands. Each ran with its exit code captured before any pipe, and all 72 exited 0. --ran: '72 derived famil(ies) accounted for — 72 run, 0 NOT-MEASURED (a DERIVED zero)'. At 23b1608, check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET, no workspace dist). At 7d167c2 it measured exit 0 over 112 entries / 69 packages. Roster gates under touched directories: check-changeset-fixed, check:authz-resolver, check:error-code-casing and check:filter-alias-parity, all exit 0. eslint --no-inline-config on the 2 changed source files: exit 0. Control-byte self-scan on the 3 files: 0 hits. CI on the PR head 7d167c2, read once at report time: 24 check-runs, 21 in_progress, 2 skipped and 1 success. The required contexts Lint & Repo Gates, TypeScript Type Check, Test Core and Build Core had not been created yet, and Governed Surface Queue Guard was in_progress. Status: in_progress, not waited on. test:repo, repo-wide pnpm lint and the type-check lanes belong to CI.",
    "mcp_calls": "0",
    "api_writes": "2 — POST /repos/objectstack-ai/objectstack/dispatches (fleet relay stroke fw-20261020T160030Z-703b64, run 39453602112: POST /repos/objectstack-ai/objectstack/pulls creating draft #24931, then POST /repos//issues/24931/assignees [os-support-ai]); POST /repos/objectstack-ai/objectstack/dispatches (relay stroke carrying this report: POST /repos//issues/24877/comments). git push x5 (branch probe, fix, changeset, merge) is not REST.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: b · reach: exception: security (read in a unit harness on the real mount; no public-door probe made; reading kept in the dev's scratchpad) · Seam: spec:server.trustProxy (stack-server.zod.ts, ADR-0069 D5/D7) -> runtime: the re-dispatching helpers' header builders (register-sso-provider.ts forwardAuthHeaders and siblings, send-verification-email.ts, sys-session-revoke.ts revokeAsOwner) · evidence (abstract): behind a DECLARED trusted proxy, the six bridges whose helper builds a fresh re-dispatched request copy only the credential and origin headers onto it, not the incoming request's forwarding headers. The trust rule therefore reads that request's own facts, the connection peer, rather than the forwarded client address. The two delegate bridges and the catch-all are unaffected. Before this PR the same requests were judged on no address under either branch. The fix lands in the helper files (outside this card's landing) or in a handleRequest that accepts an already-established address (auth-manager.ts, also outside). Same family as #24877 · dedupe words: trustProxy forwarded headers re-dispatch helper, forwardAuthHeaders client address, in-process redispatch X-Forwarded-For bridge",
    "carrier: none (承接者:无) · noted, not filed · two IAuthService.handleRequest callers outside plugin-auth (the Hono adapter's auth mount, packages/adapters/hono/src/index.ts:1009, and the runtime dispatcher's auth domain, packages/runtime/src/domains/auth.ts:138) hand the request alone, and the spec contract IAuthService.handleRequest(request) (packages/spec/src/contracts/auth-service.ts:153) declares no parameter to carry a transport. Read-only inference: reach not measured, and both sit in landings this card excludes (runtime, spec)"
    ],
    "deviations": [
    "Eighth bridge (POST /sys-session/revoke, mount at auth-plugin.ts:3689 on a7199ef) fixed beyond the claim's seven declared lines, under the bounded in-place exemption: same defect class, mechanical fix in the pinned form, same file already held by this claim, same gate family. The PM owes the claim's file-surface amendment. The PR body names the fix with its own pin and ablation leg C.",
    "The pins observe the platform-owned client-address header on requests handed to the library's handler (the established address), not a downstream consumer of the address, under the disclosure ruling.",
    "One read of the open-PR list (GET /repos/objectstack-ai/objectstack/pulls?state=open), made while weighing the in-place exemption's file-ownership condition. It was a read, not used for any write, and should not have been needed: the fix stayed in the claimed file.",
    "Commit trailers use the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude). The harness reminder's model-bearing trailer and its PR footer form were not used. The PR footer is the AGENTS.md session-URL form.",
    "origin/main 81e6edd merged into the branch before opening the PR (AGENTS.md section 10). The build, typecheck, full plugin-auth suite and gate union were re-run on the merged head."
    ],
    "files_changed": [
    "packages/plugins/plugin-auth/src/auth-plugin.ts (+39 / -9)",
    "packages/plugins/plugin-auth/src/bridge-redispatch-transport.pin.test.ts (+283, new)",
    ".changeset/24877-bridge-transport.md (+12, new; '@objectstack/plugin-auth': patch, carries 'Clause-②: no')"
    ],
    "line_budget": "343 changed lines (+334 / -9) across 3 files vs the human-merge threshold 3000: under. No skills/** or governed surface touched. Production code +39 / -9 in one file."
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24877,
    "status": "done",
    "round": "patch round 1 (REWORK 6295342749 on PR #24931)",
    "branch": "claude/issue-24877-bridge-transport",
    "pr": "#24931",
    "head": "486e46d1b",
    "session": "session_01UmqzD97e14iws6hmVhgZ5y — the parent's id (mode:subagent; this run's identity is the branch)",
    "premise_still_valid": true,
    "summary": "The REWORK item is folded in. The six bridges whose helper rebuilds the request before re-dispatching it now carry the incoming request's forwarding headers onto it. The list comes from the rule's own module: client-address.ts exports FORWARDED_CLIENT_ADDRESS_HEADERS (x-forwarded-for, then x-real-ip), and resolveClientAddress now iterates that list instead of two literals, with identical decisions, so the list is what the rule reads by construction. Each helper module carries the headers with one module-local carryForwardedClientAddress(from, to) over the list, and no helper names a header. In register-sso-provider.ts the copy sits in forwardAuthHeaders and in the get-session read (resolveActiveOrganizationId). The OIDC and SAML register builders were byte-identical to forwardAuthHeaders and now call it. In send-verification-email.ts the copy covers the session read and the send. In sys-session-revoke.ts it covers the owner lane. With no trusted proxy declared, the rule ignores the copied headers, and the no-trust pins stay as before (they now also send forwarding headers naming another address through the copy). With trustProxy declared, every bridge's re-dispatched requests are judged on the forwarded client address. origin/main 74fcec1 was merged first. The second coordinator item is fixed in the test file itself: the two TS2345 errors CI reported on 3761b50. The ledger is untouched.",
    "typecheck_fix": "check:test-typecheck on 3761b50: 'src/bridge-redispatch-transport.pin.test.ts: 2 type error(s) in a file the ledger does not cover'. Both were TS2345 at :320 and :331. PROXY_FORWARDING was inferred as a union of two object literals, { 'x-forwarded-for': string; 'x-real-ip'?: undefined } | { 'x-real-ip': string; 'x-forwarded-for'?: undefined }, and was passed where through() takes a record of strings. Fix (486e46d, test file only): PROXY_FORWARDING and CLAIMED_FORWARDING are annotated as Record-of-string header maps. test-typecheck-debt.json is not touched. pnpm --filter @objectstack/plugin-auth run typecheck: exit 0, 'check:test-typecheck: OK' with the ledger unchanged at 10 files / 88 errors / 23 signatures. CI 'Type Check · workspace' on 486e46d: success.",
    "tests": "The pin file now has 18 cases: the no-trust control plus 8 bridges, and the trusted-proxy control plus 8 bridges. The trust pins send X-Forwarded-For (client, proxy chain) and, separately, X-Real-IP from the proxy-hop peer. Names: 'a request through POST ROUTE is judged on the forwarded client address when a trusted proxy is declared'. Ablation at 486e46d, six legs, each through scripts/ablation-replace.mjs with a trap restore. Every restore read 'ok restored: blob == HEAD' with the tree clean. A: handleWithTransportOf drops the transport: Tests 6 failed | 22 passed (28), the six helper bridges' no-trust pins. B: the 2 delegate sites drop transportOf(c): 2 failed | 26 passed (remove-user, has-permission no-trust). C: the revoke mount goes back to sysSessionRevokeDepsFor(this.authManager!, basePath): 1 failed | 27 passed (sys-session/revoke no-trust). D: register-sso-provider.ts copy made a no-op: 4 failed | 24 passed (sso/register, register-saml, request-domain-verification, verify-domain trust pins). E: send-verification-email.ts copy made a no-op: 1 failed | 27 passed (send-verification-email trust pin). F: sys-session-revoke.ts copy made a no-op: 1 failed | 27 passed (sys-session/revoke trust pin). Both controls and every other pin stayed green in every leg. D, E and F were first run at 3761b50 with the same reds. The test imports from src, so no dist sits on the path. Suite at 486e46d: pnpm --filter '@objectstack/plugin-auth...' build && pnpm --filter @objectstack/plugin-auth run typecheck && pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 gave VERDICT command-exit 0, Test Files 172 passed (172) and Tests 3309 passed | 10 skipped (3319).",
    "gates": "Local, at 486e46d: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 72 commands, each run with its exit code captured before any pipe. 71 exited 0 on the first pass. check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET, no workspace dist); after a later gate of the same pass had built the workspace it re-ran and exited 0 ('112 published require entry point(s) across 69 package(s) load'), and the ran list carries the rerun's code. --ran: '72 derived famil(ies) accounted for — 72 run, 0 NOT-MEASURED (a DERIVED zero)'. Roster gates under touched directories: check-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity and check:published-readme-exports (the client-address subpath gained an export), all exit 0. eslint --no-inline-config on the 6 changed source files: exit 0. Control-byte self-scan on the 7 files: 0 hits. CI on PR head 486e46d, read once: 41 check-runs, 34 success, 7 skipped, 0 failing. That includes Lint & Repo Gates, TypeScript Type Check (Type Check · workspace / consumer gates / source gates / debt ledger), Test Core and Test Core (1/6 to 6/6), Build Core and Governed Surface Queue Guard, all success.",
    "mcp_calls": "0",
    "api_writes": "1 — POST /repos/objectstack-ai/objectstack/dispatches (fleet relay stroke carrying this report: POST /repos//issues/24877/comments). git push x3 this round (merge of origin/main, the trusted-proxy fix, the type fix) is not REST. The PR body was NOT patched: see deviations.",
    "pr_body_replacement": "Ready for the seat to write as the PR body: /tmp/claude-0/-home-user/baba1a33-5813-5ac5-906a-64b704dc6695/scratchpad/issue-24877/r1/pr-body-r1.md (9683 bytes, first line 'Fixes #24877', 'Clause-②: no' at line start, session-URL footer, no angle-bracket fragments, no probe or consequence detail). It describes the transport and the forwarding-header fix at the rule level and carries ablation legs A to F and this head's readings. The body stored on PR #24931 is still the round-0 text, which describes only the transport half.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · noted, not filed · unchanged from round 0 and kept as an Acceptance note per the seat: IAuthService.handleRequest(request) in the spec contract has no transport parameter, and two callers outside plugin-auth hand the request alone. Reach unmeasured, and both sit in excluded landings."
    ],
    "deviations": [
    "PR body not updated by the dev, although this round's write budget lists it. The os-dev agent contract says the dev writes the PR body once, at creation, never PATCHes it, and names the change for the seat to write. Where the dispatch and that contract conflict, the contract governs. The replacement body is in pr_body_replacement.",
    "client-address.ts: besides the exported list, resolveClientAddress now iterates FORWARDED_CLIENT_ADDRESS_HEADERS instead of the two literals 'x-forwarded-for' / 'x-real-ip'. Its decisions are identical: the same order, the same first-entry read, the same fall-through to the peer. Without this the export would be a second copy of the names the rule reads. The round-0 dispatch said not to change resolveClientAddress, while this round's REWORK asks for the list to be the headers it reads. Flagged rather than chosen silently.",
    "Clause-② reading: FORWARDED_CLIENT_ADDRESS_HEADERS is a new export of the published @objectstack/plugin-auth/client-address subpath, which is literally a public-surface addition. It is kept at 'Clause-②: no' and patch on the precedent of the subpath's own introduction (PR #24795: the whole ./client-address entry, Clause-②: no, patch). The seat confirms or flips; a flip makes the changeset minor and changes the claim's line.",
    "The forwarding-header copy is three module-local five-line functions, not one shared helper. Two of the modules are re-exported from the package root (export * in index.ts), so exporting a shared copy from either would add package-root API. The natural shared home, in-process-redispatch.ts, is outside the claim's surface.",
    "register-sso-provider.ts: the OIDC and SAML register builders, which were byte-identical to forwardAuthHeaders, now call it. These are the 'callers' the widened surface names. The session-read builder (resolveActiveOrganizationId) also carries the headers, and so does send-verification-email.ts's session read, because the pins require every request a bridge hands the library to be judged on the same facts.",
    "An earlier round-0 deviation stands as recorded: one read of the open-PR list."
    ],
    "files_changed": [
    "packages/plugins/plugin-auth/src/auth-plugin.ts (+39 / -9; unchanged this round)",
    "packages/plugins/plugin-auth/src/bridge-redispatch-transport.pin.test.ts (+339, new)",
    "packages/plugins/plugin-auth/src/client-address.ts (+18 / -3)",
    "packages/plugins/plugin-auth/src/register-sso-provider.ts (+26 / -12)",
    "packages/plugins/plugin-auth/src/send-verification-email.ts (+18 / -0)",
    "packages/plugins/plugin-auth/src/sys-session-revoke.ts (+20 / -3)",
    ".changeset/24877-bridge-transport.md (+13, new; '@objectstack/plugin-auth': patch, 'Clause-②: no'; text now names the forwarding-header carry and the new subpath export)"
    ],
    "line_budget": "500 changed lines (+473 / -27) across 7 files vs merge base 74fcec1, against the human-merge threshold 3000: under. Production code +121 / -27 across 5 files. No skills/** or governed surface touched."
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24877,
    "status": "done",
    "round": "patch round 2 (REWORK 6296974750 on PR #24931)",
    "branch": "claude/issue-24877-bridge-transport",
    "pr": "#24931",
    "head": "fbd92ad31",
    "session": "session_01UmqzD97e14iws6hmVhgZ5y — the parent's id (mode:subagent; this run's identity is the branch)",
    "premise_still_valid": true,
    "summary": "No new published export. FORWARDED_CLIENT_ADDRESS_HEADERS and the one shared carryForwardedClientAddress(from, to) now live beside each other in a new internal module, packages/plugins/plugin-auth/src/client-address-forwarding.ts. It is import-free, is not on the package exports map, and is re-exported from neither index.ts nor client-address.ts. client-address.ts imports the list for resolveClientAddress, whose decisions are unchanged, and no longer exports it; its header's import-free note now names this one internal exception. The three module-local copies are gone. register-sso-provider.ts calls the shared function twice (forwardAuthHeaders and the session read), send-verification-email.ts twice (the session read and the send), and sys-session-revoke.ts once (the owner lane). in-process-redispatch.ts is untouched: the copy sits beside the list, which the REWORK allows, and that module's header states that it never touches the request's headers. The changeset no longer mentions a subpath export and stays patch with 'Clause-②: no'. The built dist/client-address.js and dist/client-address.mjs export resolveClientAddress alone and require nothing; dist/index.js exports neither name. The pins are unchanged (the same blob as 486e46d). origin/main was not merged: no commit since 74fcec1 touches plugin-auth or the lockfile.",
    "tests": "Ablation at fbd92ad, each leg through scripts/ablation-replace.mjs with a trap restore. Every restore read 'ok restored: blob == HEAD', the tree was clean and blob == HEAD. D: register-sso-provider.ts stops calling the shared copy (anchor ' carryForwardedClientAddress(' x2 -> 'void ('): Tests 4 failed | 24 passed (28), the trusted-proxy pins of sso/register, sso/register-saml, request-domain-verification and verify-domain. E: send-verification-email.ts, the same anchor x2: 1 failed | 27 passed (the send-verification-email trusted-proxy pin). F: sys-session-revoke.ts, the same anchor x1: 1 failed | 27 passed (the sys-session/revoke trusted-proxy pin). G, an extra leg: the shared function copies nothing: 6 failed | 22 passed (the six helper bridges' trusted-proxy pins). Both controls and every no-trust pin stayed green in all four legs. Legs A to C (transport) stand from 486e46d: auth-plugin.ts (blob e1313b254752) and the pin file (blob bc0859e97e2e) are byte-identical at fbd92ad. At fbd92ad: pnpm --filter '@objectstack/plugin-auth...' build && pnpm --filter @objectstack/plugin-auth run typecheck && the pin file: VERDICT command-exit 0, check:test-typecheck OK with the ledger unchanged at 10 / 88 / 23, Tests 28 passed (28). The full suite, pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2: Test Files 172 passed (172), Tests 3309 passed | 10 skipped (3319).",
    "gates": "Local, at fbd92ad: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 72 commands, each run with its exit code captured before any pipe. 71 exited 0 on the first pass. check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: no workspace dist in the fresh worktree); after a later gate of the same pass had built the workspace it re-ran and exited 0, and the ran list carries the rerun's code. --ran: '72 derived famil(ies) accounted for — 72 run, 0 NOT-MEASURED (a DERIVED zero)'. pnpm check:published-readme-exports: exit 0 ('105 published document(s) across 84 workspace package(s)', 0 known instances in its baseline, no client-address or plugin-auth line). Roster gates check-changeset-fixed, check:authz-resolver, check:error-code-casing and check:filter-alias-parity: exit 0. eslint --no-inline-config on the 7 changed source files: exit 0. Control-byte scan of the 8 files: 0 hits. CI on fbd92ad, read once: 41 check-runs, 34 success, 7 skipped, 0 failing. That includes Lint & Repo Gates, TypeScript Type Check, Type Check · workspace, Test Core, Build Core and Governed Surface Queue Guard, all success.",
    "mcp_calls": "0",
    "api_writes": "1 — POST /repos/objectstack-ai/objectstack/dispatches (fleet relay stroke carrying this report: POST /repos//issues/24877/comments). git push x1 this round (fbd92ad) is not REST. PR body not written; see pr_body_replacement.",
    "pr_body_replacement": "/tmp/claude-0/-home-user/baba1a33-5813-5ac5-906a-64b704dc6695/scratchpad/issue-24877/r2/pr-body.md (10192 bytes; first line 'Fixes #24877', 'Clause-②: no' at line start, session-URL footer, no angle-bracket fragments, no probe or consequence detail). It describes the internal module and the shared copy, ablation legs A to G, and the fbd92ad readings. The seat writes it at ACCEPT.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · noted, not filed · unchanged and kept as an Acceptance note per the seat: IAuthService.handleRequest(request) in the spec contract has no transport parameter, and two callers outside plugin-auth hand the request alone. Reach unmeasured, and both sit in excluded landings."
    ],
    "deviations": [
    "The shared copy sits beside the list in client-address-forwarding.ts, not in in-process-redispatch.ts. The REWORK offers either. in-process-redispatch.ts's header states that it never touches the request's headers, and the claim names that file for the shared function only, so it is left untouched.",
    "client-address.ts gains its first import (the internal, import-free list). Its header note 'Keep this module free of imports' now names that one exception. The built entry still requires nothing: the list is inlined.",
    "The PM's 'before pushing, run typecheck and the suite' was met for the content: fbd92ad was pushed as the WIP commit before the minute-long build and test steps (AGENTS.md: push before every step that takes minutes). Typecheck, the suite, the ablations and the gates all ran on that same head afterwards, all green, and nothing was pushed after them.",
    "Extra ablation leg G (the shared function made a no-op) beyond the requested D, E and F."
    ],
    "files_changed": [
    "packages/plugins/plugin-auth/src/client-address-forwarding.ts (+42, new, internal: the list and carryForwardedClientAddress)",
    "packages/plugins/plugin-auth/src/client-address.ts (+15 / -4 vs merge base: imports the list; resolveClientAddress iterates it; no new export)",
    "packages/plugins/plugin-auth/src/register-sso-provider.ts (+10 / -12)",
    "packages/plugins/plugin-auth/src/send-verification-email.ts (+3 / -0)",
    "packages/plugins/plugin-auth/src/sys-session-revoke.ts (+5 / -3)",
    "packages/plugins/plugin-auth/src/auth-plugin.ts (+39 / -9; unchanged since round 0)",
    "packages/plugins/plugin-auth/src/bridge-redispatch-transport.pin.test.ts (+339, new; unchanged since round 1)",
    ".changeset/24877-bridge-transport.md (+12, new; patch, 'Clause-②: no'; the subpath-export line is gone)"
    ],
    "line_budget": "493 changed lines (+465 / -28) across 8 files vs merge base 74fcec1, against the human-merge threshold 3000: under. Production code +114 / -28 across 6 files. No skills/** or governed surface touched."
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24931 comment 6297994210

    domain:services seat 2 · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T18:20Z · head fbd92ad310, all checks green or expected skips; flipped to ready with auto-merge on.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 2 · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-20T18:58Z

    Reading Value
    PR #24931 timeline added_to_merge_queue 2026-10-20T18:23Z → merged 2026-10-20T18:47Z
    origin/main merge commit 2941a1929d is an ancestor (git merge-base --is-ancestor, exit 0)
    By content on origin/main client-address-forwarding.ts carries carryForwardedClientAddress (1 hit, the definition)
    This card closed completed by the PR's Fixes #24877; pm:dispatched removed in this act

    ACCEPT 6297994210 is on the PR. The spec contract's IAuthService.handleRequest(request) having no transport parameter stays an Acceptance note, carrier none. #24773's second half, which edits the same file, is no longer serial behind this card.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:identityLogin and identity — sign-up, sessions, organization membership, SSObugSomething isn't workingdomain:servicespriority:p1High: required for production / M2security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions