Surfaced during PR #1056 review. The _is_dangerous_command validator in mellea/stdlib/tools/shell.py has several bypass classes that let dangerous commands through. Filed here so the bash tool can ship in #1056 and the denylist hardening lands separately.
Problem 1 — Global flag_value_flags skip
The skip at shell.py:204 fires whenever the previous token is in flag_value_flags = {-c, --config, -f, --file, -o, --output, -d, --dir, -p, --path, -t, --timeout, -w, --wait}. The skip applies regardless of the top-level command, so it works without any SAFE_WRAPPER_COMMANDS prefix. Verified against PR head c7623279:
ssh -t sudo whoami # ALLOWED — `-t` skips `sudo`
git -c sudo whoami # ALLOWED
ls -d sudo whoami # ALLOWED
find -p sudo something # ALLOWED
env -i sudo whoami # ALLOWED (the env-chain case originally raised in #1056)
The original env-chain bypass is the scoped version of this. The wider issue is that argv[0] is never checked against SAFE_WRAPPER_COMMANDS before the skip applies.
Problem 2 — Safe-wrapper allows nested shells with -i / -c flags
The check at shell.py:220-229 lets a shell binary through as a nested arg when the wrapper is in SAFE_WRAPPER_COMMANDS (legitimate use: timeout bash script.sh). Once allowed, the interactive-shell check at shell.py:244 never fires because it keys on cmd (the wrapper), not the nested shell.
timeout 10 bash -i # ALLOWED — interactive shell spawned
env bash -i # ALLOWED
nohup bash -i # ALLOWED
timeout 10 bash -c 'echo x' # ALLOWED — also defeats interpreter-indirection
Problem 3 — git --force* long forms not blocked
shell.py:256 matches only --force exact or -f, so --force-with-lease and --force-if-includes slip through. shell.py:268-276 blocks git clean -f/-fd short forms but excludes long-option variants via the not arg.startswith("--") guard, so git clean --force is allowed.
git push --force-with-lease # ALLOWED
git push --force-if-includes # ALLOWED
git clean --force # ALLOWED
Problem 4 — Install hint should use uv
shell.py:764 (and the matching interpreter.py:1019) emit Install with: pip install 'mellea[sandbox]' when llm-sandbox is missing. Project convention is uv add. Worth fixing both files together.
Success criteria
Surfaced during PR #1056 review. The
_is_dangerous_commandvalidator inmellea/stdlib/tools/shell.pyhas several bypass classes that let dangerous commands through. Filed here so the bash tool can ship in #1056 and the denylist hardening lands separately.Problem 1 — Global
flag_value_flagsskipThe skip at
shell.py:204fires whenever the previous token is inflag_value_flags = {-c, --config, -f, --file, -o, --output, -d, --dir, -p, --path, -t, --timeout, -w, --wait}. The skip applies regardless of the top-level command, so it works without anySAFE_WRAPPER_COMMANDSprefix. Verified against PR headc7623279:The original env-chain bypass is the scoped version of this. The wider issue is that
argv[0]is never checked againstSAFE_WRAPPER_COMMANDSbefore the skip applies.Problem 2 — Safe-wrapper allows nested shells with
-i/-cflagsThe check at
shell.py:220-229lets a shell binary through as a nested arg when the wrapper is inSAFE_WRAPPER_COMMANDS(legitimate use:timeout bash script.sh). Once allowed, the interactive-shell check atshell.py:244never fires because it keys oncmd(the wrapper), not the nested shell.Problem 3 —
git --force*long forms not blockedshell.py:256matches only--forceexact or-f, so--force-with-leaseand--force-if-includesslip through.shell.py:268-276blocksgit clean -f/-fdshort forms but excludes long-option variants via thenot arg.startswith("--")guard, sogit clean --forceis allowed.Problem 4 — Install hint should use
uvshell.py:764(and the matchinginterpreter.py:1019) emitInstall with: pip install 'mellea[sandbox]'whenllm-sandboxis missing. Project convention isuv add. Worth fixing both files together.Success criteria
flag_value_flagsskip dropped, or scoped toSAFE_WRAPPER_COMMANDSwith the real per-wrapper value-taking flags (env -u VAL,env -S VAL, etc.)-i/-l/-creject is re-applied toargv[i:]git push --force*family andgit clean --forcelong form blockedshell.pyandinterpreter.pyinstall hints useuvsyntaxbash_executor,unsafe_local_bash_executor) adjusted if the chosen fix changes the framing — coordinate with the path-safety follow-up so they land coherently