Bobbit controls agents with full shell access to the host machine. Treat its admin token like an SSH key, and expose the gateway only through authorities and network paths you intend to operate.
Request admission is the gateway's outer network boundary. It rejects untrusted browser and authority contexts before readiness checks, public routes, CORS, authentication, preview/static handling, API dispatch, or WebSocket upgrade. It supplements bearer, signed-cookie, sandbox-token, rate-limit, and route authorization; it does not replace them.
- A 256-bit cryptographically random token is generated on first run. It is persisted in
serverSecretsDir()(an OS user-level directory outside any project root, defaulting to<AppData|Library/Application Support|~/.local/state>/bobbit/secrets/<hash>/token) with mode0600. Override the directory withBOBBIT_SECRETS_DIR. Keeping the token outside Headquarters prevents ordinary same-root project agents from reading the gateway admin credential. See Headquarters — Live secrets exception. - HTTP routes enforce their existing bearer, signed-browser-cookie, or scoped sandbox credential by default, including on loopback. A WebSocket's first frame must authenticate with the admin token or a session-scoped sandbox token.
--no-authexplicitly enables the local credential bypass only when the complete policy and actual socket peer are loopback: IPv4127/8, IPv6::1, or IPv4-mapped loopback. Caller-controlled Host/Origin values cannot establish local authority. - The peer-bound trusted-local rule applies consistently to API requests, preview/cookie bootstrap, and WebSocket upgrades. Adding any non-loopback public, published, direct-listener, TLS, or Vite authority disables the explicit local credential bypass for the complete policy, including requests addressed through loopback. This prevents a public deployment from retaining an accidental unauthenticated backend path.
- Docker Desktop and similar host-gateway proxies can re-originate container traffic as an indistinguishable loopback peer. Bobbit therefore refuses sandbox creation, restoration, revival, respawn, and replacement before side effects while credential-free trusted-local control is enabled. Restart without
--no-authto use sandboxes; authenticated sandboxes receive only scoped tokens. - Constant-time token comparison prevents timing attacks. Failed authentication is IP-rate-limited, and WebSocket authentication has a five-second timeout.
- Static file serving prevents directory traversal by requiring resolved paths to remain below the static directory.
- The gateway binds to the NordLynx mesh IP when requested, otherwise to
localhost. It never binds to0.0.0.0unless explicitly configured. TLS defaults on for non-loopback addresses and off for localhost unless--tlsis passed. - OAuth uses PKCE when obtaining API credentials.
The gateway compiles one immutable authority policy after the listener has selected its actual port and the published URL is known. Its inputs are finite and configuration-owned:
- the configured bind host when it is not a wildcard;
localhost,127.0.0.1, and[::1]at the actual listener port;- configured direct-TLS certificate names, including the configured deSEC name in the CLI path;
- each explicit public origin;
- the origin of the URL published after binding, including a programmatic
GatewayConfig.onBoundresult.
Separately, explicit Vite-origin-to-trusted-gateway mappings grant the narrow development browser exception. A Vite origin is not added as a gateway Host authority.
0.0.0.0 and :: are listener addresses, not authorities, so they never authorize arbitrary Host values. The policy does not infer names from DNS, TLS SNI, request headers, or the network interface. A reverse proxy, manual DNS name, externally terminated TLS scheme, or external port therefore needs an explicit public origin. See Networking — Request-admission configuration.
Every HTTP request and WebSocket upgrade must contain exactly one trusted Host, even when Origin is absent. An attacker-controlled Host and equal Origin still fail because equality cannot add the authority to the compiled set; this is the DNS-rebinding boundary.
Security-sensitive fields are read from the raw header list so duplicate fields cannot be hidden by Node's normalized header view. Missing or duplicate Host, duplicate Origin, duplicate Fetch Metadata/preflight fields, comma-joined values, control characters, whitespace ambiguity, userinfo, paths, queries, fragments, bad brackets, invalid ports, and unbracketed IPv6 are rejected. Valid values are compared after canonicalizing HTTP(S) scheme, DNS case and a single trailing dot, IPv4/IPv6 spelling, and default or explicit ports.
A trusted Host is necessary in every row. A present Origin must be one exact normalized origin for that authority, or the configured Vite origin paired with it. null, merely same-site, and attacker-selected origins are not accepted.
| Context | Accepted browser shape | Why |
|---|---|---|
| Top-level UI or preview document | Safe GET/HEAD navigation to a document with no Origin, including address-bar, bookmark, reload, external-link, and supported popup contexts; or an exact same-origin/Vite navigation |
Users must be able to open trusted URLs normally, but the navigation exception must not grant API authority. |
| UI static resource or manifest | Exact same-origin/Vite context, or coherent originless same-origin subresource metadata | Ordinary page loading remains usable without accepting sibling-origin embedding. |
| API | Exact same-origin/Vite context; a same-origin browser fetch may omit Origin when its Fetch Metadata is coherent |
Browsers do not send Origin on every same-origin request, so Host and Fetch Metadata must classify those requests without creating a cross-site bypass. |
| Embedded preview iframe | Exact same-origin/Vite iframe navigation | The top-level navigation exception never applies to iframes, and cross-site iframe navigation is rejected even when ambient preview state exists. The loaded frame then receives an opaque origin. |
| Opaque preview redirect or asset | Origin: null or the coherent browser follow-on shape, only on the matching preview route |
Every resource is authorized independently with the session-bound preview capability; authorizing the first HTML response does not authorize later resources or another session path. |
| Preview SSE stream | Exact same-origin/Vite browser context, with coherent originless same-origin metadata where browsers normally omit Origin |
SSE remains an application transport authenticated by the normal session/admin path, not an opaque-frame capability. |
| WebSocket | Exact allowed browser Origin; supplied Fetch Metadata must describe a same-origin socket. Both the standard empty destination and WebKit's websocket destination are accepted. |
Admission runs before either session/viewer upgrade and before first-frame authentication. |
| CORS preflight | Exact configured origin, allowed requested method and headers, coherent metadata, and no private-network request | The response advertises only the capability that was actually approved. |
A client with no browser Origin or Fetch Metadata can proceed as non-browser traffic. Node's known originless HTTP-fetch shape is treated the same way. This preserves CLI, agent, and sandbox callbacks, but only after Host admission and without bypassing their normal bearer or sandbox-scope checks. Browser-shaped partial, same-site, cross-site, or incoherent metadata is rejected outside the narrow safe-navigation case.
CORS responses come from the admission decision rather than route-local reflection:
Access-Control-Allow-Originis the exact approved origin, never*.Vary: Originis added.- Preflights return only the requested allowed method and requested allowed headers, with a bounded cache lifetime.
- Cross-origin cookies are not advertised on general routes:
Access-Control-Allow-Credentialsis omitted. API and WebSocket transports use bearer authentication across the finite Vite exception. The only narrow exception is a successfully authenticatedOrigin: nullrequest below the matching preview session route, where the read-only preview capability needs credentialed CORS for opaque-frame assets. - An unapproved preflight returns
403without CORS capability headers. - Private Network Access preflights are denied. The gateway omits
Access-Control-Allow-Private-Network; it never sends either an affirmative grant or a misleadingfalsevalue.
Forwarded and X-Forwarded-* are ignored for admission. There is no implicit trusted-proxy hop or CIDR mode. A proxy must preserve the externally visible Host and the deployment must declare its public origin; otherwise the browser's public Origin cannot match the admitted gateway authority. This fail-closed behavior prevents an untrusted client from manufacturing the authority through forwarding headers.
Rejected requests log only a stable reason code, transport, method, coarse route context, and bounded remote address. Raw URLs, query strings, authorization values, cookies, and header contents are intentionally excluded so a security diagnostic cannot leak credentials.
Request admission prevents an unrelated web origin from reaching Bobbit. A second boundary isolates repository- or agent-authored HTML that Bobbit intentionally renders.
Inline .html/.htm chat cards and side-panel preview documents run in iframes with sandbox="allow-scripts" and no allow-same-origin. The resulting opaque/null origin prevents authored scripts from reading the parent DOM, application storage, stored gateway credentials, and Bobbit session state. Preview responses also carry a CSP sandbox without same-origin permission; the policy applies to successful HTML, SVG/other assets, and HEAD, including content opened in a standalone tab.
Opaque assets cannot use normal same-site cookie behavior, so a successful primary-authenticated preview response mints a separate bobbit_preview cookie. It is HttpOnly, Secure, SameSite=None, read-only, bound to one session, and path-scoped below that session's preview mount. It does not authorize APIs, WebSockets, another session's preview, or an MCP decision. Credentialed Origin: null CORS is returned only after this capability verifies on the matching preview route; hostile cross-site iframe navigation is rejected before redirects or bytes.
Theme, resize, and side-panel swipe compatibility use a bounded postMessage bridge instead of parent DOM access. The child accepts theme data only from its exact parent and validates an explicit cosmetic-token allowlist; theme/ready/resize messages use the expected protocol version. The host accepts child messages only from the registered or active preview frame and validates/clamps their exact shape; side-panel swipe messages are additionally limited to the active preview. Bridge failure costs cosmetics or gestures, never isolation.
Pack panels and app-owned renderers that execute directly in the host document remain part of the Bobbit application trust domain and use its authenticated transports. Do not move repository-authored HTML into that domain or add allow-same-origin as a compatibility workaround.
Project-controlled MCP definitions are inert until an operator approves their exact effective behavior. Pending, rejected, changed, and invalid definitions are not spawned, connected, initialized, sent data, or registered as tools. This startup gate is separate from Allow / Ask / Never operation policy, which controls calls only after a server is eligible.
Approval and rejection use the established gateway control-plane authority: a valid admin bearer/query token, genuine signed bobbit_session, or peer-bound trusted-local admission. Direct, non-sandbox agents intentionally receive the admin BOBBIT_TOKEN and may decide; this is not a human-only boundary. Sandbox agents receive server-minted, project-scoped tokens, and the MCP decision route is outside their allowlist, so sandbox-only requests return 403 before body, ledger, manager, process, or network effects. A selected sandbox credential retains its scope even on a loopback peer, and configured sandbox credentials cannot override BOBBIT_TOKEN in any casing.
Approval decisions and their HMAC key live in the private OS-user serverSecretsDir() namespace rather than repository-reachable Headquarters state. The retired terminal/browser pairing system has no active endpoint, header, or verifier reader. App startup best-effort removes only the legacy browser key mcp.operator.credentials.v1; an inert serverSecretsDir()/mcp-operator-authorization.json may be deleted manually but is never auto-unlinked.
Decisions bind the stable project, logical source, server name, and a keyed fingerprint of every execution- or connection-relevant field, including secret values before display redaction. Worktree review also binds a current owning session or goal to its validated project/execution scope; arbitrary paths, foreign or stale owners, and root-only review cannot authorize an external sibling worktree.
Project Marketplace MCP contributions are pretrusted only with a private install attestation for the exact contribution configuration and complete installed pack. The pack measurement covers all directories, regular files, internal relative symlinks, paths, relevant mode bits, bytes, and link targets, with bounds and race rechecks. Unsafe, changed, missing, legacy, or unverifiable attestations fail closed into ordinary project review; packs without MCP contributions do not need this MCP-specific measurement.
Review metadata exposes project-relative provenance and useful command structure while redacting environment/header values, URL credentials/query/fragment, credential-bearing arguments, configured secret substrings, private source paths, and attestation signals. Runtime health/error output has its own projection: configured environment/header values and URL credential components are removed, configured URLs become safe endpoints, and output is bounded. See MCP server startup approvals for source classes, persistence, lifecycle, API semantics, and recovery.
The preview mount API and /preview/<session>/... content routes scope rendered bytes per session. Security measures include:
- UUID validation:
sessionIdis validated against a strict UUID-shaped expression. Values containing traversal syntax, backslashes, or colons return400, preventing sandbox agents from selecting an arbitrary state directory. - Path and asset confinement: mount input uses explicit asset opt-in, and content resolution rejects absolute paths, traversal, backslashes, NULs, and symlink escape.
- Opaque transport capability: primary authentication can bootstrap only the session-bound preview cookie described above; every follow-on content request revalidates the route/session binding.
- Vite filesystem deny:
server.fs.denyrules block.bobbitandnode_modules/.vite, preventing Vite's/@fs/route from serving sensitive files. - Vite plugin hardening:
blockDangerousGlobsrejectsimport.meta.globcalls targeting.bobbitpaths.localhostGuardrejects non-loopback peers when Vite is bound to localhost and blocks Docker bridge addresses in non-local development mode.
See Embedded HTML preview architecture for cookie admission, opaque-origin symptoms, CSP, CORS, messaging, mount, asset, SSE, theme, and artifact details.
AI Gateway well-known documents may name a one-hop remote config and cross-origin provider endpoints. Bobbit treats those URLs as untrusted: cross-origin targets require HTTPS and public DNS answers, discovery pins validated answers, redirects are refused, and the configured-origin bearer token never crosses origins. The gateway revalidates admitted provider DNS at connection time; agent processes do so through a generated extension when it can be written and activated. Extension-write failure is logged but does not block agent startup, so operators using cross-origin providers must treat that warning as security-relevant. See AI Gateway routing — Remote config security for the complete URL, header, deadline, and container-guard policy.
The configurable agent directory can contain provider credentials, so sandbox containers receive only narrow mounts:
- active
<agentDir>/sessions/for transcript continuity; - active
<agentDir>/models.jsonread-only when present; and - a generated, project-scoped auth file mounted as
/home/node/.bobbit/agent/auth.json.
Bobbit never mounts the full host agent directory or host <agentDir>/auth.json into Docker. Remote-less sandbox clone sources are generated from sanitized tracked content that excludes .bobbit/ and auth.json, then mounted read-only. See Configurable agent directory.