opaque-exec: gate local scripts handed to a script interpreter (C2) - #28
Merged
githubscum merged 1 commit intoAug 23, 2026
Conversation
SCRIPT_EXT recognizes a script by its file's extension (.ps1/.sh/.bash/ .zsh/.bat/.cmd). A local script with any other spelling flows free even though its contents are exactly as unreadable to the gate: python /tmp/evil.py, node /tmp/evil.js, and an extensionless script through a shell interpreter (bash /tmp/deploy) all execute unreadable code with no receipt. This closes the class by recognizing the interpreter, not the file suffix: when a segment hands a local file to a known script interpreter (python/node/ruby/perl/php/bash/zsh/dash/sh/pwsh/powershell, after optional env assignments), opaque-exec fires. Inline-code flags (-c/-e/-m/-r/--eval/--module) and their operands stay free: the code string is IN the command, visible to every other matcher. Boundaries held: bash -c, python -c, node -e, python -m, python --version, node -v, ls, cat, which python, echo python ... all remain free (verified in test). Regression test proves fail-first: 11 of the gate cases fail against the unpatched matcher, all pass after (24/24). Derived from KNOWN-LIMITS 21 (the deploy-incident class): the gate sees the interpreter and the file, not the code inside. One spelling away from ./deploy.sh, which already gates.
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 freeto 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.
Closes the Lotor C2 confession as delivered: a dangerous command that a gated rule does not catch.
The hole
opaque-execrecognizes a script by its file's extension (SCRIPT_EXT=.ps1|.sh|.bash|.zsh|.bat|.cmd). A local script with any other spelling flows free, though its contents are exactly as unreadable to the gate. Through the unpatched matcher, all of these exit 0 with no receipt:This is the deploy-incident class (KNOWN-LIMITS 21): "hands control to a local script the gate cannot read" — one spelling away from
source /tmp/deploy.sh, which gates today.The fix
Recognize the interpreter, not the file suffix. When a segment hands a local file to a known script interpreter (
python|python3|node|nodejs|ruby|perl|php|bash|zsh|dash|ksh|sh|pwsh|powershell, after optional inline env assignments andenv),opaque-execfires — aPreToolUseboundary, exactly like./deploy.sh.Inline-code flags stay free by design:
-c/-e/-m/-r/--code/--eval/--moduleand their operands are in the command string, visible to every other matcher, so opaque-exec need not own them. The matcher skips ordinary option flags (bash --posix file) then requires a file token.Failure modes answered (acceptance #3)
bash --posix /tmp/deploy→ gates (flags skipped, file remains)python→ free (no file)python --version,node -v→ freebash -c "...",python -c "...",node -e "...",python -m http.server→ free (code visible in-command)which python,echo python /tmp/x.py→ free (interpreter not in command position)Quietness (acceptance #4)
This tightens the gate — it does not quieten it. What it now misses is declared, not hidden: a bare direct execution of an extensionless file (
./deploy) could be a compiled binary, and a string matcher cannot tell an ELF from a shebang without reading the file. That class would need a C3-style context resolver (file sniff), which is deliberately out of scope here; the interpreter class is string-distinguishable and is the one this PR closes.Tests
test/policy-opaque-exec-interpreter.test.js— 24 cases.