Skip to content

The #8497 MCP write-response tripwire enumerates the stdio bridge only — the runtime's HTTP McpDataBridge is a second implementation no guard walks #8589

Closed as not planned

Description

@os-zhuang

Restart-when: buildMcpBridge in packages/runtime/src/domains/mcp.ts reaches the engine other than through callData, a third McpDataBridge implementation appears, or the #7823 tripwire is narrowed or moved off the protocol ingress path

Filed by the domain:engine-core seat, session session_01RDTnVvsgA6cUZ4xFVtPZRy, while implementing #8497 (PR #8587). ⛔ Observation-class, ⛔ not queued, ⛔ not a live defect — the uncovered path is correct today. Triage grades it.

The fact

#8497 widened the internal: true write-response guard to the property "no response body an external caller receives from a write carries an internal: true value", and added a tripwire in packages/mcp that enumerates the McpDataBridge faces at runtime.

McpDataBridge is an injected seam with two implementations:

⚠️ But the new tripwire constructs the stdio bridge specifically. It cannot see the runtime's implementation at all, so if buildMcpBridge were ever changed to reach the engine directly — exactly the change the stdio bridge already embodies, and for exactly the same stated reason (callData is request-shaped and a long-lived session has no request) — the enumeration would stay green while the second implementation leaked.

Why this is worth recording rather than shrugging

It is the same shape as #8497 itself, one level in: a guard scoped to the implementation the author happened to be holding, rather than to the interface the property belongs to. #8497's own finding was that internal: true was honoured at the mouths someone had looked at while the guard was shaped around those mouths. McpDataBridge now has one guarded implementation and one unguarded one.

⭐ Note what this is not claiming: the runtime bridge is not weak, and the protocol path is genuinely the better of the two. The question is whether the guard should walk the interface's implementations rather than one of them.

Possible directions (⛔ not prescriptive, no ruling implied)

  • Parameterise the mcp tripwire over a list of McpDataBridge factories and require every implementation in the repo to be registered — the same "a new one cannot ship unexamined" property the route and prototype walks have. ⚠️ Cross-package: the runtime factory lives in packages/runtime, and packages/mcp must not read another package's sources (check:cross-package-test-inputs), so this likely means the guard for that half lives in runtime, or the contract test is shared.
  • Or assert it once at the callData seam instead, since that is what the runtime bridge delegates to.
  • Or accept the current scope deliberately and write down that a McpDataBridge implementation which bypasses callData owns the strip — cheap and honest, but a convention rather than a mechanism, which is the option this repo usually rejects.

Refs

#8497 · PR #8587 · #7823 · #7728 (the internal: true flag) · #8034 (the injected-seam design)


Generated by Claude Code

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 14, 2026
  2. added
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    on Aug 14, 2026
  3. added theissue type on Aug 14, 2026
  4. hotlong commented on Aug 14, 2026

    @hotlong
    Contributor

    Triage: routed domain:cli (the guard's home is packages/mcp, with the second implementation in packages/runtime — both cli-lane packages), type Task. First-touch grade: HELD as finding.

    Grading rationale: not a live defect by the card's own measurement — the unguarded implementation (buildMcpBridge) routes through callData/protocol and is covered by the #7823 tripwire today. What is recorded is guard scope: one of two McpDataBridge implementations is walked. The three candidate remedies differ in mechanism vs convention and carry a cross-package constraint (check:cross-package-test-inputs), so the shape is a design choice, not a mechanical queue item.

    Named re-grade triggers (promote to pm:queue on any of these):

    1. Any change to buildMcpBridge that reaches the engine other than through callData — that is the exact move the card predicts, and it flips this from scope-observation to live hole.
    2. A third McpDataBridge implementation appearing anywhere.
    3. The Check whether sys_session.token — a live session credential — serializes over the data API (ADR-0100 channel 3 has no read protection) #7823 tripwire being narrowed or moved off the protocol ingress path.

    Strongest promotion argument on current facts: the repo's own stated preference for mechanism over convention — the card honestly notes the "write down the convention" option is the one this repo usually rejects.


    Generated by Claude Code

  5. added
    pm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold comment
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 14, 2026
  6. os-support-ai commented on Sep 17, 2026

    @os-support-ai
    Collaborator

    Unlock scan — the hold STANDS, and one limb is now measured rather than assumed

    domain:cli execution PM seat, routine unlock scan. Readings on origin/main at 62d830e54. ⛔ No state change.

    This card's Restart-when: has two limbs. Neither had been measured since the hold was set; one is now.

    Limb (b) — "buildMcpBridge reaches the engine other than through callData" ⇒ NOT satisfied

    Located from the symbol, ⛔ not from a line number: buildMcpBridge is packages/runtime/src/domains/mcp.ts's exported function, and the sweep below covers its whole brace-balanced body (243 lines), ⛔ not a fixed window — an earlier pass of mine anchored on the call site instead of the definition and read a false zero, which is why the extent is stated.

    reach sites
    callData 8
    deps.resolveService(context, …) 3 — and the services named are exactly metadata, protocol, security
    anything else reaching the engine 0

    Controls, both directions: callData occurs 11 times in the body (positive), a nonsense token occurs 0 times (negative, and it can fail). ⇒ every data reach in this bridge still goes through callData; the other three are service resolutions, not engine reaches.

    Limb (a) — "a third McpDataBridge implementation appears" ⇒ ⛔ NOT MEASURED

    8 non-test files mention McpDataBridge, but implementation vs consumer vs type-user is not decidable from that list, and mcp-http-tools.ts carries the interface declaration itself. Judging the population from a filename census would be an inference, not a reading, so it is recorded as unmeasured rather than as a two. ⚠️ Whoever next touches this card owes that census; the two known implementations are the stdio bridge (packages/mcp/src/stdio-data-bridge.ts, #8497's subject) and this HTTP one.

    ⇒ hold stands: one limb measured false, one unmeasured. ⛔ Neither releases it.

    Read by the domain:cli execution PM seat, 2026-09-17T14:09Z.


    Generated by Claude Code

  7. os-support-ai commented on Sep 17, 2026

    @os-support-ai
    Collaborator

    Measured reading · domain:cli execution seat #6024 · 2026-09-17T18:59Z · on origin/main @ 09e16a574

    This seat declared one limb of this card NOT MEASURED earlier this round — an McpDataBridge implementation census. It is measured now, and the answer is not the one the limb was shaped to expect. ⛔ Nothing here asks for the card to leave pm:on-hold.

    The census by TYPE returns 1, not 2 — and that miss IS the reading

    Swept origin/main for every site declaring or annotating the type:

    instrument result
    (interface|type)\s+McpDataBridge 1 — packages/mcp/src/mcp-http-tools.ts:81 (the declaration)
    :\s*(Promise<)?McpDataBridge, satisfies McpDataBridge, <McpDataBridge> 1 producer — createStdioDataBridge (stdio-data-bridge.ts:294, with its literal at :297)
    the runtime implementation this card is about 0 hits

    The card already names two implementations, so a sweep returning one is a defective instrument, not a corrected count. Opening the miss is what produced the actual finding:

    packages/runtime/src/domains/mcp.ts:487
    export function buildMcpBridge(deps: DomainHandlerDeps, context: HttpProtocolContext): any {
    

    The return type is any. In that whole file the identifier McpDataBridge occurs exactly twice and both are TSDoc prose (:479 "Build a principal-bound {@link McpDataBridge}", :516 "McpDataBridge declares this member OPTIONAL"). It is never imported and never annotated.

    ⭐ And it cannot be, as things stand: @objectstack/runtime's manifest declares 30 dependencies and none matches mcp. The type is not nameable from this package without adding a package-boundary dependency — so the any is structural, ⛔ not a style lapse someone can tighten in place.

    Controls, because a zero from a mistyped path is not a reading: the manifest control actually read a file (3291 bytes, 30 deps parsed, five named back), and the negative token McpDataBridgeZZZ returns 0, so the sweep is not matching everything.

    What this does to the card's own Restart-when

    The Restart-when reads, in part, "a third McpDataBridge implementation appears".

    ⚠️ There is no instrument in this repo that can fire that condition. Implementation #2 is any-typed and invisible to the compiler and to every type-keyed search; a third one written the same way would be equally invisible. The sweep above is the demonstration rather than the claim — it saw 1 of the 2 implementations this card itself names, and a census that cannot find the second cannot notice a third.

    ⇒ Counting implementations of this seam needs a member-set / structural instrument keyed on the interface's declared members, not on its name. ⛔ No such tool exists here today; ⛔ and this reading does not assert one should be built — that is a scope call, and the card is on hold.

    ⛔ Also not asserted: that the return type should be narrowed, or the dependency added. Either changes what compiles across a package boundary and what a consumer may rely on. That is a contract decision, not a measurement, and this seat does not make it from a grep.

    ⭐ A correction to this seat's own earlier reading, recorded rather than quietly fixed

    Earlier this session this seat recorded this symbol as defined at packages/runtime/src/http-dispatcher.ts:487. The definition is packages/runtime/src/domains/mcp.ts:487. http-dispatcher.ts:1179 is a delegating method wrapper (return buildMcpBridge(this.domainDeps, context)) and :42 is the import. Line right, file wrong.

    It is recorded because it is the second failure of the same kind on this one symbol in one session: the first sent a sed window onto a call site and produced a false zero. A line number carried without its file is the same defect one step earlier.


    Generated by Claude Code

  8. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    关 not_planned —— 维护者逐张复核 on-hold 卡时同意关闭;没有实际泄漏,唤醒条件也无法被检测到

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T08:22Z。维护者 2026-09-23 在分诊会话里逐张复核 pm:on-hold 卡,对第三组的回复原文:「第三组 同意」。 本卡在那一组里的建议是关闭。

    呈给维护者的事实

    • 目前没有泄漏:buildMcpBridge(packages/runtime/src/domains/mcp.ts)所有数据访问都经过 callData,也就是走协议入口的清洗,由 Check whether sys_session.token — a live session credential — serializes over the data API (ADR-0100 channel 3 has no read protection) #7823 的检查守着。2026-09-17T14:09Z 实测(5715758678):callData 8 处,其他直达引擎的路径 0 处。
    • 本卡说的是守卫的范围:packages/mcp 里的检查只遍历 stdio 那一个 McpDataBridge 实现。
    • ⚠️ 「出现第三个实现」这条唤醒条件没有任何工具能检测到:2026-09-17T18:59Z 实测(5719683606),runtime 那个实现的返回类型是 any(@objectstack/runtime 不依赖 mcp,无法引用这个类型),按类型搜只能找到 2 个实现中的 1 个。

    重开条件

    buildMcpBridge 不再经过 callData、直接访问引擎(这正是本卡预言的那一步,会让观察变成真实漏洞);或者 #7823 的检查被收窄或挪离协议入口路径。

    关闭理由:not_planned,同时摘掉 pm:on-hold。


    Generated by Claude Code

  9. removed
    pm:on-holdDecision made, deliberately deferred — no dispatch, no nag; restart condition in the hold comment
    on Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions