agent6 treats the LLM as untrusted. Concrete claims below, layer by layer, each with what it means and where it stops.
Open a GitHub issue prefixed [security]. Include: agent6 version
(agent6 --version), kernel + distro (uname -a, /etc/os-release),
agent6 check sandbox output, and a minimal repro (ideally a failing test under
tests/security/).
Adversary: a fully malicious worker model, or an honest model that has been prompt-injected by a file in the workspace.
We assume the adversary controls:
- the text of every LLM response
- the choice of tool calls and their arguments (within the published JSON schema)
- the content of any file the agent reads during the run
We do NOT assume the adversary controls:
- the kernel
- the agent6 binary
- the provider endpoints
Under that adversary, agent6 aims to hold:
- No writes outside the workspace.
- No reads outside the workspace and a read-only system set.
- The system set (
/usr /bin /sbin /lib /lib64 /etc /dev /proc /tmp) exists so installed toolchains resolve. hardenedalso exposes$HOME+/run(Landlock can't carve them out);sandbox.extra_read_pathsadds more.
- The system set (
- No agent egress except the configured providers (+
sandbox.allow_urls), undersandbox.agent_network = "providers"(default; §1b).- Jailed commands are governed separately by
sandbox.tool_network(defaultblock; §8).
- Jailed commands are governed separately by
- agent6's own git never pushes,
--forces, rewrites history, orreset --hards (§5).- This does NOT bind a
gitthe model runs viarun_command; that path is bounded by the sandbox (protect_gitread-only-binds.gitonstrict; push needs egress).
- This does NOT bind a
- No persistence after the run: no daemon, cron, or
.bashrcwrite.- Children can only write inside the jail's mount namespace.
Applied at run/resume start, before any network object. Restricts the Python
process irrevocably, inherited by every child:
| Landlock rule | Allowed |
|---|---|
| FS read+exec | cwd, $HOME, /usr, /etc, /tmp, /bin /sbin /lib /lib64 /dev, /run + /proc when present |
| FS write | cwd, /tmp, the /dev char devices, /proc when present |
| TCP connect (kernel ≥ 6.7) | the ports of configured providers (each base_url port, default 443) |
strictskips this layer.- Its per-command namespaces + broker (§1b) are stronger, and this would
break the jail's
pivot_root/mountat Landlock ABI ≥ 7.
- Its per-command namespaces + broker (§1b) are stronger, and this would
break the jail's
- The read+exec set mirrors the jail child's roots.
- The launcher opens each from here to grant the child, so a missing one
(e.g.
/devon merged-/usr) makes the child's execve fail EACCES.
- The launcher opens each from here to grant the child, so a missing one
(e.g.
- The network rule filters by port, not host.
- It blocks other ports but can't pin egress to a host; use
agent_network = "providers"(§1b) for that.
- It blocks other ports but can't pin egress to a host; use
- Kernels < 6.7 get FS-only Landlock, with a warning.
- Don't run there if the host UID can read exfiltratable credentials.
- The agent's only path off-host is one hard-wired unix socket per allowed
provider; the LLM never chooses a destination.
- Setup,
strict-only (needs user namespaces): in the host netns and single-threaded, the agent binds oneAF_UNIXsocket per providerhost:portand forks a broker that stays in the host netns. - The agent then
unshare(CLONE_NEWUSER|CLONE_NEWNET)s into an empty netns (loopback only). Off-host is only those sockets. - Per connection the broker dials that socket's fixed
host:port(resolved per-connect, robust to CDN IP churn). TLS is end-to-end, so it sees only ciphertext. - The allowed set derives uniformly from each provider's effective
base_urlhost (everyapi_formatanddeploymentcarries the dialled host there; the deployment profile only appends path/model), unioned withsandbox.allow_urls(app/egress.py).
- Setup,
- Fail-closed: the netns is the boundary, not a filter.
- A missing route is no connectivity, never a silent leak; the allow-list is fixed at bind time, so the agent can't widen it.
- Hosts that only support
hardenedrefuse rather than run unconfined.
localpins to loopback providers;openskips the broker.localrefuses a non-local provider.machine runapplies the same setup peragent-state subprocess.
- A detached resume is spawned from the host, not the empty netns.
- Host spawner (
sandbox/host_spawn.py): a helper forked beside the broker before isolation spawnsagent6 resume <run_id>from the host, on request over a close-on-exec pipe, exec'ing only the agent6 binary captured at fork. Neither argv nor pipe is reachable from LLM output. - Inherited-namespace refusal:
AGENT6_NETNS_ISOLATED=1makes a child that sees it refuse with the cause, not burn provider retries.
- Host spawner (
sandbox.allow_urlswidens the allow-list; only the operator can.- Each entry (
host/host:port/ URL) gets its own broker socket, same properties. Default empty, validated at load, honored only underproviders, agent path only. Last-overlay-wins, soconfig showis authoritative.
- Each entry (
- MCP servers get no outbound network under
providers(a deliberate limit; localAF_UNIXhelpers are unaffected).
apply_edit is in-process; every run_verify_command/run_command runs in
agent6-jail. Under strict it:
- Forks a new user/mount/PID/IPC/UTS/net namespace.
pivot_roots into a minimal bind-mount rootfs on a fresh tmpfs: cwd + private/tmpwritable, system paths read-only,extra_read_pathsgrants read+exec at their real paths (extra_rwgrants writable at theirs). Operator-tool dirs join as read+exectool_pathsmounts (standard bin dirs that exist, the real dirs their symlinks resolve to, uv-managed CPythons), derived bysandbox.jail.operator_tool_paths; run_command/verify jails and machine tool jails share that one computation, andmachine checkprobes the same PATH.- Exposes curated
/dev(null zero urandom random full); omits/dev/tty(it would let a child write escape sequences to the parent's terminal). - Mounts a fresh private
/proc; if that fails, leaves/procempty (never the host's, which would leak process info). - Applies Landlock FS rules (net confinement is the namespace).
- Installs a seccomp deny-list: dangerous syscalls (ptrace, mount, setns,
unshare, kexec, bpf, perf, keyctl, module loading, reboot, clock-set, …)
return
EPERM, the rest allowed. - Sets
NO_NEW_PRIVS, so the kernel ignores setuid bits (sudo/setuid can't escalate). execves the binary and SIGKILLs the group at the wall-clock timeout.
Notes:
- The memory cap is operational, not a threat-model control. A per-process
RLIMIT_DATA([sandbox].memory_limit_mb, default 4096,0off; notRLIMIT_AS, so V8/JVM/ASAN keep working) stops one runaway allocation, nothing more. - No
capset.strictmaps namespaced-root to your uid;hardenedkeeps the caller's caps (none for a normal user). hardeneddrops the namespaces + rootfs; Landlock, seccomp,NO_NEW_PRIVS, and the timeout remain.- The policy arrives as JSON on stdin from
run_in_jail; the Rust side validates it against a strict schema and refuses unknown fields.
The jail is one-way: the agent works within the environment you give it and can't expand it.
sudocan't escalate, even passwordless.NO_NEW_PRIVSvoids setuid, so jailedsudofails regardless of anyNOPASSWDrule.- Package installs are impossible.
apt/dnf/apkneed all three of root (blocked), mirror network (provider-only egress), and/usr//varwrites (denied). - Compiling and running host-installed toolchains works.
run_verify_commandand (whenrun_commandspermits)run_commandrun jailed; they just can't install new tools, and a networked build step needstool_networkloosened.
- Provisioning is operator-first. Install toolchains, venvs, and deps
yourself before/outside agent6; widen access via config, never sudo
(
extra_read_paths,tool_network,[providers.*].base_url, all inconfig show). - Running agent6 as root (
--allow-root/AGENT6_ALLOW_ROOT=1) weakens the boundary.strictmaps inside-root to real root, so jailed children run as real root under only Landlock + seccomp +NO_NEW_PRIVS.- Still no writes outside the workspace and no egress beyond providers, but
the allowed reads now include root-only files (
/etc/shadowunderhardened;strict's rootfs hides them). Run as your normal user.
Everything the model can influence runs through run_in_jail (§2). A fixed set
of modules also shells out directly with subprocess.run/Popen; each has
fixed argv depending only on operator input, never LLM output.
tests/security/test_subprocess_allowlist.py pins the file list; audit with
rg 'subprocess\.(run|Popen)' src/agent6/.
git_ops.py: agent6's own git operations (§5).sandbox/detect.py: probes the host's sandboxing capabilities.sandbox/jail.py: the jail launcher itself.sandbox/host_spawn.py: the pre-forked detach helper; spawnsagent6 resume <run_id>in the host namespaces, argv built from the agent6 exe captured at fork time, requests only from the trusted parent over a close-on-exec pipe.tools/lsp.py: thetylanguage server, exe resolved from PATH.tools/mcp_client.py: operator-configured[mcp.servers.*]server commands.providers/token_command.py: the operator-configured[providers.*].token_commandthat mints a provider bearer; argv from config.ui/spawn.py: the shared front-end spawn helper; spawns the agent6 CLI detached for run/machine launches and capturesruns merge/prune/config set; argv is the agent6 exe plus operator-chosen args.ui/notify.py: firesnotify-sendwith fixed argv (exe,--end-of-options, two positional data args, no shell) for the device-present machine notification; the message is inert data, never a command or an option.ui/cli/helpers:$EDITORfor plan and steer editing.git diff/logfor the review subcommand and theruns/askdiff views; argv from the run manifest the CLI wrote outside the jail.rgfor history search.- The fixed-argv
python -m agent6.ui.tuico-process behindrun --tui. ui/cli/system_cmds.py:cp/rm/apparmor_parservia sudo with fixed argv foragent6 system apparmor(operator host setup).
app/helpers:app/finalize.py: the operator[notify].on_completehook fired at run end; argv from config.app/machine/_scriptcheck.py: ruff/ty with fixed argv to statically read generated scripts, which only ever execute viarun_in_jail.- The
machine runsupervisor (app/machine_agent.py): spawns each agent state as a fixed-argvpython -m agent6.ui.cli.machine_agentsubprocess whose request travels in a temp file, never on argv; its operator[machine.notify].on_eventhook (argv from config, fired fromapp/machine/_preflight.py) runs on the host withAGENT6_MACHINE_*env, mirroring[notify].on_complete. ui/cli/skills_cmds.py:git clone --depth 1 -- <url>with fixed argv foragent6 skills install; the URL is operator-supplied on the CLI and nothing fetched is ever executed.
ui/tui/clipboard.py: fixed-argvtmux set-buffer -wwith the copied transcript text as one inert data argument.ui/tui/conversation.py: the operator's$PAGER, argv from the environment, transcript text on stdin.
You set sandbox.profile; it resolves against the host to the effective
profile. No silent downgrade: a request the host can't meet is refused, and
auto reaches none only when the host offers no confinement mechanism at
all (non-Linux, or a Linux kernel with neither userns nor Landlock) -- always
loudly. Capabilities are probed (unshare for userns, the Landlock ABI
syscall for hardened), never guessed from the kernel version.
sandbox.profile |
Host | Effective |
|---|---|---|
auto (default) |
Linux + user namespaces | strict |
auto |
Linux, no userns, Landlock | hardened |
auto |
Linux, no userns, no Landlock | none (loud warning) |
auto |
non-Linux | none |
strict |
Linux + user namespaces | strict |
strict |
else | ⛔ refuse |
hardened |
Linux + Landlock | hardened |
hardened |
else | ⛔ refuse (Landlock is hardened's only FS boundary) |
none (opt-out) |
any | none (the environment is the boundary) |
-
strict: full namespaces +
pivot_root+ Landlock + seccomp +NO_NEW_PRIVS. -
hardened: Landlock + seccomp +
NO_NEW_PRIVS, no namespaces.- Works in default-seccomp Docker (the container blocks the inner
clone(CLONE_NEW*)); the container is the blast radius.
- Works in default-seccomp Docker (the container blocks the inner
-
none: unsandboxed, always with a loud warning.
-
Unsandboxing is explicit and self-authorizing.
profile = "none",--dangerously-disable-sandbox, orAGENT6_DANGEROUSLY_DISABLE_SANDBOX=1. The LLM can't reach argv/env, so setting one is the consent. -
Sandbox-off + auto-approved
run_commandadds a one-time gate. For that combination only:Continue? [y/N]interactively, a warning in CI/machine run. -
CI should set
strictto fail loud if the sandbox is weaker than expected.
- The LLM only sees the fixed set in
src/agent6/tools/schema.py.- Structured edits, read-only navigation, fixed-argv verify/metric commands,
finish_run,ask_user, a curator task notepad, a cross-run memory notepad, and capability-gatedrun_command. - No
shell, nowrite_file(writes go throughapply_edit, which refuses paths outside cwd), noweb_fetch, noeval. - Adding a tool needs a security review note (AGENTS.md).
- Structured edits, read-only navigation, fixed-argv verify/metric commands,
- The memory notepad is a prompt-injection persistence channel.
add_memory/invalidate_memory(run mode) write fixed markdown under<state-dir>/<repo-id>/memories/(code picks the path; the model supplies only a schema-validated scope + text); active notes join later runs' system prompt on the same repo.- Mitigated: notes are inert data (never executed), the injected block is
size-capped and framed as untrusted, and the store is operator-auditable
(
agent6 memory list --all,agent6 memory invalidatekeeps the trail). - It weakens no boundary here: sandbox/egress/git policy come from config, not prompt content.
- agent6's own git refuses the destructive ops, by construction.
git_ops.pyis the only module through which agent6 invokes git; it wraps the safe ops (status, add, commit, diff, branch, checkout) and refusespush,reset --hard,commit --amend,rebase,filter-branch/filter-repo,branch -D/--force, and any--force/-fon a destructive verb.git.allow_push/allow_force/allow_history_rewriteexist for forward-compat but are ignored.
- A
gitthe model runs viarun_commandis bounded by the sandbox, not this list.- On
strict,protect_gitread-only-binds.git, so a rewrite fails andpushhas no egress. Onhardened,.gitis writable, so the container is the boundary.
- On
- git_ops neutralizes repo-controlled host code in a poisoned
.git/config.core.fsmonitoranddiff.externalare always off;.git/hooks/*run only undergit.run_repo_hooks = true(default false;core.hooksPathpoints away so a hook can't fire on agent6's auto-commit).- On
strictthis complementsprotect_git's RO.git. Onhardenedthe cwd is blanket RW (an RO.gitwould break cargo/pytest creatingtarget//.pytest_cache/), so.gitis writable there; acceptable, gated byrun_commands, recoverable (branch-per-run, commits via git_ops), container is the blast radius.
- The edit tools refuse writes into an in-repo venv or
site-packages.- A
pyvenv.cfgdir orsite-packagesancestor: a run rewriting an editable-install.pthwould silently corrupt the venv, invisible inruns diff/merge since venvs are gitignored. Reads stay allowed. - Related limit: an editable install records the host path in its
.pth, absent under the jail's/workspace, so averify_commandimporting the project canModuleNotFoundError. Fix with pytestpythonpath, aconftest.py, or a non-editable install.
- A
- Provider keys are
0600, owner-only, and never leave agent6's process.- In
$XDG_CONFIG_HOME/agent6/secrets.toml(refused if group/other-readable or foreign-owned, like an SSH key), or from[providers.<name>].api_key_env(env wins). Never in transcripts, never inconfig show(redacted), never mounted into the jail.
- In
agent6 connectnever executes remote input.- It only prompts locally (
getpass) and writes config/secrets. It makes one read-onlyGETto the provider's key endpoint to confirm auth (status only;--no-verifyto skip). - During a run agent6 opens no listening socket (MCP is stdio, the broker is a
private unix socket); the only accept-side socket is opt-in
agent6 web(§7).
- It only prompts locally (
- Running as root is refused without an explicit opt-in.
--allow-root/AGENT6_ALLOW_ROOT=1(+ a banner). Undersudo, agent6 reads the real user's config/secrets (fromSUDO_UID/SUDO_USER), not root's, and chowns state-dir writes back. It doesn't drop privileges in-process: the jail, not the uid, is the boundary.
- An in-process
GraphCuratorowns the task graph.- It validates every mutation against a pydantic schema before writing, and holds a per-mutation flock on the run dir. A write-path fault after the in-memory update reloads from disk before surfacing, so a later read never observes a node that was never persisted.
- The run directory is safe because of its location, not any single writer.
- Per-repo state lives at
$XDG_STATE_HOME/agent6/<repo-id>/(override with[agent6].state_dir), outside the cwd jailed commands run on.
- Per-repo state lives at
agent6 run --parallel, agent6 runs compare, and a live run's /parallel
steer directive (§ architecture.md)
each spawn subordinate work. Nothing here loosens the sandbox:
- Every lane is an ordinary run. A lane is a plain detached
agent6 runon its own clone: its own jail persandbox.profile, its own egress broker (§1b), its ownrun_commandspolicy. Nothing shares a sandbox or a broker socket across lanes or with the parent run. - Recursion is blocked by an env guard, not policy. Every spawned lane
carries
AGENT6_SUBRUN=1; both the--parallelflag and the coordinator'slane_spawnerwiring refuse when it is set, so a lane can never itself fan out or dispatch (depth 1 by construction). - A lane's config carries key references, never secret values. The
orchestrator writes each lane a
--configfile viamaterialize(), a dump of the resolvedConfigmodel (providerbase_url,api_key_envnames, etc.) --Confignever holds a raw API key. The lane's own process reads the samesecrets.toml/ provider env var as any other run, same user, same host. - No new subprocess call site.
workflows/subrun.py,app/parallel.py, andui/cli/parallel.pyadd no directsubprocessuse; lane git plumbing (clone/fetch/merge) goes throughgit_ops.pyand lane spawning goes throughui/spawn.py, both already on the §2b allowlist. Thetests/security/test_subprocess_allowlist.pypin needed no new entry. - Dirty-tree refusal, not auto-stash. A lane clones committed HEAD only,
so
--parallelrefuses a dirty origin undergit.require_clean_worktree(the same policy and message shapeagent6 runuses) rather than carrying uncommitted work into a lane it cannot see.
- The loop opens no accept-side socket.
- Only outbound HTTPS to the provider; the task graph is an in-process curator, no socket.
agent6 webis the one accept-side socket, and only when you start it.- Loopback (
127.0.0.1) by default, no app auth (run behindtailscale serve; the tailnet identity is the access control, see the web UI). - A non-loopback bind is refused unless opted in:
[web].hostneeds[web].allow_non_loopback = true,--hostneeds--allow-non-loopback.
- Loopback (
- The server renders folded state and drives typed contracts only; it executes
nothing.
- New-work spawns fixed argv with the task behind
--; machine-run is allow-listed to authored files; answers write only the addressed run's answer files (run id, answer id, machine target state dir each validated to one path component); merge/prune/config-set are fixed agent6 subcommands.
- New-work spawns fixed argv with the task behind
- State-changing POSTs carry a CSRF guard.
- Body must be
Content-Type: application/json(a cross-sitefetchwith it triggers a preflight the server never answers) and anyOriginmust matchHost. Holds on loopback and behindtailscale serve. - It does NOT cover DNS rebinding (that needs a Host allow-list incompatible with the tailnet name).
- Body must be
- Request framing is bounded. 1 MiB body cap (413), chunked refused (411), any unread-body refusal closes the connection.
- The machine write surface (
POST /api/machine/<name>/{poke,answer,approve,steer}) uses the same guards.pokewrites only the instance signal file (inert JSON the nexttoolreads); the others write only the current agent state's per-state dir. PWA assets are static; the service worker is a no-op passthrough (no Web Push/VAPID). - No telemetry, no auto-update, no remote control plane.
machine runis a supervisor in the host netns that makes no network calls.- Each
agentstate confines its own egress peragent_network(the broker, §1b); eachtoolstate is jailed, so a per-toolallow_networksets its netns independently. - This lets a machine keep agents on the provider API while one reviewed,
fixed-argv
toolreaches the network: unlikerun_command(LLM-chosen argv), atoolisn't a free exfil channel.
- Each
Egress = agent_network × tool_network × per-tool allow_network; the
effective profile decides what's enforceable. "offline" = no egress.
Agent egress by agent_network:
agent_network |
strict |
hardened |
none |
|---|---|---|---|
providers (def) |
providers + allow_urls, broker-pinned |
provider ports only (Landlock) | unconfined ⚠ |
local |
loopback providers only, broker-pinned | ⛔ refuse | unconfined ⚠ |
open |
unconfined | unconfined | unconfined ⚠ |
Jailed-command egress (run_command, machine tool) by tool_network
(cells = strict):
| jailed command | block (def) |
only_explicit_states |
allow |
|---|---|---|---|
run_command |
offline | offline | host network |
tool, allow_network auto(def)/block |
offline | offline | offline |
tool, allow_network = allow |
⛔ refuse | host network | host network |
Refusals (fail-closed):
| Configuration | When |
|---|---|
tool_network = allow without agent_network = open |
config load ¹ |
a tool sets allow_network = allow under tool_network = block |
machine start |
agent_network = local or tool_network = only_explicit_states |
run start, hardened ² |
a machine with tool states, or a tool with allow_network = block, under tool_network = block |
machine start, hardened ² |
- ⚠
none(non-Linux) is unsandboxed: nothing enforced, nothing refused, loud warning. - ¹
run_commandruns in the agent process, so it can't reach the network while the agent is confined. - ² per-command isolation needs a netns, so it's
strict-only; onhardeneda jailed child inherits the agent's Landlock and the cases needing real isolation are refused.
More fail-closed properties:
- Operator-gated policy.
agent_network/tool_networkare read only from the operator's config; a machine's[config]overlay is rejected at load if it declares[providers.*],[sandbox.*],[profiles.*], orgit.run_repo_hooks.- Otherwise a profile preset or a host
[machine.notify]argv could splice into the resolved config, andrun_repo_hookswould run repo.git/hookson the host on amode="run"commit. Atoolonly declaresallow_network; honoringallowis the operator's call, and every conflict is refused at startup naming the state.
- Otherwise a profile preset or a host
- Bundle confinement. Scripts live in a reviewed
scripts/beside the.asm.toml;machine checkverifies every entry and static reference resolves inside the bundle (escaping symlinks rejected).- Scripts are operator-authored and committed, never fetched/generated at run
time, and the
.asm.toml+scripts/are RO in every jail during a run, so a state can't rewrite its own logic or add anallow_networkflag.
- Scripts are operator-authored and committed, never fetched/generated at run
time, and the
- Notifications don't widen the agent's surface. Front-ends render
machine.notifyas an overlay, andattach/TUI callnotify-sendwith a FIXED argv (no shell), so a model message is inert data.- The out-of-band hook
[machine.notify].on_eventruns an operator argv on the host with onlyAGENT6_MACHINE_*env (mirrors[notify].on_complete); a[config]overlay setting[machine.notify]is rejected at load. No Web Push/VAPID.
- The out-of-band hook
- A skill is config, not repo content: install only from trusted sources.
agent6 skills install <url>is an operator-initiated CLI fetch (same trust class asconnect); what it installs enters the system prompt/tool results verbatim.
- Nothing in a skill runs at install or load.
- Its scripts run only if the model runs them through the jailed command path,
subject to
run_commands.
- Its scripts run only if the model runs them through the jailed command path,
subject to
use_skillis read-only and path-contained. Serves the skill's own dir only (symlinks/..resolved first), never the repo or network. Skill dirs aren't mounted into the jail; content reaches the model engine-side.- Repo-local
.claude/skills/are deliberately NOT discovered. Third-party repo content must not enter the prompt; only the installed dir +[skills].extra_dirsare scanned.
tests/security/test_prompt_injection.py
runs an adversarial corpus through the planner/worker/reviewer prompts and
asserts no exfiltration, no out-of-policy tool calls, and no following embedded
instructions to weaken constraints. It's a smoke test, not a proof: the
structural defenses above are the real mitigation; the corpus catches prompt
regressions.
- Landlock TCP rules need Linux ≥ 6.7 (ABI ≥ 4); older kernels leave the agent
process net-unconfined (children stay net-isolated in
strictvia the empty netns). - User namespaces must be enabled; some distros disable them, and agent6
refuses
strictthere. - AppArmor userns (Ubuntu 24.04+) blocks unprivileged userns without a profile.
- agent6 ships one scoped to the launcher (
agent6 system apparmor install); with it, per-command jailing isstrict, without ithardened. - Caveat: the egress broker needs the agent process to make a userns, which
the launcher-only profile doesn't grant, so a default run downgrades to
hardened(Landlock egress) unless you set the sysctl to 0 host-wide or useagent_network = "open".
- agent6 ships one scoped to the launcher (
- seccomp is required; kernels that block it from unprivileged callers make the jail fail closed.
- Devcontainers get
hardened; the container is the FS blast radius, network still Landlock-confined when supported. The XDG state base is ephemeral (lost on rebuild), so mount a volume at the state dir or set[agent6].state_dirto persist runs. - Side channels: no claim about timing/cache/speculative side channels; don't co-locate agent6 with secrets if Spectre-class attacks are in your model.
- Supply chain: pin your install. Runtime deps
pydantic,httpx2,argcomplete, thetree-sitterpair,textual,ruff,ty; build-dephatchling; the jail's Rust cratesnix,libc,landlock,seccompiler,serde,serde_json.