Severity: P1 (RCE)
Confirmed end-to-end this evening (2026-05-18 06:46 UTC) by adversarial code review + independent empirical re-verification on the deployed contextcrawler 0.1.7 binary. Not a false alert.
What
ripgrep honors two env vars (RIPGREP_CONFIG_PATH, RIPGREP_CONFIG_FILE) that load a config file. That config file can contain --pre=<script> and --pre-glob=*, which makes rg execute <script> as a per-file preprocessor. Both contextcrawler grep paths (the canonical filter at src/cmds/system/grep_cmd.rs AND the new format-flag passthrough at src/main.rs:run_grep_format_passthrough) invoke rg as a subprocess without clearing these env vars.
A user whose shell env has RIPGREP_CONFIG_PATH set to an attacker-controlled file gets arbitrary code execution on their next contextcrawler grep ... call. No CLI arguments need to differ from normal usage — the attack is invisible to the user typing contextcrawler grep foo bar.
Attack surface that elevates severity in this codebase:
- contextcrawler is invoked by hooks for both Claude Code (auto-wrap on every Bash tool call) and Codex (AGENTS.md MUST rule, ~80% adoption today). A tainted env → any agent invocation that touches grep → silent code execution.
- The Tirith integration + the supply-chain audit posture (per CHANGELOG v0.1.6) set user expectations that contextcrawler defends against this class.
- Vector requires only the ability to set ONE env var. Common compromise paths: malicious npm postinstall, supply-chain
pip install, hostile dotfile drop, dirty CI runner.
Reproduction (verified end-to-end, 4 variants)
(Redacted to not seed a working PoC in a public issue. Full reproduction lives in the corresponding fix-PR's negative-regression test fixture under tests/security/.)
Summary of what was verified:
- Canonical grep path + env var alone (no CLI
--pre) → preprocessor fired. Confirmed via PID/UID-logged file.
- New format-flag bypass path + env var alone → preprocessor fired.
- Canonical grep path with
--pre in extra_args (no env var) → preprocessor fired.
- Control case: raw
rg with env var (no contextcrawler) → preprocessor fired (confirms rg's design behavior; contextcrawler inherits it).
All four logged matching PID/PPID lineage as the user's uid.
Affected versions: contextcrawler 0.1.7 (deployed today) and likely all prior versions that called rg without env-clearing (this is a long-standing pre-existing exposure that was uncovered by today's security review, NOT a regression introduced by today's PRs).
Fix (proposed)
Two changes, both applied to both call sites that spawn rg (src/main.rs::run_grep_format_passthrough AND src/cmds/system/grep_cmd.rs::run):
- Clear the rg config env vars before spawning:
Command::new(rg_path)
.env_remove("RIPGREP_CONFIG_PATH")
.env_remove("RIPGREP_CONFIG_FILE")
.args(user_args)
...
- Reject
--pre, --pre-glob, --search-zip in user args (both the canonical extra_args and the bypass's user_args) with a helpful error pointing at contextcrawler proxy rg ... for users with a legitimate need. These flags exist for power users; agents and hooks shouldn't be forwarding them.
Regression tests (must accompany the fix)
tests/security/grep_env_rce.rs (new):
- Negative test: with
RIPGREP_CONFIG_PATH pointing at a config that would fire a preprocessor, run contextcrawler grep and assert the preprocessor file marker does NOT appear.
- Negative test: same for
RIPGREP_CONFIG_FILE.
- Negative test: passing
--pre /tmp/... directly returns a rejection (exit non-zero with the documented error).
- Positive test: normal
grep invocations (no env, no --pre) still work.
These tests become the regression gate — any future change that re-enables env-var inheritance or --pre forwarding fails CI.
Out of scope (separate issues)
--search-zip reads zip archives; zip-slip is rg's own surface but worth a docs/notes mention. File as P3 follow-up.
- PATH-based binary substitution of
rg itself (which::which() trusts $PATH) — standard exposure, separate hardening ticket.
- Resource-cap parity gap (the bypass uses inherited stdio +
.status() vs canonical's exec_capture() with 64 MiB caps + stdin-null). Separate P2 follow-up.
References
- Adversarial codex review output:
/tmp/security-review-grep.txt (local, redacted before publishing)
- Independent verification log: this session's terminal output 2026-05-18 06:43-06:46 UTC
- Affected functions:
src/main.rs:1469-1535 (today's bypass)
src/cmds/system/grep_cmd.rs:43-48 (canonical, pre-existing)
src/core/utils.rs:resolved_command (rg path resolution — separate hardening ticket)
Severity: P1 (RCE)
Confirmed end-to-end this evening (2026-05-18 06:46 UTC) by adversarial code review + independent empirical re-verification on the deployed
contextcrawler 0.1.7binary. Not a false alert.What
ripgrephonors two env vars (RIPGREP_CONFIG_PATH,RIPGREP_CONFIG_FILE) that load a config file. That config file can contain--pre=<script>and--pre-glob=*, which makes rg execute<script>as a per-file preprocessor. Both contextcrawler grep paths (the canonical filter atsrc/cmds/system/grep_cmd.rsAND the new format-flag passthrough atsrc/main.rs:run_grep_format_passthrough) invoke rg as a subprocess without clearing these env vars.A user whose shell env has
RIPGREP_CONFIG_PATHset to an attacker-controlled file gets arbitrary code execution on their nextcontextcrawler grep ...call. No CLI arguments need to differ from normal usage — the attack is invisible to the user typingcontextcrawler grep foo bar.Attack surface that elevates severity in this codebase:
pip install, hostile dotfile drop, dirty CI runner.Reproduction (verified end-to-end, 4 variants)
(Redacted to not seed a working PoC in a public issue. Full reproduction lives in the corresponding fix-PR's negative-regression test fixture under
tests/security/.)Summary of what was verified:
--pre) → preprocessor fired. Confirmed via PID/UID-logged file.--preinextra_args(no env var) → preprocessor fired.rgwith env var (no contextcrawler) → preprocessor fired (confirms rg's design behavior; contextcrawler inherits it).All four logged matching PID/PPID lineage as the user's uid.
Affected versions: contextcrawler 0.1.7 (deployed today) and likely all prior versions that called
rgwithout env-clearing (this is a long-standing pre-existing exposure that was uncovered by today's security review, NOT a regression introduced by today's PRs).Fix (proposed)
Two changes, both applied to both call sites that spawn
rg(src/main.rs::run_grep_format_passthroughANDsrc/cmds/system/grep_cmd.rs::run):--pre,--pre-glob,--search-zipin user args (both the canonicalextra_argsand the bypass'suser_args) with a helpful error pointing atcontextcrawler proxy rg ...for users with a legitimate need. These flags exist for power users; agents and hooks shouldn't be forwarding them.Regression tests (must accompany the fix)
tests/security/grep_env_rce.rs(new):RIPGREP_CONFIG_PATHpointing at a config that would fire a preprocessor, runcontextcrawler grepand assert the preprocessor file marker does NOT appear.RIPGREP_CONFIG_FILE.--pre /tmp/...directly returns a rejection (exit non-zero with the documented error).grepinvocations (no env, no--pre) still work.These tests become the regression gate — any future change that re-enables env-var inheritance or
--preforwarding fails CI.Out of scope (separate issues)
--search-zipreads zip archives; zip-slip is rg's own surface but worth a docs/notes mention. File as P3 follow-up.rgitself (which::which()trusts $PATH) — standard exposure, separate hardening ticket..status()vs canonical'sexec_capture()with 64 MiB caps + stdin-null). Separate P2 follow-up.References
/tmp/security-review-grep.txt(local, redacted before publishing)src/main.rs:1469-1535(today's bypass)src/cmds/system/grep_cmd.rs:43-48(canonical, pre-existing)src/core/utils.rs:resolved_command(rg path resolution — separate hardening ticket)