Repository navigation
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.
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
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.
| 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. |
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_agentwithallowed_toolsrestricts 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 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.
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.
Treat traces, artifacts, and replay snapshots as potentially sensitive:
-
activity_previewandactivity_textredact keys and labelled credentials and cap size. They are not a DLP boundary. -
trace_valueinsrc/fleet_rlm/observability.pyredacts credential,thought, andscratchpadkeys and bounds nodes, depth, and string length. Unlike the activity stream, it does not redact areasoningkey: 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 onin the TUI). It retains the bounded conversation, memories, dataset handle, Skill catalog and revision, non-secret runtime configuration, and workspace bytes up toMAX_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.
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.
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.
[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.
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,
.envvalues, 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.
| 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 |