Skip to content

Classify dev-env auto-exec paths as sensitive and broaden .envrc#134

Merged
JAORMX merged 1 commit intomainfrom
sec/sensitive-dev-exec-paths
Apr 17, 2026
Merged

Classify dev-env auto-exec paths as sensitive and broaden .envrc#134
JAORMX merged 1 commit intomainfrom
sec/sensitive-dev-exec-paths

Conversation

@JAORMX
Copy link
Copy Markdown
Contributor

@JAORMX JAORMX commented Apr 17, 2026

Summary

Closes a MEDIUM finding: modern dev tooling has files whose contents are executed by routine developer actions (npm install, pip install, direnv allow && cd, F5 in VSCode, Reopen in Container). An agent in auto-accept mode could quietly plant malicious scripts in these files — they'd flush back to the host and run on the user's next unrelated workflow step.

Changes

New Tier 2 (warn-but-flush) sensitive-path entries, narrowly targeted to actual exec surfaces:

Path Exec trigger
.vscode/tasks.json F5 / Run Task
.vscode/launch.json Debug start
.devcontainer.json, .devcontainer/devcontainer.json Reopen in Container
package.json (basename) npm install / yarn install scripts
pyproject.toml (basename) pip install / poetry install hooks
setup.py (basename) pip install . / python setup.py
.envrc (basename) direnv allow && cd

Deliberately skipped to avoid noise on routine edits:

  • .vscode/settings.json, .vscode/extensions.json — no exec surface
  • .devcontainer/Dockerfile and other .devcontainer/* non-entrypoints
  • .idea/ — JetBrains UI state
  • Lockfiles (package-lock.json, poetry.lock)

UX trade-offs

Every new entry is Tier 2 (warn, still flush). Tier 1 would have broken Node/Python workflows because agents routinely edit package.json, pyproject.toml, etc. as part of legitimate work. The stderr warning surfaces the concern without blocking.

.envrc re-tiered from 1 → 2

Pre-patch: Tier 1 (auto-reject). Post-patch: Tier 2 (warn-but-flush). Rationale:

  • direnv itself gates execution behind direnv allow after every .envrc change — a user-action-required step, same risk profile as npm install triggering package.json scripts.
  • Auto-reject in auto-accept mode would destroy the agent's entire session work (the COW snapshot is discarded at cleanup), not just the .envrc — particularly painful in direnv-heavy monorepos (Nix, Rust, Ruby).
  • Also changed from exactRootRule to basenameRule for correctness: direnv walks upward, so a subdir .envrc fires on cd sub/.

REJECTED line now self-documents recovery

REJECTED: <path> — <reason> (re-run with --review to approve) instead of requiring the user to read the summary footer. Any remaining Tier 1 rejection (e.g. .git/hooks/) prints the recovery hint on the same line as the file name.

New helper

pathSuffixRule does separator-aware suffix matching — .vscode/tasks.json at any depth matches, but foo.vscode/tasks.json (filename ending with ".vscode") correctly does NOT.

Test plan

  • task fmt && task lint && task test — all green
  • Comprehensive TestClassifyPath coverage: every new entry at workspace root + monorepo-nested path + negative controls (settings.json, Dockerfile, lockfiles, foo.vscode/tasks.json false-positive)
  • TestAutoAcceptReviewer_MixedTiers updated for the .envrc re-tier
  • End-to-end: workspace with package.json + .vscode/tasks.json + README.md; agent edits all three; stderr shows two WARNINGs (package.json, tasks.json); all three files flush; VM cycles clean

Follow-ups (not this PR)

  • Ruby (Gemfile, Rakefile) and Rust (build.rs) install-time exec surfaces — same class, deferred.
  • Monorepo multiplier: 10× package.json edits → 10 warnings. Could dedup in the summary.
  • Reject-then-persist sidecar so users hitting Tier 1 can recover content without re-running the session.

🤖 Generated with Claude Code

Modern dev tooling has a handful of files whose contents are
executed by routine developer actions — IDE opening, `npm install`,
`pip install`, `cd` into a direnv-managed dir. Previously none of
these were flagged, so an agent in auto-accept mode could quietly
plant malicious scripts that the host user would run on their next
unrelated workflow step.

Changes:

- `.envrc` changes from exactRootRule to basenameRule AND from
  TierAutoExec to TierBuildCI. direnv walks upward from the user's
  shell cwd, so a subdir `.envrc` fires on `cd sub/` — same auto-
  exec surface as the root case. The tier downgrade reflects that
  direnv itself gates execution behind `direnv allow` after each
  `.envrc` change (user-action-required), making this consistent
  with how `package.json` scripts require `npm install`. Keeping
  Tier 1 would have destroyed agent work on direnv-heavy monorepos
  because a rejected flush discards the entire session's changes.

- New Tier 2 (warn-but-flush) entries for the genuinely exec-
  surfaced dev files only, staying targeted to avoid noisy warnings
  on routine edits:
    - `.vscode/tasks.json` and `.vscode/launch.json` — execute on
      F5 / Run Task (skipped: settings.json / extensions.json which
      carry no exec surface).
    - `.devcontainer.json` and `.devcontainer/devcontainer.json` —
      lifecycle commands execute on Reopen in Container (skipped:
      other files in .devcontainer/ which users edit routinely).
    - `package.json` — scripts.{pre,post}{install,prepare,…} execute
      on npm/yarn install. Basename so monorepo per-package manifests
      are caught too.
    - `pyproject.toml`, `setup.py` — script hooks execute on pip /
      poetry install.

New helper `pathSuffixRule` does separator-aware suffix matching so
`.vscode/tasks.json` at any depth matches while `foo.vscode/tasks.json`
(a filename ending with ".vscode") correctly does not.

UX: the auto-accept reviewer's per-file REJECTED line now embeds the
"re-run with --review to approve" hint directly, so users who hit a
Tier 1 rejection see the recovery path on the same line without
having to scroll to the summary footer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JAORMX JAORMX merged commit 3a803a1 into main Apr 17, 2026
8 checks passed
@JAORMX JAORMX deleted the sec/sensitive-dev-exec-paths branch April 17, 2026 09:47
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.

1 participant