Skip to content

feat(plugins): stage agent-visible plugin bundles with digest verification and agent env scrub - #89

Open
dmorosanu wants to merge 4 commits into
mainfrom
feat/agent-skill-bundle
Open

feat(plugins): stage agent-visible plugin bundles with digest verification and agent env scrub#89
dmorosanu wants to merge 4 commits into
mainfrom
feat/agent-skill-bundle

Conversation

@dmorosanu

Copy link
Copy Markdown
Contributor

What

Stages an allowlisted, digest-verified copy of every agent.plugins[{type: local}] source and hands the agent that path instead of the raw checkout.

  • src/coder_eval/plugin_bundle.py — builds a bundle per resolved source path (once per process, thread-safe, cached) under a temp staging root:

    1. Subtree allowlist — only Claude Code's plugin-discovery subtrees are staged: skills, commands, agents, .claude-plugin, hooks. An allowlist, not a denylist, so a newly added answer-bearing top-level directory is excluded by default.
    2. File-level manifest — every staged file is declared (bundle-relative path -> sha256). A recognizable grading artifact inside an allowed subtree (RESOLUTION.md, check_*.py) fails the build loudly rather than shipping. A symlink whose target escapes the source root fails the build; in-root symlinks are copied verbatim, never followed (loop-proof).
    3. Runtime digest verification — the staged bundle is re-hashed against its recorded manifest digest before every agent start. Any drift (tampered, added, or missing file) aborts the run.

    Every layer fails closed: a violation raises PluginBundleError and nothing ever falls back to the raw path.

  • orchestrator.py_stage_agent_plugin_bundles() rewrites agent.plugins[].path to the bundle before agent creation, so the projection is driver-independent (tempdir and docker alike) and lands at a single choke point. The resulting digests are recorded in environment_info["plugin_bundles"]. Skipped when CODER_EVAL_IN_CONTAINER is set, where the docker driver's own staging surface already applies.

  • Agent env scrub (utils.py + the three agent spawn seams) — SKILLS_REPO_PATH and the CODER_EVAL_* prefix are masked from every agent subprocess. The overrides are explicit empty values, not omissions: the SDK merges {**os.environ, **options.env}, so an omitted key would simply be inherited. Antigravity, which spawns its harness off os.environ, scrubs and restores inside the existing spawn-guard window.

  • Lint rule CE034 (tests/lint/rules/ce034_plugin_bundle_choke_point.py) — keeps the staging call at its single choke point so a future agent cannot re-introduce a raw plugin path.

Why

A task's local plugin path typically expands to an entire skills checkout. Handed to the agent raw, that path also exposes the repo's own grading material — RESOLUTION.md reference answers, check_*.py grader scripts, and tests/ fixtures with golden outputs — giving the evaluated agent unintended access to the answer key on every driver, not just one. The bundle projects only the plugin-consumable subtrees, so the agent sees the skills it is meant to read and nothing else.

Grading is unaffected: run_command criteria and the sandbox environment keep resolving $SKILLS_REPO_PATH to the raw checkout (covered by a dedicated test), because the scrub applies at agent-spawn seams only.

Validation

make verify on the rebased branch (Windows, Python 3.13):

  • ruff format --check — 362 files already formatted
  • ruff check — all checks passed
  • pyright — 0 errors (1 pre-existing warning in antigravity_agent.py, unchanged from main)
  • custom lint (tests/test_custom_lint.py) — 177 passed, CE034 registered and unique
  • full suite with coverage — 3947 passed, 105 skipped, total coverage 90.33% (gate 80%)

New tests: tests/test_plugin_bundle.py (26 cases) covering the adversarial projection, hidden-material detection inside allowed subtrees, symlink safety, digest verification and fail-closed drift, once-per-source and concurrent staging, the orchestrator seam (including the in-container skip), and the env scrub vs. grading-env split. Plus scrub coverage in tests/test_codex_agent.py / tests/test_antigravity_agent.py and a CE034 rule test.

The 105 skips are the usual POSIX-only cases on Windows (docker driver, symlinks, mode bits). Two tests/test_sandbox.py failures on this machine (WinError 1314, symlink creation without Developer Mode) are pre-existing on main and touch files this branch does not modify.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Claude finished @dmorosanu's task in 1m 31s —— View job


🔍 Reviewing PR #89: Plugin Bundle Staging with Digest Verification

Working on review...

Todo List:

  • Read .github/code_review.md for review guidelines
  • Review CLAUDE.md for project conventions
  • Analyze the full diff (git diff origin/main...HEAD)
  • Read and review each changed file completely
  • Check cross-file consistency
  • Analyze what might be missing
  • Provide design-level scrutiny
  • Format and post final review

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