LearnNewsExamplesServices
Frontmatter
id15537
titleE5: private Electron Fleet capability and credential ingress
stateClosed
labels
enhancementaiarchitecturesecurity
assigneesneo-gpt-emmy
createdAtJul 18, 2026, 11:35 PM
updatedAtJul 19, 2026, 9:17 PM
githubUrlhttps://github.com/neomjs/neo/issues/15537
authorneo-gpt-emmy
commentsCount3
parentIssue13377
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 19, 2026, 9:17 PM

E5: private Electron Fleet capability and credential ingress

Closed Backlog/active-chunk-7 enhancementaiarchitecturesecurity
neo-gpt-emmy
neo-gpt-emmy commented on Jul 18, 2026, 11:35 PM

Context

ADR 0034 already settles this boundary and names one unfiled refinement leaf:

E5 — preload capability contract + credential ingress: the one contextBridge surface + allowlist, sender validation, token custody, and the shell-owned credential surface retiring the transitional Accounts path.

The E8 headed lifecycle witness exposed why E5 is now executable rather than speculative. The native hide/show identity proof and exact-once Brain teardown both pass, but the combined witness remains degraded because the packaged Fleet process is bearer-gated while the Electron shell has neither an authenticated readiness path nor a named renderer capability that can attach the main-owned bearer internally.

Live evidence from npm --prefix harness run witness:lifecycle on 2026-07-18:

  • sameBrowserWindow: true
  • sameRenderer: true
  • sameViewportIdentity: true
  • hide → zero visible windows → tray restore succeeds
  • owned Fleet + orchestrator process groups stop cleanly
  • brainUp: false: awaitFleetReady() times out against the authenticated Fleet endpoint

The latest 20 open issues, targeted searches for Electron preload/Fleet/bearer/credential capability, and team-memory retrieval found no existing E5 leaf.

The Problem

The current source has four pieces that do not yet compose into the packaged security contract:

  1. devFleetServer.mjs requires a per-process bearer and refuses unauthenticated Fleet ingress.
  2. harness/brain.mjs probes POST /fleet without the bearer, so genuine readiness and attach-or-own detection cannot authenticate.
  3. harness/preload.cjs exposes only neoShell.shellVersion; the App Worker therefore has no named private capability through which the Electron main process can attach the bearer without revealing it.
  4. The current Accounts add-agent path still accepts credential bytes in App-Worker state. ADR 0034 §2.3.6 explicitly permits that only as transitional dev-server drift and assigns its packaged-shell retirement to E5.

Fixing only the readiness probe would make one witness green while leaving the product path and credential-ingress contract false. This ticket owns the whole E5 row.

Architectural Reality

ADR 0034 §2.3.4–.6 is upstream authority:

  • There is exactly one minimal contextBridge surface of named, capability-shaped affordances. Raw ipcRenderer and Node exposure are forbidden.
  • Every main-process handler validates event.senderFrame against the packaged app:// origin before acting.
  • The Brain endpoint stays loopback TCP, but loopback is not authentication.
  • The per-boot Fleet bearer remains main/Brain-side. Its bytes never enter renderer-readable state, App-Worker state, URLs, logs, IPC replies, or persisted config.
  • The preload/main capability attaches authorization internally; the renderer invokes intent, not a secret.
  • Packaged credential entry is shell-owned: credential bytes flow preload/main → Brain, while Body/App Worker submits only curated public intent such as {harnessType, id, repo/account facts}.
  • Direct-browser dev mode remains a distinct supported topology. Its current in-memory installFleetBridge({url, bearerToken}) path must keep working and fail closed when no bearer is supplied.

The existing worker boundary is usable without inventing a second transport: App-Worker code already reaches named main-thread functions through Neo.Main. E5 must publish the private shell capability through that boundary while preserving the current globalThis.AgentOS.fleet.registryBridge consumer contract.

The Fix

  1. Add a named neoShell.fleetRequest(intent) preload capability.
    • It accepts only the Fleet wire request shape.
    • It returns only the Fleet response.
    • It never exposes endpoint or bearer getters.
  2. Add one Electron-main Fleet request owner.
    • Retain the current per-boot endpoint + bearer in main-owned state.
    • Attach Authorization internally.
    • Validate the sender frame and packaged origin.
    • Apply a named method allowlist at the existing Fleet dispatch choke point rather than creating a divergent allowlist.
  3. Install the packaged App-Worker bridge through the existing Main-worker boundary.
    • Preserve the current AgentOS.fleet.registryBridge consumer API.
    • Keep the direct-browser installFleetBridge path separate and unchanged.
  4. Authenticate shell readiness and attach-or-own probes with the same main-owned bearer.
    • A generic open loopback port is never readiness.
    • Readiness remains a real Fleet verb/probe response from the process the shell owns or has authenticated for reuse.
  5. Add the shell-owned credential ingress required by §2.3.6.
    • Credential bytes travel preload/main → Brain only.
    • App Worker receives only public readback and status.
    • The packaged Accounts flow no longer holds a PAT.
    • Curated intent remains declarative; no command, args, env, executable path, or secret field crosses from Body.
  6. Preserve multi-window semantics.
    • The retained cockpit and same-origin popups use the same main-owned capability.
    • No per-window secret copy or popup-local credential state is introduced.

Contract Ledger

Surface Owner Required contract Evidence
Preload API harness/preload.cjs one named promise capability; no raw IPC, Node, endpoint, or bearer exposure preload surface unit + negative property census
Main request handler Electron main sender/origin validation, Fleet allowlist, internal bearer attachment admitted + forged-sender + disallowed-method tests
App-Worker composition AgentOS boot bridge packaged shell consumes the same registry-bridge API without bearer bytes real App-Worker request witness
Brain readiness shell lifecycle owner authenticated protocol identity, not port occupancy red/green boot and incumbent-reuse controls
Credential ingress shell-owned surface secret bytes bypass Body/App Worker; public intent/readback remains declarative secret-census + accepted/rejected flow
Multi-window one app:// origin/partition cockpit and popup share capability semantics without secret duplication same-session popup request witness
Direct browser mode installFleetBridge current dev/test transport remains distinct and fail-closed existing transport suite + no-bearer negative

Acceptance Criteria

  • A packaged/headed Fleet request succeeds through a named preload/main capability while the Fleet bearer is absent from window, globalThis, App-Worker state, URL/search params, logs, IPC replies, and persisted configuration.
  • Main rejects a forged/off-origin sender frame before dispatch.
  • Main rejects a disallowed Fleet method and a malformed request before network dispatch.
  • The capability reuses the canonical Fleet wire-method authority; no second drifting method list is introduced.
  • The retained cockpit and a same-origin popup both invoke the same main-owned capability; neither receives or duplicates the secret.
  • probeFleetServing, detectLiveBrain, and awaitFleetReady authenticate the process they inspect. A foreign listener, wrong bearer, mismatched viewer, or generic open port fails closed with a named reason.
  • Packaged credential entry is shell-owned. PAT bytes never enter App-Worker state; Body submits only curated public intent and receives public readback/status.
  • Direct-browser dev mode remains working through its existing transport and still rejects missing or URL-carried bearer material locally.
  • [L3-deferred — healthy macOS host needed] npm --prefix harness run witness:lifecycle becomes fully green with brainUp: true while retaining its same-window, same-renderer, zero-visible, restore, and exact-once teardown receipts.
  • Unit tests include admitted, wrong-bearer, forged-sender, disallowed-method, malformed-request, popup, secret-census, and readiness red controls.
  • Cross-family review includes a security-focused seat and verifies ADR 0034 §2.3.4–.6 line by line.

Out of Scope

  • E8 tray/hide/restore behavior and lifecycle policy (#15531).
  • Fleet business-method semantics or new Fleet wire verbs.
  • Changing PAT persistence policy outside the packaged ingress boundary.
  • External-network or cloud authentication.
  • Replacing the direct-browser development topology.
  • Electron ownership of application windows; popups remain Neo/browser-owned and merely materialize as BrowserWindow instances.

Avoided Traps

  • Exposing getBearer(), bearerToken, endpoint configuration, or raw ipcRenderer through preload.
  • Smuggling the bearer through a query parameter, init script, DOM/global, response object, or log.
  • Teaching the App Worker a packaged-shell branch with secret bytes.
  • Duplicating FLEET_WIRE_METHODS in Electron main.
  • Treating a TCP listener as Fleet readiness.
  • Fixing only the E8 probe while leaving packaged product calls or credential ingress false.
  • Using a renderer-supplied viewer identity, command, args, env, or executable path as authority.

Related

Parent epic: #13377
E8 consumer/witness: #15531
ADR authority: learn/agentos/decisions/0034-electron-shell-architecture.md §2.3.4–.6 and §5 E5
Dev transport: src/ai/fleet/installFleetBridge.mjs
Fleet ingress: ai/services/fleet/fleetIngressAuth.mjs

Origin Session ID: ad71d4c3-3e37-4a17-8df7-8415509def84

Retrieval Hint: Electron E5 private Fleet capability preload main bearer credential ingress App Worker readiness