Skip to content

security

Zachary BENSALEM edited this page Sep 4, 2026 · 6 revisions

Security

Active contributors: Zachary BENSALEM

Fleet is a local, single-user tool. It has no account system, no multi-user authorization, and no remote deployment mode. The web process can run model-generated commands with the user's permissions, so process isolation is for lifecycle and recovery, not a security sandbox.

Trust boundaries

graph LR
    B["Browser on loopback"] -->|"HTTP, NDJSON, SSE"| S["Fleet web server"]
    S -->|"Local daemon socket"| D["Prime Agent daemon"]
    D --> W["Session workers and IPython"]
    W --> F["Filesystem, shell, network<br/>as the user"]
Loading

The browser/server boundary is protected by loopback binding, request-header checks, typed validation, and path containment. The server-to-runtime boundary is a local process boundary but not a privilege boundary. The runtime can access whatever the user can access.

Loopback binding

scripts/prime-agent-web-launcher.mjs enforces local-only access twice:

  • --host accepts only 127.0.0.1, localhost, or ::1;
  • every request must have a loopback Host header and, when present, a loopback Origin header.

Non-loopback requests receive 403 Loopback requests only. The API has no bearer-token authentication or TLS. This is intentional for the local single-user product and means that any process able to reach the port is trusted with the workspace.

Workspace path containment

Workspace reads use resolveContainedWorkspacePath in web/server/src/workspace-paths.ts. It canonicalizes the root, resolves the requested relative path, rejects absolute and .. escapes, resolves symlink targets, and checks containment again. web/server/src/workspace-file.ts limits reads to 1 MiB and treats NUL-containing content as binary instead of returning it to the browser.

The workspace tree in web/server/src/workspace-tree.ts limits recursion to 10 levels and 100 entries per directory. The launcher applies a separate containment check before serving files from dist/web/client. The root is fixed for the process: POST /api/workspace/root returns 405, while GET /api/workspace/browse returns 410 and directs callers to the project directory picker.

Project directory browsing

Project registration can use an opaque directory token. The ProjectRegistry in web/server/src/project-registry.ts validates and expires those tokens rather than asking the browser to infer a project path from an identifier. web/server/src/handlers/projects.ts owns project registration, rename, removal, browsing, and session fork requests.

This picker is intentionally different from workspace file reads: it may browse directories outside the current workspace in order to choose a new root. The selected root is then fixed for the process.

OpenUI artifact sandbox

OpenUI HTML is validated on the server by validateAndNormalizeOpenUIHtmlArtifact in web/protocol/src/openui-artifact.ts before it is stored by web/server/src/handlers/chat-openui-artifact.ts. The renderer in web/design/src/components/openui/html-artifact.tsx validates it again before rendering.

The validator caps documents at 1 MiB, injects the canonical CSP, and rejects forms, frames, external resources, inline event handlers, unsafe URL schemes, network APIs, downloads, and top-level navigation attributes. The renderer uses an iframe with sandbox="allow-scripts" and srcDoc, keeping the artifact in an opaque origin without app same-origin access.

Runtime and dependency supply chain

PRIME_AGENT_RUNTIME.json pins prime-agent 0.9.1 to a specific R2 tarball and SHA-256:

573bce0cd004fc62052e9a924089941b7f39266ab71e66a94c85a1f9d35835ba

scripts/check-prime-agent-runtime.mjs cross-checks that URL in web/server/package.json, packages/fleet-web/package.json, and pnpm-lock.yaml. With PRIME_RUNTIME_VERIFY_TARBALL=1, it downloads the archive and verifies both the manifest SHA-256 and lockfile SHA-512.

pnpm-workspace.yaml adds a seven-day release-age gate, a no-downgrade trust policy, restricted install scripts, and explicit security overrides. The known upstream extract-zip 2.0.1 advisory is documented in docs/guides/upstream-runtime.md because no patched version is available to override in Fleet.

Secrets handling

  • The release pipeline uses separate least-privilege CircleCI contexts for release-PR preparation and GitHub release assets. npm publication uses a short-lived CircleCI OIDC token with the npm registry audience, not a stored NPM_TOKEN.
  • Provider credentials are managed by Prime Agent's AuthStorage. Fleet's web/server/src/prime-provider-env-map.ts supplies provider labels and environment names; credentials are not part of browser responses.
  • redactSessionLabelSecrets in web/protocol/src/session-label.ts removes common API-key and token shapes before transcript-derived labels reach browser chrome.
  • Error responses pass through safeErrorMessage in web/server/src/wrap-api-handler.ts, which removes local filesystem paths from browser-facing messages.

Intentionally out of scope

  • Authentication and multi-user access: the product serves one local user.
  • TLS on loopback: the launcher is zero-config and local-only.
  • Sandboxed agent execution: worker and kernel isolation does not prevent filesystem, shell, or network access available to the user.

Report vulnerabilities through GitHub private vulnerability reporting as described in SECURITY.md; do not open a public issue for security reports.

The same trust boundaries are shown in the system architecture.

Clone this wiki locally