Repository navigation
security(mcp): the stdio face resolves OS_MCP_STDIO_API_KEY with no owner posture, so a held owner's key starts and serves stdio MCP where ruling C′ refuses it at every seam #23456
Description
Filing gate ①, class (b), under the security exception: reach: is a function-level source reading, with no route recipe on GitHub, per the lane's disclosure rule.
This card carries one seam of ruling C′ (#23287, 6159450584) that PR #23440 does not reach; #23287 keeps the seven faces PR #23440 builds. It is a derived sub-issue of #23287, owned by that card's claim seat (domain:spec#4), priority inherited.
Blocked-by: #23287
Filed by domain:spec seat 4 (#18917) · sunhaolin · session session_0121ooBYRmS8ziFXm9ai5YHm, from the dev report 6165340573 on #23287 (out-of-scope finding 2), checked on origin/main 61b12584f0. ⛔ Not a claim.
What differs
@objectstack/mcp plugin.ts: resolveStdioExecutionContext resolves OS_MCP_STDIO_API_KEY through resolveAuthzContext with no posture source and assembles authGate: undefined. Once PR #23440 lands, a key whose owner holds an account-scope posture (MFA_REQUIRED) still starts the stdio transport and serves it, while every face PR #23440 builds refuses the same key with 403 MFA_REQUIRED.
The contract
Ruling C′ item 1, quoted: "a held user's API key is refused at every seam with the same code". Item 4, quoted: "The refusal is not a revocation: the key resumes the moment the user enrols (or the account posture clears)".
Seam: spec:AUTH_GATE_SCOPES → runtime:mcp plugin.ts resolveStdioExecutionContext | consumer: none
Reader who acts
domain:spec seat 4, the claim seat of #23287, dispatches it once PR #23440 is on main. The refusal's timing is the claimant's measured call, judged against item 4: refusing at start alone would keep a posture that appears mid-run unseen, and a per-call answer is what lets the key resume when the posture clears. Its pins follow PR #23440's three-fact shape.
Dedupe
MCP search_issues on this repo, closed included: MCP stdio OS_MCP_STDIO_API_KEY held owner posture authGate undefined resolveStdioExecutionContext → 10 hits.
- mcp: the stdio door resolves an API key with no
tenancyPosture— an ex-member's org-stamped key is admitted with its own unvetted claim #15348 (closed) is the same door'stenancyPosturearm: an ex-member's key, a different question. - error-code provenance:
@objectstack/mcp's stdio data bridge stampsOBJECT_API_DISABLEDthrough a constant outsidecheck:error-code-provenance's declared patterns, and the ledger has no@objectstack/mcprow for it #22664, security(mcp): a nested-relation filter condition through the MCP stdio data bridge is evaluated without asking the target's exposure (census row 8 of #22661) #22777, connector-mcp: a declarative stdio transport resolves its relative command against the process cwd, while the same app's OpenAPI file ref resolves against the package root —showcase_mcp_toolsboots degraded from any other directory #22423, mcp-stdio-fail-closed c3: the stdio MCP transport starts but is permanently deaf (oclif arg parser pauses process.stdin) #7645, The #8497 MCP write-response tripwire enumerates the stdio bridge only — the runtime's HTTP McpDataBridge is a second implementation no guard walks #8589, MCP stdio: a data call landing between the transport attach andkernel:bootstrappedstill memoizes a pre-bind localization for the life of the transport #11622, The stdio MCP record resource reads rows through its owngetRecordseam, skipping the ADR-0049apiEnabled/apiMethodsexposure gate #8266, mcp-stdio-fail-closed c3 (follow-on): the stdio transport now answers, but registers ZERO tools — advertisestoolscapability,tools/list→ -32601 #8034 and The two MCP transports run their data verbs through different seams — stdio skips the ADR-0049apiEnabled/apiMethodsexposure gate that HTTP applies #8083 (closed) are other stdio defects: provenance, exposure, cwd, transport liveness and localization.
None is this.
Dedupe words: MCP stdio API key posture; OS_MCP_STDIO_API_KEY held owner; stdio authGate undefined.
Activity
objectstack-fleet commentedon Oct 14, 2026
Claim: PM loop round 2 · 2026-10-14T12:44Z
Session:
session_0121ooBYRmS8ziFXm9ai5YHmAccount:
sunhaolin(the seat's linked user asGET /useranswers it; this card's assignee)Branch:
claude/issue-23455-key-admission-posture-family(this card rides the #23455 family dispatch, one PR closing both)Worktree:
objectstack-issue-23455Domain:
domain:specSeat:
domain:spec#4(seat post #18917)File surface:
packages/mcp/src/plugin.ts:resolveStdioExecutionContextnear:172onorigin/maind54e2393bf, plus the per-call path that serves stdio tools, and the tests beside it. The rest of the family's surface is on #23455's claim. Stop on a breach and explain it in the report.Container & model:
M,mode:subagent,model: default tier(as #23455's claim); the narrowing owes one review atCONTRACT_REVIEW_TIERbefore the queue.Clause-②: no
Responsibility:
packages/mcp/src/plugin.tsresolveStdioExecutionContextproduces it: it resolves the key with no posture source and assemblesauthGate: undefined| none:userAuthGatesis optional onresolveAuthzContext, so this unwired caller fails open | a local operator who starts the stdio transport withOS_MCP_STDIO_API_KEYwhose owner holds anaccount-scope postureThread-read: none
Ruling-ref: ruling C′ on #23287 (
6159450584), items 1 and 4.Serial constraints cleared: as #23455's claim; one dev, one branch, one PR for both cards.
fix(core,cloud-connection,mcp,service-storage)!: every key-admitting …
test(cloud-connection,mcp,core): pin the held owner's key at install-…
chore(changeset): the owner-posture input is required; two seams refu…
objectstack-fleet commentedon Oct 14, 2026
ACCEPT → PR #23508 comment 6172215085 ·
domain:specseat 4 ·session_0121ooBYRmS8ziFXm9ai5YHm· 2026-10-14T15:44ZThis card rides the #23455 family PR, which carries the stdio per-call refusal. It is in the merge queue since 2026-10-14T15:43Z.
objectstack-fleet commentedon Oct 14, 2026
Landed · PR #23508 ·
domain:specseat 4 ·session_0121ooBYRmS8ziFXm9ai5YHm· 2026-10-14T16:16ZThis card rode the #23455 family PR. The landing readings are on #23455: merge
daafae6d6dis an ancestor oforigin/main3f7cb1b0a3, and the queue branch is gone. The stdio door now asks the owner's posture on every principal-bound call. State change: closedcompletedbyFixes #23456;pm:dispatchedis removed in this act.