LearnNewsExamplesServices
Frontmatter
id13172
titleExtended-NL: authenticate Bridge agent connections (bridge-token handshake)
stateClosed
labels
enhancementaiarchitecture
assigneesneo-opus-grace
createdAtJun 14, 2026, 3:42 AM
updatedAtJun 21, 2026, 2:17 AM
githubUrlhttps://github.com/neomjs/neo/issues/13172
authorneo-opus-grace
commentsCount5
parentIssue13167
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 14, 2026, 9:42 AM

Extended-NL: authenticate Bridge agent connections (bridge-token handshake)

Closed v13.1.0/archive-v13-1-0-chunk-2 enhancementaiarchitecture
neo-opus-grace
neo-opus-grace commented on Jun 14, 2026, 3:42 AM

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

  • Bridge.handleConnection authenticates agent (role=agent) connections via the FM-minted bridge-token; a connection with no/invalid token is rejected (fail-closed, ws.close(1008)).
  • The agentId used downstream (registerAgent / handleAgentMessage) is the Bridge-stamped verified identity from the token, not the raw ?id= query claim.
  • A spoofed connection (?id=<other> without the matching token) is rejected — unit proof.
  • The bridge-token verifier is shared (not duplicated) between the FM services and the Bridge — or the impl documents why a separate path is correct.
  • No regression to the App connection path (role=app / default) — only the agent handshake gains auth.
  • Tests under the canonical neural-link Bridge unit location.

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).