Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,19 @@ The shared React renderer supports desktop and direct browser clients. New rende

Remote-access credentials are authority boundaries. Pairing tokens must remain short-lived, memory-only, and single-use. Persist only device-session digests in owner-only atomic stores; enforce expiry and revocation. Browser HTTP/WebSocket origins must match exactly or be explicitly configured—never reflect arbitrary `Origin`/forwarded headers. Keep authentication limiters bounded, and do not hardcode CJ's `crewcode.logixhub.icu` deployment as a default Hub URL.

The self-hosted Hub is a separate `crewcode hub` process, not Electron renderer state. Keep its SQLite store owner-only and server-side; persist WebAuthn public credentials and only digests of browser/CSRF secrets. Bootstrap credentials and WebAuthn challenges stay short-lived and memory-only. Require user verification, exact configured RP origin/id, one-use challenges, secure HttpOnly SameSite cookies, and CSRF checks for mutations. Do not let Hub identity imply brain execution authority: enrollment, tickets, relay, and brain-side authorization remain separate gates.
Browser delegation keeps its agent-facing endpoint Brain-loopback and bearer-scoped;
delegate requests, editor watches, and LSP handles remain bound to the authenticated
browser session that owns them. Browser plugin iframes load only approved plugin
assets through expiring asset-only grants and continue to invoke capabilities through
the trusted renderer plus manifest permission gate. Remote GitHub UI may drive the
Brain's `gh` device login and registered-workspace publishing, but must never expose
the Brain's GitHub credential or allow remote logout.

The self-hosted Hub is a separate `crewcode hub` process, not Electron renderer state. `crewcode hub --local-brain` may spawn a sibling `crewcode brain` on the Hub host after owner passkey setup; keep Hub SQLite and Brain credentials in separate data dirs, do not default-grant scopes, and still enroll extra machines with `crewcode enroll` then `crewcode brain`. Keep its SQLite store owner-only and server-side; persist WebAuthn public credentials and only digests of browser/CSRF secrets. Bootstrap credentials and WebAuthn challenges stay short-lived and memory-only. Require user verification, exact configured RP origin/id, one-use challenges, secure HttpOnly SameSite cookies, and CSRF checks for mutations. Machine enrollment tokens must also stay short-lived, memory-only, single-use, and rate-limited; persist only machine bearer digests at the Hub and keep the brain credential file owner-only. Presence and relay connections are outbound-only and revocation must fail closed. Hub connection tickets remain short-lived, memory-only, one-shot, browser-session/user/machine bound, and exact-origin protected. Relay application frames must stay end-to-end encrypted and ordered; the Hub may route metadata but must not receive RPC/source/terminal/agent plaintext. Do not let Hub identity, machine presence, or requested ticket scope imply Brain execution authority: `crewcode brain` defaults to no RPC grants, and every decrypted method must pass both explicit Brain-local scope and registered-workspace validation. Relay loss means pending outcomes are interrupted, never successful.

Remote cross-thread conversation handoff stays Brain-local. Namespace browser replay shards under `web:`; never copy the replay store into browser persistence. Require an authenticated owner-held destination bridge and Brain-local `agent` scope, refuse handoff while the destination is running, perform bounded disposable summarization on the Brain, clear the destination native resume id, and replay the combined destination history exactly once on its next native-provider prompt. Missing history, lost ownership, or summary failure is an explicit failure, never inferred success.

The Hub mobile home is control-plane-only until a user selects an online machine. At ≤768px an authenticated Hub root may route to `/app?hub=mobile`, where `MobileDashboard` reads only cookie-authenticated Hub session and machine-presence data. Do not install a Brain runtime, request relay authority, expose fake agent/worktree statistics, or affect Electron/direct-server/desktop startup from this route. Machine selection enters `/app?hub=mobile&machine=…`, which may open a disposable end-to-end encrypted, Brain-scoped mobile overview; it must request only the scopes needed for real stats, render unavailable values explicitly, return bounded recent-thread metadata rather than transcript bodies, and close its relay before the full renderer opens. Compatibility fallback for an older Brain may use `transcripts.mtimes` to render untitled saved rows, but must never use `transcripts.loadAll` for this overview. Mobile overview counts must use the canonical Mission Control `deriveMissionStats` aggregation over Brain-visible transcript sessions and executions; do not duplicate `mc-stats` semantics, and do not classify completed solo turns as done. A recent-row deep link must carry a bounded workspace/tab/scope descriptor, validate workspace→tab→scope ownership after transcript hydration, preserve the exact transcript scope id when restoring a missing browser session, and refuse invalid/conflicting descriptors. Only the explicit full-app action enters `/app?machine=…` without a thread target. Keep `/?hub-admin=1` as the mobile escape hatch for Hub account and device administration.

### Path alias

Expand Down
76 changes: 76 additions & 0 deletions docs/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,82 @@ per-authenticated-session RPC bandwidth/request budget, remote execution custody
not fully persisted, and public deployment still depends on correctly configured
TLS/reverse-proxy infrastructure. Prefer loopback or a trusted tailnet.

## Hub owner -> enrolled machine boundary

**Boundary:** signing into the Hub must not silently enroll a machine or grant remote
execution. A stolen/replayed enrollment link, database disclosure, cross-site
request, or revoked machine credential must not produce lasting machine presence.

**Enforcement:** only a valid owner browser session plus its rotated CSRF secret can
issue an enrollment token. Tokens contain 256 bits of random secret, live only in
Hub memory, expire after ten minutes, are single-use, and are consumed after a failed
secret presentation for a known id. Issuance/enrollment requests are bounded by a
per-peer limiter. Enrollment creates an Ed25519 identity on the brain and a separate
256-bit bearer credential; the brain stores both in an owner-only atomic file while
the Hub stores the public key and only SHA-256 of the bearer secret. Heartbeats are
outbound HTTPS requests. Presence goes offline after 90 seconds without a successful
heartbeat, and owner revocation immediately makes the bearer credential fail closed.
Browser mutations retain exact-origin and CSRF enforcement. The explicitly labeled
first-owner QR carries the same short-lived, single-use bootstrap fragment already
printed to the trusted terminal; it expires in ten minutes and is removed after
registration. Normal mobile QR codes encode only the stable validated HTTPS Hub
origin and are rendered in the authenticated dashboard; they contain no browser
session, enrollment secret, or Brain ticket. Phone-approved enrollment keeps its
Ed25519 private key on the PC, uses the short code only for human comparison, and
protects polling/credential delivery with a separate 256-bit request secret. Pending
requests are memory-only, bounded, rate-limited, ten-minute, owner-approved with
CSRF, one-time on delivery, and audited.
Tailscale setup refuses to overwrite an existing Serve configuration without an
explicit replacement flag. QR transfer does not weaken normal passkey sign-in.

**Connection and execution gates:** only an authenticated Hub browser session plus
CSRF can issue a 60-second memory-only ticket for one owned, active, relay-connected
machine. Ticket ids are one-shot even after a wrong-secret guess. The authenticated
outbound Brain WebSocket and exact-origin browser WebSocket are paired only for that
machine; the relay bounds frame size and buffered output, applies a shared per-connection
token bucket (240-frame/8 MiB burst, refilling at 60 frames and 2 MiB per second),
and never accepts arbitrary destinations. Browser and Brain then use ephemeral P-256 ECDH. The Brain signs the
handshake transcript with its enrolled Ed25519 key, and ordered application frames
use direction-separated HKDF/AES-256-GCM keys. The Hub sees routing metadata and
handshake public values, but not RPC, source, terminal, prompt, or response plaintext.

Hub identity still does not grant execution. `crewcode hub --local-brain` enrolls
the Hub host only after the owner passkey exists, then spawns a sibling Brain;
it does not skip Brain-local scope checks or write workspace roots into Hub SQLite.
The first `crewcode brain` start grants
no RPC scope by default and seeds an owner-only persisted policy from explicit local
`--workspace-root` and repeatable `--allow-scope` settings. Thereafter Settings →
Brain Access manages it only through E2EE owner RPC. Reductions apply immediately,
stop affected agents/terminals, and remove scopes from existing sessions; additions
require a fresh ticket and handshake. Each decrypted method must be included in both
the ticket request and current local grant. The backend revalidates live workspace
roots for filesystem, Git, PTY, attachments, and agent calls.

**Tests:** `hub-server.test.ts` covers CSRF, issue/enroll, local-brain owner gating,
replay rejection, stale
presence, heartbeat, and revocation. `hub-local-brain.test.ts` covers sibling spawn
plans, loopback origin matching, credential reuse, and supervisor stop. `hub-machine-enrollment.test.ts` covers URL
policy, argument-secret avoidance, credential validation, owner-only file mode, and
Brain CLI grants. `hub-relay.test.ts` covers ticket expiry/one-shot behavior,
authenticated relay routing, Ed25519-authenticated E2EE, scoped read success, local
scope denial, per-connection traffic rejection, and ticket replay rejection.
`hub-relay-client.test.ts` covers explicit fresh-ticket reconnect without RPC replay.

**Residual limitation:** machine credential rotation/logout and recovery are not yet
implemented. Relay traffic is bounded per connection, but durable bandwidth metrics
and broader aggregate abuse accounting are not implemented. Browser relay loss now
detaches Brain-owned terminals and agents instead of stopping them. A fresh encrypted
connection can explicitly reclaim stable resource ids, with up to 100 owned resources
per user and 1,000 events / 1 MiB of detached evidence buffered per resource;
interrupted RPCs are never replayed.
Execution custody is still process-resident rather than crash durable: Brain process,
VPS, revocation, or persistent Brain-to-Hub relay loss can stop execution without a
complete remote halt journal. Attachments are tunneled as ordered chunks inside the
E2EE relay: Hub receives ciphertext only, while Brain enforces `workspace:write`, a
25 MiB file limit, registered-root and symlink containment, strict sequence and
size bounds, SHA-256 integrity, active-upload limits, and temporary-file cleanup.


## Hop 1 — untrusted content -> agent

**Boundary:** injected instructions in scraped/file/MCP content must not gain
Expand Down
1 change: 1 addition & 0 deletions electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default defineConfig({
index: resolve('src/main/index.ts'),
headless: resolve('src/main/headless.ts'),
hub: resolve('src/main/hub.ts'),
brain: resolve('src/main/hub-machine-enrollment.ts'),
},
},
}
Expand Down
Loading
Loading