fix(e2): exempt child-process env pass-through from harvesting - #492
Open
malinfossum wants to merge 1 commit into
Open
fix(e2): exempt child-process env pass-through from harvesting#492malinfossum wants to merge 1 commit into
malinfossum wants to merge 1 commit into
Conversation
E2 fired HIGH at 0.6 confidence on `subprocess.run(cmd, env={**os.environ,
...})` and on an `os.environ.copy()` bound to a name and passed as `env=`,
which is the standard way to hand an environment to a child process. A real
harvester and the most common benign idiom were indistinguishable in the
report.
The analyzer's own docstring already excluded this case: a full mapping copy
is a harvesting signal "unlike a targeted single-key lookup or passing
os.environ through to a child process". The code did not implement the second
half, because it keyed on the copy rather than on where the copy goes.
Collect the expressions passed as `env=` to a known process launcher, plus the
names bound to them, and skip those at emit time. An environ copy that goes
anywhere else still fires, and network and execution sinks remain the
behavioral taint analyzer's job.
Fixes NVIDIA#441
Signed-off-by: Malin Fossum <malinfossum.dev@proton.me>
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
E2no longer fires on anos.environcopy whose only destination is a child process'senv=. An environ copy that goes anywhere else is unchanged, and so areE1,E3–E5, the regex fallback for unparsable Python, and every non-Python path.Fixes #441.
Root cause
_analyze_python_environment_readskeys on the copy rather than on where the copy goes, sosubprocess.run(cmd, env={**os.environ, "GIT_OPTIONAL_LOCKS": "0"})reached the sameemit()at the same HIGH severity and the same 0.6 confidence as a real harvester. The analyzer's docstring already excluded this case — a full mapping copy is a harvesting signal "unlike a targeted single-key lookup or passingos.environthrough to a child process" — but only the first half was implemented.The fix collects, per file, the expressions passed as
env=to a known process launcher (subprocess.run/call/check_call/check_output/Popen,asyncio.create_subprocess_exec/_shell) plus the plain names bound to them, and skips those nodes at emit time. Two shapes are covered: the mapping written inline at the call site, and one built on an earlier line and passed by name.I kept this to an allowlist rather than exempting any
env=keyword, so a call to an arbitrary function named with anenv=argument is not a way to silenceE2.Validation
A skill with the issue's three benign variants plus one real harvester (
requests.post(url, json=dict(os.environ))), scanned with--no-llm:variants.py:8env={**os.environ, ...}variants.py:13env = os.environ.copy()harvester.py:7dict(os.environ)→requests.postThe subprocess calls themselves still surface as
AST4, so the behavior is not hidden — only the harvesting claim about it is withdrawn.Three tests added to
TestRunStaticPatternsDataExfiltration, written before the fix and confirmed failing againstmain: one per benign shape, plus a guard that an environ copy bound to a name and sent torequests.poststill fires.pytest tests/nodes/analyzers/test_static_patterns.py -k e2— 7 passedpytest -m "not integration and not provider" tests/— 3954 passed, 26 skipped, 4 xfailed, 22 failedruff check src/ tests/— cleanruff format --check src/ tests/— cleanThe 22 failures are pre-existing on a Windows host and unrelated to this change: I ran the same four files on
mainwith this branch stashed and got the identical 22 (test_build_context.pysymlink and secure-open cases,test_compare_scan_accuracy.py— which is #485 —test_create_github_release.py, and onetest_input_handler.pycase). No test outsidetest_static_patterns.pychanges state with this patch applied.Scope
This fixes the
E2precision slice only. It does not touchE2's severity or confidence values, the#329broadening that made the rule match{**os.environ, ...}in the first place, orE1/taint coverage of credential flows to network sinks.