Skip to content

hook: refuse a quoted command string passed through a login shell - #508

Open
EdbertChan wants to merge 1 commit into
stack/EdbertChan/reflect/ui-input-guard-hook-freshness-20260908/count-bogus-didn-t-run-same-class--065043b6from
stack/EdbertChan/reflect/ui-input-guard-hook-freshness-20260908/refuse-quoted-cmd-string-passed-login-shell--acc7d623
Open

hook: refuse a quoted command string passed through a login shell#508
EdbertChan wants to merge 1 commit into
stack/EdbertChan/reflect/ui-input-guard-hook-freshness-20260908/count-bogus-didn-t-run-same-class--065043b6from
stack/EdbertChan/reflect/ui-input-guard-hook-freshness-20260908/refuse-quoted-cmd-string-passed-login-shell--acc7d623

Conversation

@EdbertChan

@EdbertChan EdbertChan commented Sep 12, 2026

Copy link
Copy Markdown
Owner

sudo -i starts the target user's login shell, and that shell parses the
remaining arguments a second time. The quoting the first parse consumed is
gone by then, so the command string is re-split and its first word becomes
the whole command. A handoff script died this way in a user's terminal.

Reproduced on a real host, one variable apart: sudo -u demo -H bash -lc '<body>' prints the body's output, and the same line with -i gives
bash: line 1: set: -c: invalid option.

Newlines are not the trigger, which is what the first reading of the
failure got wrong. A multi-line body survives ssh and survives sudo without
-i.

Backtested over 37,015 real Bash commands: 6 hits, all the broken shape.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01KU2pPKob4MJ1NqjsfTNyYJ

Depends-On: #507


Note

Low Risk
Adds an optional PreToolUse gate on Bash commands with fail-open error handling; may block rare legitimate sudo -i + quoted -c patterns until rewritten.

Overview
Adds a new Claude PreToolUse (Bash) hook, remote-payload-collapses, that blocks commands where a quoted command string is passed through sudo -i, su -, or su -l. The login shell re-parses arguments after the first parse consumed quoting, so the payload is re-split and often breaks (e.g. bash -lc '<body>' vs the same line with -i).

Detection is text-only (detect.collapse_risk): login-shell pattern, a carried interpreter (bash, -c, etc.), and quotes in the tail; heredocs are stripped so stdin/file patterns stay allowed. On match the hook exits 2 with remediation (drop -i, or scp + run by path); malformed JSON and detector errors fail open.

install.sh symlinks the hook and merges install_claude_hook.py into ~/.claude/settings.json. README / docs/ecosystem.md document it; unit tests cover fire/silent fixtures and install wiring.

Reviewed by Cursor Bugbot for commit d76e1e5. Bugbot is set up for automated code reviews on this repo. Configure here.

`sudo -i` starts the target user's login shell, and that shell parses the
remaining arguments a second time. The quoting the first parse consumed is
gone by then, so the command string is re-split and its first word becomes
the whole command. A handoff script died this way in a user's terminal.

Reproduced on a real host, one variable apart: `sudo -u demo -H bash -lc
'<body>'` prints the body's output, and the same line with `-i` gives
`bash: line 1: set: -c: invalid option`.

Newlines are not the trigger, which is what the first reading of the
failure got wrong. A multi-line body survives ssh and survives sudo without
`-i`.

Backtested over 37,015 real Bash commands: 6 hits, all the broken shape.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KU2pPKob4MJ1NqjsfTNyYJ
Change-Id: Iacc7d62317959adeb953d4fef0bc9dc222a37430
@EdbertChan

Copy link
Copy Markdown
Owner Author

This pull request is part of a Mergify stack:

# Pull Request Link
1 hook: a script handed to the user is a claim that it runs #506
2 audit(token_audit): count "bogus" and "it didn't run" as the same class #507
3 hook: refuse a quoted command string passed through a login shell #508 👈
4 gate: a branch taken per operating system needs a test that injects one #509
5 audit(token_audit): one submission carrying two slash commands is one message #510
6 audit: use the transcript's own markers for what the human actually sent #511

@cursor

cursor Bot commented Sep 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_b4f81974-f0cc-47e1-bd46-3f4d0b6ad249)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant