Security
Prax aims at defense-in-depth across its trust boundaries. Read the linked pages for what each control actually covers today; the headline, as of 2026-09:
Synced from Prax at
35c345e5
View source ↗
Prax aims at defense-in-depth across its trust boundaries. Read the linked pages for what each control actually covers today; the headline, as of 2026-09:
- Inbound webhooks. Only the Twilio routes (
/sms,/transcribe,/respond,/reader,/read,/conference,/say,/play) carry signature validation (prax/blueprints/twilio_auth.py), and it is skipped whenTWILIO_AUTH_TOKENis unset. The TeamWork webhook (POST /teamwork/webhook) and the rest of/teamwork/*,/plugins/*and/api/users/*are checked byprax/blueprints/inbound_auth.pyonly whenPRAX_API_KEYis set: then every request must carry the same value inX-API-Key(constant-time compare, 401 otherwise), and TeamWork’s Prax proxy routers send it when TeamWork’s ownPRAX_API_KEYmatches. Default (unset, 2026-09): no inbound check —TEAMWORK_API_KEYis only sent outbound byprax/services/teamwork_service.py, and the network perimeter is the only control there (network-exposure.md). - Path containment on the workspace file operations (
safe_join); on the Library (prax/services/library_service.py, since 2026-09): every caller-suppliedspace/notebook/slug— wiki notes, raw, outputs, archive, and thelibrary_tasks/progress_servicefiles — must be a single path component (_require_slug) and the resolved path is checked against the library root (_assert_in_library) before any I/O; and onworkspace_root()’s legacy fallback inprax/services/workspace_service.py(the id isfullmatched to one component andsafe_joined, andensure_workspacerefuses via_check_workspace_root_containmentany root whose realpath is outsideWORKSPACE_DIRor nested in another workspace’s git repo). The review’s PoCs are pinned intests/test_library_path_containment.pyandtests/test_workspace_root_containment.py. - Sandbox access. There is no per-process sandbox auth key: local mode is
docker execvia the Docker socket; remote mode is the operator-setSANDBOX_DAEMON_TOKEN(bearer, optional mTLS). The sandbox’s own ports are unauthenticated and published on loopback only. - A multi-layer plugin security model — with the limits stated in plugin-trust.md.
Contents
- Plugin Trust & Isolation — Trust tiers, subprocess isolation, capabilities proxy, lifecycle audit
- Tool Risk Classification — Risk levels, governance layer, supply chain hardening
- Configuration — Environment variables, .env setup, all configuration options
- Network Exposure & Binding — Why Prax/TeamWork bind loopback by default, and how to serve on
0.0.0.0safely behind an authenticating proxy (Tailscale, IAP, Cloudflare Access, oauth2-proxy) - Deployment Topology — isolate, sandbox, guardrail — the endorsed production shape: Prax, the secrets-proxy, the sandbox (and optionally TeamWork) as separate containers on one shared network — they can talk but can’t read each other’s filesystem/env/keys. Covers the two proxy modes (reverse = model keys; forward MITM = ~all egress), how to wire forward mode, the 🔒 lock-the-proxy-down-hard checklist, and the keys-in-Prax opt-out (at your own risk).
- Credential Registry — the single source of truth (Prax ⇄ proxy, no drift) — every credential Prax supports, classified by whether/how the proxy can hold it (model / forward / local). Backed by
prax/services/credential_registry.py+ a CI drift-guard test so a new key can’t be added without classifying it. Start here to see what’s proxyable and what stays in Prax. - The Secrets Proxy — running a KEYLESS Prax — Run Prax with no real API keys in its process: a small separate proxy holds the keys, injects them into model calls, and streams responses back — so a compromised/injected Prax has nothing to steal (the infra-level “make the secret unreachable” wall). Tier 1 (OpenAI + Anthropic, base-URL reverse proxy) is built as a separate, isolated service (
praxagent/prax-secrets-proxy) — real isolation is process/filesystem separation, not a second env file Prax canopen(); it’s opt-in and default Prax is unchanged: allowlist by construction, streaming, an audit log that never logs the key/body. Honest limits (stops theft, not abuse; the proxy becomes the trusted component; onlybuild_llm()readsOPENAI_BASE_URL/ANTHROPIC_BASE_URL— the direct OpenAI clients elsewhere do not) + how to run it. The Tier-2 forward (MITM) proxy for REST egress is shipped as an opt-in profile — see deployment-topology.md. - Git repos attached to a space — Attaching repositories to a Library space: one ed25519 deploy key per repo (an account SSH key cannot be scoped — it reaches everything the account can),
IdentitiesOnly=yesso ssh cannot silently widen that, write off until a human toggles it, per repository, and checkouts isolated under their own space with a name check plus a resolved-path check. Keys live outside the workspace andlibrary/spaces/*/repos/is gitignored, because the workspace commits everything not ignored. - Provenance Laundering — Found 2026-08-07, FIXED the same day (kept as the worked example of “provenance follows the content, not the transport”). Provenance used to be decided by which TOOL returned content:
fetch_url_contentwasuntrusted_source, but the same page auto-captured intolibrary/raw/and read back viaworkspace_readwasprivate_data, which could ARM the lethal-trifecta guard’s private leg with attacker text. The fix stampsprovenance: untrusted-externalinraw_capture’s front-matter and taints on the marker, whatever tool returned it. Remaining limits (in the doc): the marker check lives inside theUntrustedContentTaintmiddleware and so is only active whenAGENT_MIDDLEWARE_ENABLEDis on (default on since 2026-08-07);library_raw_promotecopies a capture into a note without the marker, so the laundering re-opens one hop later; user-PASTED content is never tainted; the → sink leg was never demonstrated. - The Sandbox Execution Boundary — Where code-exec tools (
run_python,data_query,sandbox_shell,lean_check) actually run and what they can reach: those tools dispatch throughprax_sandbox_clientinto the container and never fall back to the host. Known gaps stated there (2026-09):desktop_*, plugincaps.run_commandand the mermaid validator go throughprax/utils/shell.py, which runs on the Prax host unlessRUNNING_IN_DOCKER=true(i.e. on every native install); prax’s owndocker-compose.yml/.lite.ymlnow match prax-sandbox’s compose (no keys passed to the sandbox, only/workspacemounted, the imageHEALTHCHECKrelied on — pinned bytests/test_compose_sandbox_service.py; thedocker-compose.dev.ymloverlay still bind-mounts./prax,./app.py,./config.py,./testsrw at/source/*and is grandfathered there as a ratchet);/workspace/plugins/is auto-imported in-process by the loader;delegate_sandboxis invisible to the trifecta guard. The coding-agent CLIs were removed from the image (prax-sandbox #4) and the coding-session tools from Prax (#142). Residual: egress is unrestricted; ranked mitigations (tracked). Why isolation, not command-filtering; the rules for adding a code-exec tool.