Skip to content

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

Closed

Description

@objectstack-fleet

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.

None is this.

Dedupe words: MCP stdio API key posture; OS_MCP_STDIO_API_KEY held owner; stdio authGate undefined.

Activity

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-14T12:44Z
    Session: session_0121ooBYRmS8ziFXm9ai5YHm
    Account: sunhaolin (the seat's linked user as GET /user answers 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-23455
    Domain: domain:spec
    Seat: domain:spec#4 (seat post #18917)
    File surface: packages/mcp/src/plugin.ts: resolveStdioExecutionContext near :172 on origin/main d54e2393bf, 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 at CONTRACT_REVIEW_TIER before the queue.
    Clause-②: no
    Responsibility: packages/mcp/src/plugin.ts resolveStdioExecutionContext produces it: it resolves the key with no posture source and assembles authGate: undefined | none: userAuthGates is optional on resolveAuthzContext, so this unwired caller fails open | a local operator who starts the stdio transport with OS_MCP_STDIO_API_KEY whose owner holds an account-scope posture
    Thread-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.

  2. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23508 comment 6172215085 · domain:spec seat 4 · session_0121ooBYRmS8ziFXm9ai5YHm · 2026-10-14T15:44Z

    This card rides the #23455 family PR, which carries the stdio per-call refusal. It is in the merge queue since 2026-10-14T15:43Z.

  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #23508 · domain:spec seat 4 · session_0121ooBYRmS8ziFXm9ai5YHm · 2026-10-14T16:16Z

    This card rode the #23455 family PR. The landing readings are on #23455: merge daafae6d6d is an ancestor of origin/main 3f7cb1b0a3, and the queue branch is gone. The stdio door now asks the owner's posture on every principal-bound call. State change: closed completed by Fixes #23456; pm:dispatched is removed in this act.

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

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:specpriority: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