fix(approval): sudo-S brute-force block + sudo-stdin/askpass DANGEROUS (salvage of #22194 + #21128) - #23736
Merged
Merged
Conversation
Contributor
🔎 Lint report:
|
15 tasks
…not set Fixes #9590: Block explicit sudo -S (stdin password mode) commands when the SUDO_PASSWORD environment variable is not configured. The attack vector: the LLM constructs 'echo guessedpass | sudo -S cmd' to brute-force sudo passwords, iterates based on sudo's error output ('Sorry, try again'). The existing _transform_sudo_command only injects -S when SUDO_PASSWORD exists; without it, the LLM's explicit sudo -S must be treated as a guessing attempt. Changes: - Add _check_sudo_stdin_guard() in approval.py: detects sudo -S when SUDO_PASSWORD is absent, anchored to command-start positions (^ ; && || | etc.) to avoid false positives on literal text - Integrate into check_all_command_guards() above yolo/mode=off so the block is unconditional (like the hardline floor) - Add 6 tests covering: detection, allow-list, SUDO_PASSWORD bypass, integration with check_all_command_guards, yolo non-bypass, container backend bypass
Adds the only #17873 category not covered by the in-flight PRs #17962 (briandevans, reverse shell + download-execute) and #7993 (SHL0MS, credential reads + curl/wget exfiltration): sudo invocations that an LLM-driven agent can drive without TTY interaction. The agent has no TTY, so the sudo forms that succeed without human involvement are those reading the password from stdin (`-S` / `--stdin`) or via an askpass helper (`-A` / `--askpass`). The shell-launch (`-s`) and list-privileges (`-a`) flags are also gated since they are privilege-relevant invocations the agent can chain after acquiring the password (e.g. read SUDO_PASSWORD from .env -> sudo -S -s -> root shell). Plain `sudo cmd` (no flag) is TTY-bound and excluded. Two patterns: 1. Direct flag: `\bsudo\b[^;|&\n]*?\s+(?:-s\b|--stdin\b|-a\b|--askpass\b)` The lazy `[^;|&\n]*?` consumes flag-arguments without spanning command separators, so `sudo -u root -S whoami` matches (a textbook offensive form that a strict `(?:\s+-[^\s]+)*` "leading flags only" pattern would have missed because `root` is a flag-value not a flag). 2. Combined short flags: `\bsudo\b[^;|&\n]*?\s+-[a-z]*[sa][a-z]*\b` Catches packed forms like `sudo -nS id` where multiple flags share a single `-X` token. `_normalize_command_for_detection` lowercases input before pattern matching (tools/approval.py:340), so case variants of S/s and A/a collapse — both letter-pairs are gated since each is a privilege- relevant invocation. Tests: 21 new cases in TestDetectSudoStdin (12 positive covering all flag-order permutations including herestring source and printf-piped forms; 9 negative including TTY-bound `sudo whoami`, interactive `sudo -i`, env-var reference `$SUDO_USER`, doc lookup `man sudo`, package install, and the `pseudosudo` word-boundary edge case). Empirical coverage: 11/11 attacks matched, 0/10 false positives. Refs: #17873 category 4. Adjacent: #17962 (reverse shell + download- execute), #7993 (credential reads + curl/wget exfiltration). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
kshitijk4poor
force-pushed
the
salvage/sudo-hardening-9590-17873
branch
from
May 11, 2026 13:56
2faba06 to
65a45b7
Compare
This was referenced May 11, 2026
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Salvages two complementary security fixes for the sudo-password class of issues into one PR. Both target the same underlying problem: an LLM agent with no TTY can attempt sudo brute-forcing or unauthorized privilege escalation by piping passwords via
sudo -S/--stdin/--askpass. The two PRs cover different layers of the defense:sudo -S(the LLM brute-force vector) incheck_all_command_guardsabove yolo, whenSUDO_PASSWORDis unset. Cannot be bypassed by--yolo/approvals.mode=off.sudo -S/--stdin/-A/--askpass/-s/-aand combined-short-flag variants toDANGEROUS_PATTERNS. Surfaces the privilege-relevant invocations through normal approval (yolo can bypass — by design).Together they address #9590 (sudo password chat-leak via small-model brute-force) and #17873 cat 4 (broader sudo privilege-flag surface).
Why both layers
sudo --askpass, herestring (<<<), and combined-flag forms (-nS,-sA).--yolocould still brute-forcesudo -S [guess].The two layers don't overlap behaviorally. The unconditional guard fires only when
SUDO_PASSWORDis unset; the legitimate Hermes_transform_sudo_commandpath (which injects-Sitself whenSUDO_PASSWORDis configured) is unchanged.Closes / supersedes
Changes
tools/approval.py_check_sudo_stdin_guard()+ 2 DANGEROUS_PATTERNS entriestests/tools/test_hardline_blocklist.pytests/tools/test_approval.pyscripts/release.py303 net lines, 254 tests pass in the modified suites (
test_hardline_blocklist.py+test_approval.py).E2E verification
Verified through
check_all_command_guards()with real imports (no mocks) that:sudo -S whoami(SUDO_PASSWORD unset)sudo -k && sudo -S whoamisudo whoami(plain sudo)ls -lasudo -S whoamiwithHERMES_YOLO_MODE=1sudo -S whoamiin docker backendsudo --stdin id,sudo -A whoami,sudo -nS id,sudo -u root -S whoami,sudo -S id <<< 'pw', `printf "%s\n" "$PW"Test plan
Lint diff: ruff clean, no new ty issues on changed files.
Authorship
Cherry-picked individual commits to preserve per-commit authorship:
fix(terminal): block sudo -S password guessing when SUDO_PASSWORD is not set— @29206394 (PR fix(terminal): block sudo -S password guessing when SUDO_PASSWORD is unset #22194)fix(approval): catch sudo with stdin/askpass/shell privilege flags— @fr33d3m0n (PR fix(approval): catch sudo with stdin/askpass/shell privilege flags #21128)chore: AUTHOR_MAP entries for sudo-hardening salvage contributors— @kshitijk4poor (this salvage)Both contributors credited via
AUTHOR_MAP. Original PRs will be closed pointing to this one.