Safety & Gating
How shell and tool calls are vetted before they run
Muninn can run shell commands, reach other hosts over SSH, and act on the home unattended. The thing that makes that safe is not the model's good judgement — it is a deterministic gate pipeline that inspects every command before execution. The guiding principle:
Deterministic rules guard the unmistakable; the LLM judges the nuanced
Anything catastrophic or categorically off-limits (partition wipes, fork bombs,
reaching a protected host, encoded-payload bypasses) is blocked by hardcoded
pattern matching — the model never gets a vote. Only the genuinely ambiguous
middle ground (“is git reset --hard HEAD~1 reasonable self-admin?”)
is delegated to an LLM safety judge, and that judge is the
last gate, not the first. Safety never depends on the prompt being obeyed.
The Gate Pipeline
Every shell invocation passes through these checks in order
(src/tools/shell.rs). Any gate can stop the command and return a message
to the LLM instead of executing.
self_restart
tool. Absolute; runs before every bypass.shell_protected_hosts is blocked, even tunnelled through a sandbox hop.
These machines are never SSH-reachable.python -c, base64 -d | sh, paramiko/fabric, …) are
rejected before the sandbox shortcut, so nothing can smuggle a remote session inside
an interpreter.ssh/scp/
rsync/… must target a host in shell_sandbox_hosts or
shell_ssh_remote_allowlist. An empty allowlist denies all
outbound remote-exec. localhost/127.0.0.1 are always
local.`…` or $(…)) —
an echo/printf redirected/teed to a file, or an
unquoted heredoc (<< EOF, not
<< 'EOF') — is blocked, because bash executes that
substitution instead of writing it literally. The rejection points at the safe
quoted-heredoc form. Universal: fires even under
shell_local_full_access — it is the gate that prevents a
rule's literal backticks from running a command and dumping its output into the
target file.safe / caution / dangerous by the
model. dangerous blocks; caution is logged and allowed;
safe runs. If the judge call fails, the command is blocked
(fail-closed).The Catastrophic Floor
A hard tripwire that no flag can lift
A small subset of the reject list is irreversible host destruction —
recursive wipes of / or $HOME, raw writes to
/dev/sd*//dev/nvme*, mkfs/wipefs/
fdisk/parted, and fork bombs. These are caught by
fast_reject_catastrophic() and fire even under
shell_local_full_access, which otherwise skips the judge and the
full reject list. There is no operational reason to run them, so they are blocked
deterministically with the model out of the loop entirely.
Full-Access Bypass Flags
Two opt-in flags loosen the gates for trusted self-administration. Crucially, neither lifts Gates 0/0a/0b — a bypassed command still cannot reach a protected host, smuggle an obfuscated payload, or SSH outside the allowlist.
| Flag | Scope | Skips | Still enforced |
|---|---|---|---|
shell_local_full_access |
Commands that run on this host (no outbound remote-exec) | The safety judge and the full fast-reject list | Self-service, protected-hosts, obfuscation, remote-allowlist, and the catastrophic floor |
shell_adb_full_access |
A single bare adb invocation (no top-level shell operators) |
Fast-reject and the safety judge | Self-service, protected-hosts, obfuscation, remote-allowlist |
The trade-off is explicit: local_full_access lets Muninn self-administer its
own box (sudo, systemctl, apt, docker,
git reset, iptables…) but drops the deterministic floor from
the full reject list down to the catastrophic subset. Enable it only on a host you trust
Muninn to own.
Deterministic vs. LLM
The defining property of the design — almost everything is hardcoded; the model is one gate among many, and the only non-deterministic one.
| Check | Type |
|---|---|
| Self-service control | Deterministic |
| Protected hosts | Deterministic |
| Obfuscation / encoded payloads | Deterministic |
| Remote-exec allowlist | Deterministic |
| Fast-reject (dangerous patterns) | Deterministic |
| Catastrophic floor (irreversible) | Deterministic |
Safety judge (safe/caution/dangerous) | LLM |
Role gating sits in front of all of this
Before a command ever reaches the pipeline, the role-based tool filter
decides whether the caller may use shell at all — it is admin-only.
The gates above are the second line for the privileged path, not the first. See the
Tools and Config pages for the permissions
matrix and the shell_* config keys.