Skip to content

test(authz): register MCP execution-surface identity admission in the ADR-0096 matrix (#3167)#3202

Merged
os-zhuang merged 1 commit into
mainfrom
claude/adr-0096-mcp-admission-row
Jul 18, 2026
Merged

test(authz): register MCP execution-surface identity admission in the ADR-0096 matrix (#3167)#3202
os-zhuang merged 1 commit into
mainfrom
claude/adr-0096-mcp-admission-row

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

#3167PR-A(记账 + 钉子,非默认接线)。给 ADR-0096 的鉴权准入矩阵补上 MCP 这个执行面的身份姿态——并在审计代码后纠正了 #3167 的一个错误前提

审计结论:HTTP 面早已准入,真缺口在 stdio

矩阵(ADR-0056 D10 / ADR-0096 D4)用 CI 绊线钉住每个执行面的身份姿态,但 MCP 这个活的工具执行面此前没有自己的行(只在 readonly-strip 行里被顺带提及)。逐路径读代码后,两条传输的姿态确实不同:

传输 身份姿态 证据
HTTP /api/v1/mcp 已准入(enforced) handleMcp 拒匿名(401)、OAuth scope 裁剪工具族(#2698)、buildMcpBridge(context) 把 caller EC 线进每个 callData(..., ec);handleHttpRequest 每请求 new McpServer + principal-bound bridge → RLS/FLS/租户与 REST /data 完全一致
stdio(autoStart / OS_MCP_SERVER_ENABLED=true 才起) 无 scope(experimental) MCPServerPlugin.start() metadata 服务 + data engine 桥到长驻 server(bridgeResources(metadataService, dataEngine)),无 per-request principal → stdio 客户端无 scope 读取

这直接推翻 #3167 决策 1 的"身份准入缺失、fall-open = 满权限"表述:HTTP 面根本没 fall-open,它拒匿名并线了身份;只是从没被矩阵钉住。真正的缺口是 opt-in 的 stdio(裸权限),而它本就非默认(shouldStart 门)。

变更:两行 + 三个探针钉子

mcp-http-identity(enforced) —— covers handleMcp(新增同类 MCP 数据 handler → UNCLASSIFIED 绊线)+ buildMcpBridge(context)(丢线程 → STALE → 红 CI)。未标 high-risk:不是因为它 latent(它是活路由),而是它的端到端 dogfood 证明(匿名→401、principal→RLS 裁剪结果)留给 dev 接线跟进 PR-B——行里注明了。

mcp-stdio-authority(experimental) —— covers bridgeResources 裸桥;note 写清姿态(仅单操作者本地安全:能 attach stdio 的人本就拥有进程+dev 库)、与 HTTP 路径的对比、以及准入要求:stdio 被提升为默认开 / 多用户 / 托管场景前,必须给长驻 bridge 线一个 principal。

三个探针来自真实源码匹配;bite 测试 (f)(g) 证明两个新钉子确实触发。基线保持 sound:3 个新 key 恰好被 2 个新行覆盖。

范围与验证

  • 纯测试改动,限于私有 @objectstack/dogfood 门包 → 无 changeset;不动任何运行时代码,不接线 os dev(那是 PR-B)。
  • authz-conformance.test.ts 本地 9/9 绿(基线 sound + 原有 5 bites + 新 (f)(g));eslint 干净;@objectstack/verify(checkLedger)构建绿。

Refs #3167(PR-A;PR-B = os dev 接线 + off-switch 统一 + 连接指引 + dogfood 证明,待方向拍板)、ADR-0096、ADR-0056 D10、#2698

🤖 Generated with Claude Code

https://claude.ai/code/session_01Pbmw3pMqNJfPbkvwh9Fkjs


Generated by Claude Code

… ADR-0096 matrix (#3167)

The authz conformance matrix (ADR-0056 D10 / ADR-0096 D4) pins every execution
surface's identity posture with a CI ratchet, but the MCP server — a live tool-
execution surface — had no row of its own (only an incidental mention in the
readonly-strip row). This adds two rows + three probe pins, one per transport,
after auditing the actual code:

- mcp-http-identity (ENFORCED): /api/v1/mcp already admits the caller —
  handleMcp denies anonymous (401), OAuth-scope-gates the tool families
  (#2698), and buildMcpBridge(context) threads the caller ExecutionContext so
  handleHttpRequest's per-request McpServer runs every tool under RLS/FLS/tenant
  exactly like REST /data. Pinned by probes on  (UNCLASSIFIED
  tripwire for a new sibling MCP data handler) and
  (STALE if the threading is dropped). This CORRECTS #3167's premise that the
  HTTP admission was missing — it is wired; it was just unpinned. Its e2e
  dogfood proof is deferred to the dev-wiring follow-up (PR-B), so it is not
  marked high-risk yet — noted on the row.

- mcp-stdio-authority (EXPERIMENTAL): the opt-in stdio transport
  (autoStart / OS_MCP_SERVER_ENABLED=true) bridges the RAW metadata service +
  data engine onto the long-lived server with NO per-request principal, so a
  stdio client reads unscoped (no RLS/FLS/tenant). Safe only as a single-
  operator local tool (the operator already owns the process + dev DB), and
  deliberately not default. This is the REAL admission gap. The row states the
  requirement before stdio is ever promoted to default-on / multi-user: thread
  a principal into the long-lived bridge. Pinned by a probe on the raw-service
  bridging (STALE forces re-classification when it changes).

Bite tests (f)(g) prove both new pins fire. Baseline stays sound: the three new
probe keys are covered exactly by the two new rows. Test-only change in the
private @objectstack/dogfood gate — no changeset.

Refs #3167, ADR-0096, ADR-0056 D10, #2698.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pbmw3pMqNJfPbkvwh9Fkjs
@vercel

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 18, 2026 9:24am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): packages/qa.

2 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/permissions/authorization.mdx (via packages/qa)
  • content/docs/permissions/delegated-administration.mdx (via packages/qa)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 18, 2026 09:24
@os-zhuang
os-zhuang merged commit 8a39022 into main Jul 18, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/adr-0096-mcp-admission-row branch July 18, 2026 09:24
os-zhuang added a commit that referenced this pull request Jul 18, 2026
…rade the matrix row to high-risk (#3167) (#3225)

showcase-mcp-http-identity.dogfood.test.ts boots the real showcase + security + MCPServerPlugin and drives POST /api/v1/mcp: anonymous tools/call is 401 before any tool runs, and a member's query_records over the owner-private showcase_private_note returns only their own rows (cross-owner would leak if it ran unscoped — the stdio posture). The mcp-http-identity matrix row is upgraded to HIGH_RISK with this proof, closing the deferral from #3202. Also documents that serve/dev already auto-load MCPServerPlugin on the unified isMcpServerEnabled switch (decisions A/B/C were already shipped). Test-only in the private dogfood gate.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants