fix(e2): match shell env harvesting across grep flags and quoting - #483
fix(e2): match shell env harvesting across grep flags and quoting#483MandoCodes1 wants to merge 1 commit into
Conversation
rng1995
left a comment
There was a problem hiding this comment.
[SkillSpector Review]
Changes requested at head 8981765f76e440b74eeeca7e802f8e2c7bc6da1e.
src/skillspector/nodes/analyzers/static_patterns_data_exfiltration.py:78: the negative lookahead searches the entire remainder of the line for-v, not just grep's option segment. A genuine harvest such asenv | grep SECRET > /tmp/out # -v(or a later; echo -v) is therefore suppressed, providing a trivial comment/trailing-command evasion. Limit the inversion exclusion to options actually attached to the grep invocation and add regressions with trailing comments/commands containing-vwhile preserving the realgrep -vredaction case.
Required CI is green, but this detection bypass and the BEHIND merge state block merge.
8981765 to
ae8e731
Compare
The shell arm of E2 only matched `env | grep` followed by an optional `-i` and a bare keyword, so `env | grep -i -E 'token|key|secret'`, `env | grep -iE "aws_|secret"` and `env | egrep -e password` all scored as clean. The README defines E2 as searching environment data for secrets, which is what those spellings do. Widen the pattern to accept env or printenv as the source, grep, egrep or fgrep as the filter, any number of short or long flags, and a keyword anywhere in the first 40 characters of the pattern argument. A quoted argument is scanned to the closing quote; an unquoted one ends where the word does, at whitespace, a comment or any shell separator, the pipe included. Inverting flags have to be excluded, since `grep -v` keeps secrets out of the output and is redaction rather than harvesting. Look for them by walking the run of option words attached to grep and giving up on the first inverting one, in either the short or the long spelling. Scoping the search this way matters in both directions: a check over the whole line would let a trailing `# -v` comment or a later `; echo -v` suppress a real harvest, while ending the argument at the pipe is what keeps `env | grep TERM | grep -v SECRET` clean, since the redacting stage is then a command the walk can see. Require the keyword to stand alone as a name, optionally plural or numbered, rather than appear anywhere inside one. MONKEY_PATCH, XKB_DEFAULT_KEYMAP and `grep -i keyboard` no longer score as secret lookups, while KEYS, TOKENS and KEY2 still do. Add pattern tests for seventeen harvesting spellings, twenty-three ordinary or inverted lookups and a backtracking bound at two input sizes, plus a SKILL.md fixture with a CLI regression test. Signed-off-by: Miguel Orti Vila <[email protected]>
ae8e731 to
ea37182
Compare
|
Good catch, and thanks for the precise repro. Fixed in The inversion check no longer looks at the line. It walks only the run of option words attached to grep and gives up on the first inverting one: That run ends at the first word not starting with a dash, so a trailing comment or a later command cannot reach it. The argument scan is bounded to the one command as well: quoted arguments run to the closing quote, unquoted ones end at whitespace, a comment or a shell separator, the pipe included. Both of the cases you named now flag, and the
Ending the argument at the pipe is what keeps that last row clean. A scan that ran past it would read the redacting stage as part of the first grep's pattern and score the whole pipeline as harvesting, which would be the same defect you found, one stage further along. Scoping the walk this way also picks up two cases a line-wide check gets wrong in the other direction. One related change while I was in the pattern. The keyword now has to stand alone as a name rather than appear anywhere inside one, so Tests are 17 harvesting spellings, 23 ordinary or inverted lookups, the backtracking bound at two input sizes, and the CLI fixture. Worst case I could build is 4 ms on a 50 KB adversarial line. Known gaps I left alone, all of them inherited rather than new: an attached short-flag value ( |
Fixes #482.
E2_OTHER_PATTERNS(static_patterns_data_exfiltration.py:73) matchedenv | grep, one optional-i, then a bare keyword. Any second flag, combined flags, a quoted pattern oregrepfell through, soenv | grep -i -E 'token|key|secret' > /tmp/ctx.txtscanned clean whileenv | grep secretscored HIGH.The pattern now accepts
envorprintenvas the source,grep,egreporfgrepas the filter, any run of short or long flags, an optional opening quote, and the keyword within the first 40 characters of the pattern argument. Three guards keep the wider match from costing precision: the keyword has to start at a name boundary (AWS_SECRET_ACCESS_KEYmatches,MONKEY_PATCHdoes not), the argument scan stops at quotes, backticks,;,>,&,#and newline, and-v/--invert-matchis excluded because inverting the match is the redaction idiom rather than harvesting. The flag run is possessive, so a long run of flags is linear rather than exponential; a test pins that at 60 repetitions.Static scan,
--no-llm, oneSKILL.mdwith one fenced line:env | grep secretenv | grep -i -E 'token|key|secret' > /tmp/ctx.txtenv | grep -iE "aws_|secret"env | grep --ignore-case tokenenv | egrep -e password -e tokenenv | grep AWS_SECRET_ACCESS_KEYenv | grep PATHenv | grep MONKEY_PATCHprintenv | grep -v -E 'KEY|SECRET|TOKEN'dotenv | grep KEYTests: eight harvesting spellings, nine ordinary or inverted lookups and a backtracking bound in
tests/unit/test_patterns.py, plustests/fixtures/e2_shell_env_harvest/with a CLI regression test intests/unit/test_cli.py. Full suite 3978 to 3997 passed, same 14 skipped and 4 xfailed either side.ruff checkandruff format --checkclean.Deliberately left out:
env > file,export -pandset(ordinary debugging uses, no keyword to key on), non-grep filters such asrgandawk, and filters behind an intermediate stage such asenv | sort | greporenv | tee. Confidence stays at 0.8.