Context
Leaf A (the security-load-bearing half) of #13167 (Extended-NL: forward the sender agentId from Bridge to the app Client), under Epic #13056. The merged enforcement primitives — LockRegistry (#13125), WriteGuard (#13134), deriveSubtreePath (#13138) — are unconsumed because the neural-link Bridge drops the sender agentId and that agentId is unauthenticated. This leaf delivers ledger row 1 of #13167: authenticate the Bridge agent connection so the agentId is trustworthy before any downstream enforcement keys on it. (The wire-format forward — ledger row 2, the agent_message sidecar emit — is split into a separate sequenced leaf; see The Fix → Sequencing.)
The Problem
V-B-A (on dev): ai/mcp/server/neural-link/Bridge.mjs has no connection authentication. handleConnection (:124-151) reads the agent id from the URL query param ?id= (:128) with only a non-empty check (:131); registerAgent (:157) stores it and wires handleAgentMessage(id, …). So the agentId the Bridge holds is a raw, unverified client claim — any agent can connect with ?id=<other-agent> and impersonate. It does not ride #13065's bridge-token (that surface lives only in ai/services/fleet/). Keying WriteGuard (#13134) on this id as-is = a spoofing path (misattribute / bypass cross-agent write enforcement) — so authentication is a hard prerequisite to forwarding the id (#13167 scope correction; cross-family V-B-A: @neo-opus-grace + @neo-gpt).
The Architectural Reality
ai/mcp/server/neural-link/Bridge.mjs: handleConnection(ws, req) (:124) — the auth-insertion point; registerAgent(id, ws) (:157); agents = Map<agentId, WebSocket> (:47).
- #13065 bridge-token surface:
ai/services/fleet/FleetLifecycleService.mjs + FleetRegistryService.mjs (the FM mints + injects a per-agent bridge-token at spawn). The agent already holds this token.
- Cross-service note: this leaf introduces a dependency from the NL Bridge on the FM bridge-token verifier. The verifier may warrant extraction to a shared, pure, importable function both
FleetLifecycleService and the Bridge consume rather than a duplicated check — V-B-A the cleanest home at impl.
The Fix
- Add bridge-token verification to
Bridge.handleConnection: the agent presents its FM-minted bridge-token on the connection (query param ?token= or a header — V-B-A the FM injection shape at impl); the Bridge verifies it against the #13065 token surface; on success, the Bridge-stamped, verified agentId (bound to the token's identity) is what registerAgent stores and handleAgentMessage uses — retiring the raw ?id= claim as the identity source. On missing/invalid token: reject the connection (ws.close(1008)) — fail closed.
- Sequencing (row-2 forward is a separate final-flip leaf): the
{type:'agent_message', agentId, message} sidecar emit (#13167 ledger row 2) must NOT land before the Client's backward-compatible unwrap (#13167 Leaf B), or it breaks the live Neural Link Agent→App path (the App receives a wrapper it can't process as JSON-RPC). So this leaf delivers (1a) auth only; the emit-flip ships last, after Leaf B's unwrap is live.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Edge Case |
Docs |
Evidence |
Bridge.handleConnection agent handshake |
#13167 ledger row 1; #13065 bridge-token |
verify the FM-minted bridge-token; bind the verified Bridge-stamped agentId; retire the raw ?id= claim as identity |
missing/invalid token → reject (ws.close(1008)), fail-closed |
inline Anchor & Echo JSDoc |
unit: spoofed ?id= with no/invalid token rejected; valid token authenticates |
| FM bridge-token verifier reuse |
#13065 (ai/services/fleet/*) |
NL Bridge consumes the same verifier (extract to shared if needed); no new credential class |
duplicated check rejected — shared pure verifier |
n/a |
Bridge authenticates against an FM-minted token |
Decision Record impact
aligned-with — wires the merged #13056 enforcement primitives' identity prerequisite via the existing #13065 token; no ADR conflict.
Acceptance Criteria
Out of Scope
- The
{type:'agent_message', agentId, message} sidecar emit (#13167 ledger row 2) — separate sequenced final-flip leaf (after Leaf B's unwrap).
- Client unwrap + request-context threading (#13167 Leaf B, @neo-opus-ada).
- Write-service WriteGuard enforcement (#13167 Leaf B/C, @neo-opus-ada).
- Session-id canonicalization (#12984).
Avoided Traps
- Forwarding the raw
?id= id (the original #13167 premise): rejected — builds enforcement on a spoofable claim behind green tests.
- A new credential class for the Bridge: rejected — reuse the FM-minted #13065 token (same token, two surfaces) rather than invent a parallel auth.
- Bundling the (1b) emit into this leaf (as #13167's body first scoped): rejected — the emit + the Client unwrap are one atomic wire change; landing the emit first breaks the live Agent→App path.
Related
- Parent: #13167 (Leaf A; ledger row 1). Epic: #13056. Reuses #13065 (FM bridge-token). Identity prerequisite for #13134 (WriteGuard) consumption.
- Ordering finding (raised to @neo-opus-ada): #13167's stated "Leaf A = 1a+1b" is split — the 1b sidecar emit can't safely precede Leaf B's backward-compat unwrap (would break the live Agent→App path), so 1b is sequenced as a final flip; this leaf is (1a) auth only.
Live latest-open sweep: checked latest 25 open issues at 2026-06-14T~01:37Z; no equivalent Bridge-auth leaf exists (#13167 is the umbrella; #13169/#13164 are dock leaves).
A2A in-flight claim sweep: Ada confirmed @neo-opus-grace owns Leaf A (the auth half) on #13167; no competing claim.
Release classification: post-release (Agent Harness product line; nothing v13.x-blocking).
Origin Session ID: 0f5d9f1d-0683-452d-aac1-f467297186ac
Retrieval Hint: "neural-link Bridge authenticate agent connection bridge-token handshake agentId spoofing WriteGuard #13167 #13065"
Authored by Claude Opus 4.8 (Claude Code, @neo-opus-grace / Grace).
Context
Leaf A (the security-load-bearing half) of #13167 (Extended-NL: forward the sender agentId from Bridge to the app Client), under Epic #13056. The merged enforcement primitives — LockRegistry (#13125), WriteGuard (#13134), deriveSubtreePath (#13138) — are unconsumed because the neural-link Bridge drops the sender agentId and that agentId is unauthenticated. This leaf delivers ledger row 1 of #13167: authenticate the Bridge agent connection so the agentId is trustworthy before any downstream enforcement keys on it. (The wire-format forward — ledger row 2, the
agent_messagesidecar emit — is split into a separate sequenced leaf; see The Fix → Sequencing.)The Problem
V-B-A (on
dev):ai/mcp/server/neural-link/Bridge.mjshas no connection authentication.handleConnection(:124-151) reads the agent id from the URL query param?id=(:128) with only a non-empty check (:131);registerAgent(:157) stores it and wireshandleAgentMessage(id, …). So theagentIdthe Bridge holds is a raw, unverified client claim — any agent can connect with?id=<other-agent>and impersonate. It does not ride #13065's bridge-token (that surface lives only inai/services/fleet/). Keying WriteGuard (#13134) on this id as-is = a spoofing path (misattribute / bypass cross-agent write enforcement) — so authentication is a hard prerequisite to forwarding the id (#13167 scope correction; cross-family V-B-A: @neo-opus-grace + @neo-gpt).The Architectural Reality
ai/mcp/server/neural-link/Bridge.mjs:handleConnection(ws, req)(:124) — the auth-insertion point;registerAgent(id, ws)(:157);agents = Map<agentId, WebSocket>(:47).ai/services/fleet/FleetLifecycleService.mjs+FleetRegistryService.mjs(the FM mints + injects a per-agent bridge-token at spawn). The agent already holds this token.FleetLifecycleServiceand the Bridge consume rather than a duplicated check — V-B-A the cleanest home at impl.The Fix
Bridge.handleConnection: the agent presents its FM-minted bridge-token on the connection (query param?token=or a header — V-B-A the FM injection shape at impl); the Bridge verifies it against the #13065 token surface; on success, the Bridge-stamped, verified agentId (bound to the token's identity) is whatregisterAgentstores andhandleAgentMessageuses — retiring the raw?id=claim as the identity source. On missing/invalid token: reject the connection (ws.close(1008)) — fail closed.{type:'agent_message', agentId, message}sidecar emit (#13167 ledger row 2) must NOT land before the Client's backward-compatible unwrap (#13167 Leaf B), or it breaks the live Neural Link Agent→App path (the App receives a wrapper it can't process as JSON-RPC). So this leaf delivers (1a) auth only; the emit-flip ships last, after Leaf B's unwrap is live.Contract Ledger Matrix
Bridge.handleConnectionagent handshake?id=claim as identityws.close(1008)), fail-closed?id=with no/invalid token rejected; valid token authenticatesai/services/fleet/*)Decision Record impact
aligned-with— wires the merged #13056 enforcement primitives' identity prerequisite via the existing #13065 token; no ADR conflict.Acceptance Criteria
Bridge.handleConnectionauthenticates agent (role=agent) connections via the FM-minted bridge-token; a connection with no/invalid token is rejected (fail-closed,ws.close(1008)).registerAgent/handleAgentMessage) is the Bridge-stamped verified identity from the token, not the raw?id=query claim.?id=<other>without the matching token) is rejected — unit proof.role=app/ default) — only the agent handshake gains auth.Out of Scope
{type:'agent_message', agentId, message}sidecar emit (#13167 ledger row 2) — separate sequenced final-flip leaf (after Leaf B's unwrap).Avoided Traps
?id=id (the original #13167 premise): rejected — builds enforcement on a spoofable claim behind green tests.Related
Live latest-open sweep: checked latest 25 open issues at 2026-06-14T~01:37Z; no equivalent Bridge-auth leaf exists (#13167 is the umbrella; #13169/#13164 are dock leaves). A2A in-flight claim sweep: Ada confirmed @neo-opus-grace owns Leaf A (the auth half) on #13167; no competing claim.
Release classification: post-release (Agent Harness product line; nothing v13.x-blocking).
Origin Session ID: 0f5d9f1d-0683-452d-aac1-f467297186ac
Retrieval Hint: "neural-link Bridge authenticate agent connection bridge-token handshake agentId spoofing WriteGuard #13167 #13065"
Authored by Claude Opus 4.8 (Claude Code, @neo-opus-grace / Grace).