Skip to content

security

Zachary BENSALEM edited this page Oct 20, 2026 · 6 revisions

Security

Active contributors: Zachary Bensalem

Fleet is a single-user development runtime. Its security model is a loopback-only process with server-side credentials, not a multi-tenant service. This page separates what the code enforces from what an operator must arrange.

Trust boundaries

graph TD
    Caller["HTTP caller<br/>no authentication"] --> API["Fleet API<br/>loopback by default"]
    API --> Host["Server process<br/>credentials, Store, MLflow"]
    Host --> DB["PostgreSQL"]
    Host --> Daytona["Daytona control plane"]
    Daytona --> Sandbox["Sandbox<br/>generated Python only here"]
    Host --> Gateway["Databricks Gateway<br/>provider models"]
    Host --> MLflow["MLflow<br/>local or Databricks"]
    subgraph Host trust zone
        API
        Host
    end
Loading

The API has no caller authentication and no authorization model. It binds to 127.0.0.1 by default. A non-loopback bind requires FLEET_ALLOW_NON_LOOPBACK_BIND=true, and that switch only bypasses the bind check in src/fleet_rlm/cli.py. It does not add TLS, tokens, or access control. Anyone who can reach the port can run Turns, read and write workspace files, change models, and read history and memory.

The SECURITY.md policy states the API has deterministic local scope and that the settings API remains loopback-only. Treat any non-loopback deployment as requiring a separately secured boundary.

Enforced by code

Control Where What it does
Loopback default and explicit opt-in src/fleet_rlm/cli.py fleet serve and fleet cli error unless the host is loopback or FLEET_ALLOW_NON_LOOPBACK_BIND=true.
Secrets excluded from configuration responses src/fleet_rlm/config.py configuration() dumps with _SECRET_FIELDS excluded; the Session config route returns values plus provenance and digest.
Credentials environment-only src/fleet_rlm/config.py Secret fields are SecretStr and read from environment or .env; they are never written to TOML or returned by the API.
Generated Python runs in Daytona src/fleet_rlm/sandbox/ The interpreter sends code to a Daytona sandbox over run_code; the host does not exec model code. Host-mediated tools execute on the server and are reached through the bridge.
Session ID validation src/fleet_rlm/workspace.py IDs must match ^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$, must not be . or .., and must not contain ... Used before an ID becomes a Daytona Volume subpath.
Workspace path validation src/fleet_rlm/workspace.py Rejects empty paths, backslashes, NUL and control characters, paths over 1024 characters, and traversal. First segment must be one of files, artifacts, memory, skills, buffers, sessions.
Symbolic-link rejection and escape check src/fleet_rlm/workspace.py and src/fleet_rlm/sandbox/manager.py The sandbox-side Workspace.resolve refuses symlinks at each component and checks commonpath against /workspace. Host listing and delete skip or refuse symlinks.
25 MB file and upload caps src/fleet_rlm/api/routes/workspace.py Reads, writes, stat, and attachments are capped; uploads use min(FLEET_MAX_UPLOAD_BYTES, 25 MB) and return 413 above the limit.
Request bodies reject unknown fields src/fleet_rlm/api/schemas.py All request models forbid extra fields, so an unsupported option cannot be silently ignored.
Sanitized error responses src/fleet_rlm/api/deps.py Internal failures become fixed 503 messages, logged by type only. Turn failures return a request ID unless the error is an idempotency conflict.
Advisory Session lease src/fleet_rlm/session.py, src/fleet_rlm/store/db.py A PostgreSQL advisory lock ensures one worker owns a Session runtime; a worker that lost its lease fails closed with 503.
Migration DSN separation src/fleet_rlm/store/db.py, src/fleet_rlm/config.py An optional DDL DSN is validated to point at the same host and uses a one-connection pool that closes after startup.
Bounded, redacted activity previews src/fleet_rlm/observability.py activity_text redacts labelled credentials and control characters; activity_preview bounds depth, collection sizes, and total length; keys matching credential or reasoning patterns become [redacted].
Predictor outputs not streamed src/fleet_rlm/api/events.py Module events report status only; bridged predictor calls report [predictor payload omitted].
SQL parameterization src/fleet_rlm/store/db.py Statements are centralized and use parameters; the schema identifier is regex-validated before interpolation.
Dependency floors pyproject.toml override-dependencies forces patched transitive versions even where upstream metadata allows older ones.

Operational guidance

These are operational decisions. The repository does not implement them:

  • Caller authentication, authorization, TLS termination, rate limiting, or an audit trail for API requests.
  • A network sandbox for delegation. spawn_agent with allowed_tools restricts which bridged tools a child may call; it is not a network policy, and it does not change what the Daytona sandbox or the provider can reach. allowed_tools=[] removes the child's ability to delegate further, but it is not a Python or network sandbox policy.
  • Provider-side isolation. Daytona organization policy, snapshot contents, volume permissions, and Databricks workspace permissions are external. Local tests and docs do not prove their configuration.
  • Data-loss prevention. Redaction recognizes labelled credentials and known sensitive keys. It cannot find an unlabelled secret in arbitrary prose, code, or model output.

Credentials and process configuration

Credentials come from environment variables or a local .env and are held in SecretStr fields. They are passed to Daytona and Databricks through SDK clients and to MLflow through its expected environment variables. They are never included in /health, /config, event payloads, or trace attributes added by Fleet.

/health exposes the MLflow destination (mode, tracking URI, non-secret workspace host, experiment names, trace catalog/schema/prefix). A Databricks tracking workspace URL is rejected if it contains userinfo, a query, or a fragment. This identity exists so clients can refuse to reuse a server pointed at a different destination.

.env files must never be committed. .gitignore covers them; the packaging configuration ships only src/fleet_rlm in the wheel.

The Session configuration route (GET /sessions/{session_id}/config) returns the same non-secret values, their source, and a digest. Turn traces record the digest and the non-secret root and sub model settings, not the full values.

Generated code and host tools

Model-generated Python is intended to execute in Daytona, never in the server process. The host owns tool dispatch: sandbox code emits a locked marker line, the host runs the tool, and the result returns through fs.upload_file. This keeps credentials and host capabilities off the sandbox.

The delegation tool allow-list is a capability restriction inside that bridge. A child agent receives only selected files/, skills/, and artifacts/ inputs, and its tools are limited to the ones passed to spawn_agent. It does not create a separate network boundary, and it does not prevent remote code from using whatever network access the Daytona sandbox and provider policy allow.

Sandbox transport failure makes the outcome unknown. Fleet stops the root sandbox and does not replay code. That is a correctness property, and also limits accidental duplicate side effects.

Sensitive data in traces, artifacts, and replays

Treat traces, artifacts, and replay snapshots as potentially sensitive:

  • activity_preview and activity_text redact keys and labelled credentials and cap size. They are not a DLP boundary.
  • trace_value in src/fleet_rlm/observability.py redacts credential, thought, and scratchpad keys and bounds nodes, depth, and string length. Unlike the activity stream, it does not redact a reasoning key: reasoning text may be retained in traces for debugging. Provider-side trace storage is therefore more sensitive than the SSE stream.
  • Artifacts are workspace files. They may contain model-generated or user-supplied data and are downloadable through the API.
  • Replay capture is opt-in per Turn (record_replay, or /replay on in the TUI). It retains the bounded conversation, memories, dataset handle, Skill catalog and revision, non-secret runtime configuration, and workspace bytes up to MAX_REPLAY_FILE_BYTES (8,000,000). Snapshots are stored in PostgreSQL after a successful Turn. They become eligible for GEPA only after explicit operator approval with gold expectations. Use this only for data approved for that purpose.

Path handling details

validate_session_id and _workspace_relative_path in src/fleet_rlm/workspace.py run on the host before a path reaches Daytona. The remote Workspace.resolve repeats the checks in sandbox code and additionally refuses any symlink component, a resolved target outside /workspace, and a non-regular existing target. Workspace listing uses os.walk with followlinks=False and skips symlinked directories and files.

Note that _workspace_relative_path accepts buffers and sessions as roots in addition to the four named in its error message. The allowance is intentional for internal paths; the error text is simply narrower than the check.

Attachment filenames are reduced to a basename, and empty names, ., .., and NUL bytes are rejected before storage.

Database separation

FLEET_DATABASE_URL is the runtime DSN. FLEET_DATABASE_MIGRATION_URL is an optional DDL DSN. Validation requires both to use the same host and requires OAuth Lakebase DSNs to use sslmode=require with host, user, and sslmode=require present. When the DSNs differ, migrations run on a dedicated single-connection pool that is closed immediately after startup, so the runtime role does not need DDL rights.

Migrations run under pg_advisory_xact_lock, and Session ownership uses pg_try_advisory_lock. That serializes concurrent starts and prevents two workers from running one Session, but it is not a substitute for database authentication or network isolation.

Dependency security overrides

[tool.uv].override-dependencies in pyproject.toml pins floors for transitive packages that upstream metadata still allows at vulnerable versions:

Package Floor Stated reason
aiohttp >=3.14.4 LiteLLM 1.83.x pins older vulnerable releases.
cryptography >=50.0.0 PKCS#7 Bleichenbacher oracle in 44.x to 49.x.
idna >=3.16 idna.encode() bypass advisory.
mako >=1.3.12 TemplateLookup traversal with Windows backslash URIs.
gitpython >=3.1.58 Unsafe git option forwarding and path access.
starlette >=1.3.1 Form-limit DoS and request URL hostname parsing advisories.
litellm >=1.87.0 CVE remediation family; installed only as a DSPy transitive dependency.
urllib3 >=2.7.0 Redirect header leak and streaming decompression issues.
multidict >=6.9.1,<7 Memory leak in attacker-controlled item-view operations.
werkzeug >=3.1.9 Windows path traversal in safe_join.
sqlparse >=0.6.0 SQL parsing DoS and code-generation flaws.

make check-security runs pip-audit against the locked requirements. The current suppression is PYSEC-2026-2447; adding a suppression should be treated as a reviewed exception, not a routine fix.

Reporting a vulnerability

SECURITY.md provides the process:

  • Email contact@qredence.ai; do not open a public issue.
  • Include a description, reproduction steps, impact, and optional fix.
  • Acknowledge within 48 hours; initial assessment target is 5 business days.
  • Report sanitized steps only. Never include credentials, .env values, private trace content, or raw provider errors.

The supported-version table in SECURITY.md currently lists 0.7.x as the maintenance line while pyproject.toml declares version 0.8.0. Treat the policy text as the reporting process and the package metadata as the version of a checkout; the table has not been updated with the current release.

Key source files

File Role
SECURITY.md Supported versions and vulnerability reporting
src/fleet_rlm/cli.py Loopback enforcement for serve and cli
src/fleet_rlm/config.py Secret fields, configuration dump exclusion, DSN and bind validation
src/fleet_rlm/workspace.py Session ID and workspace path validation, sandbox-side resolve
src/fleet_rlm/sandbox/manager.py Host workspace operations, symlink refusal, size caps
src/fleet_rlm/observability.py activity_text, activity_preview, trace_value redaction
src/fleet_rlm/replay.py Replay snapshot size limits and schema
src/fleet_rlm/store/db.py Advisory locks, migration lock, DSN pools
pyproject.toml Dependency floors and packaging contents

Related

Clone this wiki locally