Skip to content

Bash tool validator: denylist bypass classes (deferred from #1056) #1087

@planetf1

Description

@planetf1

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

  • flag_value_flags skip dropped, or scoped to SAFE_WRAPPER_COMMANDS with the real per-wrapper value-taking flags (env -u VAL, env -S VAL, etc.)
  • All Problem 1 reproducers blocked
  • When a shell is allowed as a nested arg in a safe wrapper, the -i/-l/-c reject is re-applied to argv[i:]
  • All Problem 2 reproducers blocked
  • git push --force* family and git clean --force long form blocked
  • shell.py and interpreter.py install hints use uv syntax
  • Adversarial regression tests added for each bypass class
  • Public docstrings (bash_executor, unsafe_local_bash_executor) adjusted if the chosen fix changes the framing — coordinate with the path-safety follow-up so they land coherently

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions