Summary
The dangerous-command safety gate in tools/approval.py classifies commands with a regex denylist (DANGEROUS_PATTERNS, and the newer HARDLINE_PATTERNS) matched against a lightly-normalized command string. Because the string is matched before the shell performs backslash/quote removal, parameter expansion, and command substitution, any shell-level encoding of a blocked keyword defeats the regex while preserving execution behavior. The command then runs via subprocess.run(..., shell=True) with no approval prompt — silent arbitrary command execution under the hermes user in any non-interactive context (cron, RL rollouts, subagents).
This is the core safety boundary the project's own SECURITY.md describes as protecting the operator from LLM actions — so it is squarely in scope under the documented trust model.
Affected code (current main, ef3a650f05d2)
tools/approval.py:336 — DANGEROUS_PATTERNS (regex list, \brm\b, \bchmod\b, etc.)
tools/approval.py:218 — HARDLINE_PATTERNS (unconditional blocklist — also pure regex)
tools/approval.py:485 — _normalize_command_for_detection (strips ANSI/null/NFKC only — no shell tokenization)
tools/approval.py:503 — detect_dangerous_command / :289 — detect_hardline_command
Verified bypass (run against current main tools/approval.py)
DANGEROUS HARDLINE command
True False rm -rf /home/victim ← baseline, correctly flagged
False False r\m -rf /home/victim ← BYPASS
False False r''m -rf /home/victim ← BYPASS
False False $(echo rm) -rf /home/victim ← BYPASS
False False ${0/x/r}m -rf /home/victim ← BYPASS
True True rm -rf / ← only the literal form is caught
All four encodings parse to rm under bash (r\m, empty-quote split, command substitution, parameter substitution) but evade both the DANGEROUS and the new HARDLINE lists. The hardline root-delete guard (rm -rf /) is itself bypassable with the same r\m -rf / trick.
Reproduction
from tools.approval import detect_dangerous_command, detect_hardline_command
assert detect_dangerous_command("rm -rf /home/x")[0] is True # flagged
assert detect_dangerous_command(r"r\m -rf /home/x")[0] is False # bypass
assert detect_hardline_command(r"r\m -rf /")[0] is False # bypass hardline too
# → detect_* returns not-dangerous → approval layer returns approved → bash runs rm
Impact
On a non-dangerous classification the approval layer returns approved with no prompt; in non-interactive deployments this is silent RCE under the hermes account. Interactive users simply never see the approval prompt for the dangerous command — defeating the last line of defense. CWE-184 (Incomplete List of Disallowed Inputs), CWE-78 (OS Command Injection). The same weak-regex pattern is reused elsewhere (tui_gateway — see sibling issue).
Suggested remediation
Stop classifying shell strings with regex. Tokenize with shlex.split(), resolve each statement separator (;, |, &&, ||), strip backslash/empty-quote escapes, recurse into $(…)/backticks, and match each resolved argv[0] against a normalized program-name set. Optionally cross-check the post-expansion command from a locked-down bash -c 'printf %s' dry run. Regex on the raw string cannot soundly classify shell commands.
Prior private disclosure & status
This was reported privately via GitHub Security Advisory on 2026-04-23 (advisory GHSA-gmqw-rqrf-c48w, "Dangerous-command denylist bypass and platform-adapter approval fail-opens", rated Critical). The advisory was closed on 2026-05-14 without a fix, without a CVE, and without publication. As shown above, the bypass still reproduces on current main (ef3a650f05d2, 2026-06-01) — the subsequently-added HARDLINE_PATTERNS list does not address it. Opening publicly so the regression has a tracked, visible record. (A related fail-open from the same advisory has its own issue; the Telegram item from that advisory was independently fixed in #24457.)
Summary
The dangerous-command safety gate in
tools/approval.pyclassifies commands with a regex denylist (DANGEROUS_PATTERNS, and the newerHARDLINE_PATTERNS) matched against a lightly-normalized command string. Because the string is matched before the shell performs backslash/quote removal, parameter expansion, and command substitution, any shell-level encoding of a blocked keyword defeats the regex while preserving execution behavior. The command then runs viasubprocess.run(..., shell=True)with no approval prompt — silent arbitrary command execution under the hermes user in any non-interactive context (cron, RL rollouts, subagents).This is the core safety boundary the project's own
SECURITY.mddescribes as protecting the operator from LLM actions — so it is squarely in scope under the documented trust model.Affected code (current
main,ef3a650f05d2)tools/approval.py:336—DANGEROUS_PATTERNS(regex list,\brm\b,\bchmod\b, etc.)tools/approval.py:218—HARDLINE_PATTERNS(unconditional blocklist — also pure regex)tools/approval.py:485—_normalize_command_for_detection(strips ANSI/null/NFKC only — no shell tokenization)tools/approval.py:503—detect_dangerous_command/:289—detect_hardline_commandVerified bypass (run against current
maintools/approval.py)All four encodings parse to
rmunder bash (r\m, empty-quote split, command substitution, parameter substitution) but evade both theDANGEROUSand the newHARDLINElists. The hardline root-delete guard (rm -rf /) is itself bypassable with the samer\m -rf /trick.Reproduction
Impact
On a non-dangerous classification the approval layer returns approved with no prompt; in non-interactive deployments this is silent RCE under the hermes account. Interactive users simply never see the approval prompt for the dangerous command — defeating the last line of defense. CWE-184 (Incomplete List of Disallowed Inputs), CWE-78 (OS Command Injection). The same weak-regex pattern is reused elsewhere (
tui_gateway— see sibling issue).Suggested remediation
Stop classifying shell strings with regex. Tokenize with
shlex.split(), resolve each statement separator (;,|,&&,||), strip backslash/empty-quote escapes, recurse into$(…)/backticks, and match each resolvedargv[0]against a normalized program-name set. Optionally cross-check the post-expansion command from a locked-downbash -c 'printf %s'dry run. Regex on the raw string cannot soundly classify shell commands.Prior private disclosure & status
This was reported privately via GitHub Security Advisory on 2026-04-23 (advisory
GHSA-gmqw-rqrf-c48w, "Dangerous-command denylist bypass and platform-adapter approval fail-opens", rated Critical). The advisory was closed on 2026-05-14 without a fix, without a CVE, and without publication. As shown above, the bypass still reproduces on currentmain(ef3a650f05d2, 2026-06-01) — the subsequently-addedHARDLINE_PATTERNSlist does not address it. Opening publicly so the regression has a tracked, visible record. (A related fail-open from the same advisory has its own issue; the Telegram item from that advisory was independently fixed in #24457.)