Context
V-B-A finding while verifying #13082 against live dev (HEAD 8e545f48b, post #13103/#13084 merge): the Neural Link harness tool-projection mechanism is fully implemented and enforced (#13084 shipped ToolService.isToolAllowedForProjection + the OpenAPI tier contract + POLICY_REFUSED on call_tool), but the projection mode is selected solely from client-supplied request metadata. An embedded agent that simply omits the flag receives the full developer/operator surface — including write-locked and admin tools. This is the "server-side connection-class binding leaf" @neo-gpt flagged as a split-out in the 2026-06-13 team-sunset handoff on #13012.
Release classification: post-release (harness security hardening leaf; non-blocking for v13).
The Problem
The read-only projection for embedded harness agents is opt-in by the client, so it is not a security boundary — it is a convention. The point of the curated tool surface (#13056) is that an embedded agent cannot reach write-locked/admin tools without an explicit capability gate. Today it can, by not asking for the projection.
This is the data-not-code / capability-security discipline the Body already enforces across the worker trust boundary (the App Worker can only emit JSON DOM-ops, never inject code): the surface an actor can reach must bind to what the actor is, not to what the actor claims.
The Architectural Reality
Verified from source on dev@8e545f48b:
ai/mcp/server/neural-link/Server.mjs:85-92 — buildToolProjectionContext({request}) derives the mode from request.params._meta.neoToolProjection; undefined → null.
ai/mcp/server/BaseServer.mjs:168-170 — the base buildToolProjectionContext returns null (full surface) by default; a projection is applied only when a non-null context is returned.
ai/mcp/ToolService.mjs:222-289 — getToolsForProjection(null) returns allToolsForListing (every tool); assertToolProjectionAllows(name, null) is a no-op. isToolAllowedForProjection is fail-closed for an unknown mode, but the mode itself is client-chosen, so omitting _meta never reaches that gate.
ai/mcp/server/neural-link/Server.mjs:128-151 — the NL server boots a stdio transport (single-tenant); BaseServer.buildRequestContext(reqAuth) is SSE-only (BaseServer.mjs:183-191). So there is no per-request connection identity to bind to in the shipping topology — the binding must live at the server-instance level.
Net: the read-only projection is bypassable by an embedded agent that does not send _meta.neoToolProjection.
The Fix
Bind the projection to the server instance, not the request:
- Add a server-instance "forced projection mode" knob — a resolved
AiConfig leaf (read at the use-site per ADR 0019; never re-implemented/mutated) and/or a boot CLI flag (e.g. --tool-projection-mode harness-embedded).
- In
buildToolProjectionContext (base + NL override), when a forced mode is set, return {mode: forcedMode} and ignore (or take the more-restrictive of) the client _meta. A client must never be able to widen its own surface.
- When no forced mode is set, preserve today's behavior (
null → full surface) for trusted dev/operator-launched servers — backward compatible.
- The Fleet Manager (#13015) is the natural injector: when it spawns an embedded NL server for an embedded agent, it sets the forced mode at spawn (alongside the identity-env + wake-sub injection it already owns).
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Evidence |
buildToolProjectionContext({request}) (BaseServer + NL Server) |
this leaf |
When server-instance forced mode is set, returns {mode: forcedMode} regardless of client _meta; client cannot widen the surface |
No forced mode → existing null/full-surface behavior (dev/operator) |
Unit: forced mode + client omitting _meta → only read tools listed; write-locked/admin call_tool → POLICY_REFUSED |
| Server-instance projection-mode config (new) |
ADR 0019 (AiConfig SSOT) + boot CLI |
Resolved leaf / CLI flag that pins the embedded NL server to harness-embedded |
Unset = full surface (back-compat) |
Unit: config/flag set → forced; unset → null |
_meta.neoToolProjection (existing client hint, Server.mjs:85) |
#13084 |
Demoted to a narrowing-only hint; cannot escalate past the server-instance ceiling |
Server forced mode is the ceiling |
Test: client sending no _meta under forced mode still gets the restricted surface |
Decision Record impact
aligned-with ADR 0020 (the curated harness tool surface is part of the harness concept) and aligned-with ADR 0019 (the projection-mode knob is a resolved AiConfig leaf, read at the use-site). No amend/supersede.
Acceptance Criteria
Out of Scope
- Fleet-manager spawn-time injection of the forced mode (#13015 owns that; this leaf provides the knob it sets).
- Per-connection / multi-tenant SSE auth binding (no SSE NL transport ships today; revisit when it does).
- Topological Locking and the
write-locked capability gate (#13056 siblings) — write-locked stays withheld here.
- Changing the tier taxonomy or the OpenAPI contract (#13064).
Avoided Traps
- Trusting client-asserted capability — rejected; capability binds to what the server instance is, not what the client claims.
- Per-request auth binding — rejected for the shipping topology; stdio is single-tenant with no per-request identity. Server-instance binding is the correct grain.
- Failing open when the mode config is missing at an embedded spawn — the embedded spawn path (#13015) must set the mode explicitly; unset=full-surface is preserved only for the trusted dev/operator launch.
Related
- Parent: #13056 (Extended-NL coordination: curated tool surface)
- Builds on: #13084 / PR #13103 (projection boundary + mechanism), #13064 / PR #13078 (tier contract)
- Consumer/injector: #13015 (Fleet Manager MVP — spawn-time provisioning)
- Sibling (closed; verified done by #13103): #13082
- Concept: ADR 0020 (
learn/agentos/decisions/0020-agent-harness-concept.md)
Live latest-open sweep: checked latest 25 open issues at 2026-06-13 (post-#13103 merge); only #13056 (parent) overlaps — no equivalent leaf. A2A in-flight sweep: turn-start list_messages (50, recency-bound) shows no [lane-claim]/[lane-intent] on this scope; #13082/#13084 released/merged.
Retrieval Hint: "harness-embedded neural link tool projection client-asserted server-instance forced mode bypass _meta neoToolProjection"
Verification anchor: dev@8e545f48b (#13103) — ai/mcp/ToolService.mjs, ai/mcp/server/BaseServer.mjs, ai/mcp/server/neural-link/Server.mjs
Context
V-B-A finding while verifying #13082 against live
dev(HEAD8e545f48b, post #13103/#13084 merge): the Neural Link harness tool-projection mechanism is fully implemented and enforced (#13084 shippedToolService.isToolAllowedForProjection+ the OpenAPI tier contract +POLICY_REFUSEDoncall_tool), but the projection mode is selected solely from client-supplied request metadata. An embedded agent that simply omits the flag receives the full developer/operator surface — includingwrite-lockedandadmintools. This is the "server-side connection-class binding leaf" @neo-gpt flagged as a split-out in the 2026-06-13 team-sunset handoff on #13012.Release classification: post-release (harness security hardening leaf; non-blocking for v13).
The Problem
The read-only projection for embedded harness agents is opt-in by the client, so it is not a security boundary — it is a convention. The point of the curated tool surface (#13056) is that an embedded agent cannot reach
write-locked/admintools without an explicit capability gate. Today it can, by not asking for the projection.This is the data-not-code / capability-security discipline the Body already enforces across the worker trust boundary (the App Worker can only emit JSON DOM-ops, never inject code): the surface an actor can reach must bind to what the actor is, not to what the actor claims.
The Architectural Reality
Verified from source on
dev@8e545f48b:ai/mcp/server/neural-link/Server.mjs:85-92—buildToolProjectionContext({request})derives the mode fromrequest.params._meta.neoToolProjection;undefined → null.ai/mcp/server/BaseServer.mjs:168-170— the basebuildToolProjectionContextreturnsnull(full surface) by default; a projection is applied only when a non-null context is returned.ai/mcp/ToolService.mjs:222-289—getToolsForProjection(null)returnsallToolsForListing(every tool);assertToolProjectionAllows(name, null)is a no-op.isToolAllowedForProjectionis fail-closed for an unknown mode, but the mode itself is client-chosen, so omitting_metanever reaches that gate.ai/mcp/server/neural-link/Server.mjs:128-151— the NL server boots a stdio transport (single-tenant);BaseServer.buildRequestContext(reqAuth)is SSE-only (BaseServer.mjs:183-191). So there is no per-request connection identity to bind to in the shipping topology — the binding must live at the server-instance level.Net: the read-only projection is bypassable by an embedded agent that does not send
_meta.neoToolProjection.The Fix
Bind the projection to the server instance, not the request:
AiConfigleaf (read at the use-site per ADR 0019; never re-implemented/mutated) and/or a boot CLI flag (e.g.--tool-projection-mode harness-embedded).buildToolProjectionContext(base + NL override), when a forced mode is set, return{mode: forcedMode}and ignore (or take the more-restrictive of) the client_meta. A client must never be able to widen its own surface.null→ full surface) for trusted dev/operator-launched servers — backward compatible.Contract Ledger Matrix
buildToolProjectionContext({request})(BaseServer+ NLServer){mode: forcedMode}regardless of client_meta; client cannot widen the surfacenull/full-surface behavior (dev/operator)_meta→ onlyreadtools listed;write-locked/admincall_tool→POLICY_REFUSEDharness-embeddednull_meta.neoToolProjection(existing client hint,Server.mjs:85)_metaunder forced mode still gets the restricted surfaceDecision Record impact
aligned-withADR 0020 (the curated harness tool surface is part of the harness concept) andaligned-withADR 0019 (the projection-mode knob is a resolved AiConfig leaf, read at the use-site). No amend/supersede.Acceptance Criteria
harness-embedded, the NL server applies the read-only projection to every request regardless of client_meta—tools/listlists onlyread-tier tools;call_toolon awrite-locked/admintool returnsPOLICY_REFUSED._metais narrowing-only)._metabypass is closed under forced mode, and that the unset default is unchanged.Out of Scope
write-lockedcapability gate (#13056 siblings) —write-lockedstays withheld here.Avoided Traps
Related
learn/agentos/decisions/0020-agent-harness-concept.md)Live latest-open sweep: checked latest 25 open issues at 2026-06-13 (post-#13103 merge); only #13056 (parent) overlaps — no equivalent leaf. A2A in-flight sweep: turn-start
list_messages(50, recency-bound) shows no[lane-claim]/[lane-intent]on this scope; #13082/#13084 released/merged.Retrieval Hint: "harness-embedded neural link tool projection client-asserted server-instance forced mode bypass _meta neoToolProjection"
Verification anchor: dev@8e545f48b (#13103) — ai/mcp/ToolService.mjs, ai/mcp/server/BaseServer.mjs, ai/mcp/server/neural-link/Server.mjs