Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(e2): match shell env harvesting across grep flags and quoting - #483

Open
MandoCodes1 wants to merge 1 commit into
NVIDIA:mainfrom
MandoCodes1:fix/e2-shell-env-harvest
Open

fix(e2): match shell env harvesting across grep flags and quoting#483
MandoCodes1 wants to merge 1 commit into
NVIDIA:mainfrom
MandoCodes1:fix/e2-shell-env-harvest

Conversation

@MandoCodes1

Copy link
Copy Markdown

Fixes #482.

E2_OTHER_PATTERNS (static_patterns_data_exfiltration.py:73) matched env | grep, one optional -i, then a bare keyword. Any second flag, combined flags, a quoted pattern or egrep fell through, so env | grep -i -E 'token|key|secret' > /tmp/ctx.txt scanned clean while env | grep secret scored HIGH.

The pattern now accepts env or printenv as the source, grep, egrep or fgrep as 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_KEY matches, MONKEY_PATCH does not), the argument scan stops at quotes, backticks, ;, >, &, # and newline, and -v / --invert-match is 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, one SKILL.md with one fenced line:

spelling before after
env | grep secret E2 0.8 E2 0.8
env | grep -i -E 'token|key|secret' > /tmp/ctx.txt none E2 0.8
env | grep -iE "aws_|secret" none E2 0.8
env | grep --ignore-case token none E2 0.8
env | egrep -e password -e token none E2 0.8
env | grep AWS_SECRET_ACCESS_KEY none E2 0.8
env | grep PATH none none
env | grep MONKEY_PATCH none none
printenv | grep -v -E 'KEY|SECRET|TOKEN' none none
dotenv | grep KEY E2 0.8 none

Tests: eight harvesting spellings, nine ordinary or inverted lookups and a backtracking bound in tests/unit/test_patterns.py, plus tests/fixtures/e2_shell_env_harvest/ with a CLI regression test in tests/unit/test_cli.py. Full suite 3978 to 3997 passed, same 14 skipped and 4 xfailed either side. ruff check and ruff format --check clean.

Deliberately left out: env > file, export -p and set (ordinary debugging uses, no keyword to key on), non-grep filters such as rg and awk, and filters behind an intermediate stage such as env | sort | grep or env | tee. Confidence stays at 0.8.

@rng1995 rng1995 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 as env | 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 -v while preserving the real grep -v redaction case.

Required CI is green, but this detection bypass and the BEHIND merge state block merge.

@MandoCodes1
MandoCodes1 force-pushed the fix/e2-shell-env-harvest branch from 8981765 to ae8e731 Compare September 13, 2026 19:28
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]>
@MandoCodes1
MandoCodes1 force-pushed the fix/e2-shell-env-harvest branch from ae8e731 to ea37182 Compare September 13, 2026 19:55
@MandoCodes1

Copy link
Copy Markdown
Author

Good catch, and thanks for the precise repro. Fixed in ea37182, which also rebases onto current main.

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:

(?!(?:[^\S\n]+-\S*)*[^\S\n]+-(?:\w*v\w*|-inv[\w-]*)(?![\w-]))

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 grep -v redaction cases stay clean:

input before after
env | grep SECRET > /tmp/out # -v clean E2
env | grep SECRET; echo -v clean E2
env | grep -i token # redact with -v before sharing clean E2
env | grep -v SECRET clean clean
env | grep -iv SECRET clean clean
env | grep --invert-match SECRET clean clean
env | grep TERM | grep -v SECRET clean clean

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. --invert and --inv are working abbreviations of --invert-match in GNU grep and were scoring as harvesting. And an inverting flag that follows an option written with an attached value, as in env | grep --color=auto -v SECRET, is now seen, where a check that stopped at the first non-flag word would have missed it.

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 env | grep XKB_DEFAULT_KEYMAP, env | grep -i keyboard and env | grep -i tokenizer no longer score as secret lookups. Plurals and numbered names still match, so KEYS, TOKENS and KEY2 are unaffected.

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 (grep -eSECRET), a pattern list whose earlier element is quoted (grep -e '^AWS' -e SECRET), a literal -v passed as an argument to -e, and flags written after the pattern (grep SECRET -v), which GNU grep permutes but this pattern scores as harvesting. Each needs the option walk to model grep's argument-taking flags properly. Happy to take that on in a follow-up if you want it covered.

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.

E2 shell arm matches one env | grep spelling: a second grep flag, quotes, or egrep all evade "Env Variable Harvesting"

2 participants