Problem
Warden's real-time hook dispatch evaluates security patterns against the command string passed to the Bash tool —
not the contents of any script that command invokes. This means a malicious or destructive payload hidden inside a
Python, Bash, or any other script file bypasses all of Warden's runtime checks entirely.
Example:
malicious.py — contents never inspected by Warden
import os
os.system("rm -rf ~/important-dir")
What the agent calls — all Warden sees
python3 malicious.py # ← no pattern match, passes clean
Every pattern in policies.py (DESTRUCTIVE_COMMAND_PATTERN, RCE_CANARY_PATTERN, SECRET_EXFIL_PATTERN, etc.) is
evaluated only against this outer command string. The script file is never opened or read at dispatch time.
This applies to any interpreter invocation: python3 , bash , node , ruby , etc.
Impact
- An adversarial prompt or supply-chain-compromised dependency could generate a script file and then instruct the
agent to run it — completely bypassing Warden in enforce mode.
- The warden scan command does read MCP server source files (scanner.py:_resolve_skill_source), but this is a
one-time audit pass, not real-time protection, and it only covers scripts referenced in agent config files — not
arbitrary scripts run during a session.
Proposed Solution: Pre-execution static analysis for script invocations
In hooks.py, inside the normalize_payload / should_block path, add a pre-execution step that:
- Detects when the shell command matches an interpreter invocation pattern (python3 , bash , node
, etc.)
- Resolves the script path relative to the workspace
- Reads the script contents (with a size cap, similar to scanner.py's _MAX_SOURCE_SIZE = 100 KB)
- Runs the existing policy patterns against the script source as an additional synthetic event
Rough sketch:
_INTERPRETER_RE = re.compile(
r"^\s*(python3?|bash|sh|node|ruby|perl|Rscript)\s+([^\s\-][^\s]*.(py|sh|js|rb|pl|r))\b",
re.IGNORECASE,
)
def _extract_script_source(command: str, workspace: Path) -> str | None:
m = _INTERPRETER_RE.match(command)
if not m:
return None
script_path = Path(m.group(2))
if not script_path.is_absolute():
script_path = workspace / script_path
if not script_path.exists() or script_path.stat().st_size > 100 * 1024:
return None
return script_path.read_text(encoding="utf-8", errors="replace")
The extracted source would then be evaluated against the same PolicyEngine rules as a synthetic shell event, with
the finding evidence pointing back to the script file path so the user knows exactly where the threat is.
Limitations to note
This approach won't catch:
- Obfuscated payloads (exec(base64.b64decode(...)))
- Scripts fetched at runtime (curl … | python3)
- Compiled binaries or .pyc files
Those are harder problems (dynamic analysis territory). The static pass described above handles the straightforward
case of a readable script file with inline destructive code, which covers the most realistic threat model for an
AI coding agent session.
Files to change
- warden/hooks.py — add pre-execution script content extraction and re-evaluation
- warden/policies.py or warden/policy_engine.py — possibly expose a helper to evaluate raw source text so it can be
reused from the hooks path without duplicating pattern logic `
Problem
Warden's real-time hook dispatch evaluates security patterns against the command string passed to the Bash tool —
not the contents of any script that command invokes. This means a malicious or destructive payload hidden inside a
Python, Bash, or any other script file bypasses all of Warden's runtime checks entirely.
Example:
malicious.py — contents never inspected by Warden
import os
os.system("rm -rf ~/important-dir")
What the agent calls — all Warden sees
python3 malicious.py # ← no pattern match, passes clean
Every pattern in policies.py (DESTRUCTIVE_COMMAND_PATTERN, RCE_CANARY_PATTERN, SECRET_EXFIL_PATTERN, etc.) is
evaluated only against this outer command string. The script file is never opened or read at dispatch time.
This applies to any interpreter invocation: python3 , bash , node , ruby , etc.
Impact
agent to run it — completely bypassing Warden in enforce mode.
one-time audit pass, not real-time protection, and it only covers scripts referenced in agent config files — not
arbitrary scripts run during a session.
Proposed Solution: Pre-execution static analysis for script invocations
In hooks.py, inside the normalize_payload / should_block path, add a pre-execution step that:
, etc.)
Rough sketch:
_INTERPRETER_RE = re.compile(
r"^\s*(python3?|bash|sh|node|ruby|perl|Rscript)\s+([^\s\-][^\s]*.(py|sh|js|rb|pl|r))\b",
re.IGNORECASE,
)
def _extract_script_source(command: str, workspace: Path) -> str | None:
m = _INTERPRETER_RE.match(command)
if not m:
return None
script_path = Path(m.group(2))
if not script_path.is_absolute():
script_path = workspace / script_path
if not script_path.exists() or script_path.stat().st_size > 100 * 1024:
return None
return script_path.read_text(encoding="utf-8", errors="replace")
The extracted source would then be evaluated against the same PolicyEngine rules as a synthetic shell event, with
the finding evidence pointing back to the script file path so the user knows exactly where the threat is.
Limitations to note
This approach won't catch:
Those are harder problems (dynamic analysis territory). The static pass described above handles the straightforward
case of a readable script file with inline destructive code, which covers the most realistic threat model for an
AI coding agent session.
Files to change
reused from the hooks path without duplicating pattern logic `