Harden agent capabilities/settings against untrusted scanned repos - #10
Open
schenksj wants to merge 8 commits into
Open
Harden agent capabilities/settings against untrusted scanned repos#10schenksj wants to merge 8 commits into
schenksj wants to merge 8 commits into
Conversation
…nfig.py Prerequisite: the agent package did not parse, blocking all tests. Co-Authored-By: Claude Code (VulnFix)
A readonly scan of an untrusted repo previously passed --allowedTools Read Write Edit Bash Agent with --permission-mode acceptEdits unconditionally, so prompt-injected content in the scanned repo could drive arbitrary Bash/Write/Edit on the host. Gate on the readonly flag: readonly scans now use a read-only tool set (Read/Grep/Glob/Agent) and --permission-mode plan. Non-readonly default callers are unchanged. VulnHunter-Finding: CANON-03 Co-Authored-By: Claude Code (VulnFix)
run_vulnhunt and verify_runner passed setting_sources=[user,project,local] while cwd was the untrusted cloned repo, so the scanned repo's .claude/settings(.local).json (hooks, permissions) and its skills loaded and could execute on the host. Restrict setting_sources to [user] in both runners; the trusted vulnhunt / vulnhunt-fix-verify skills live in the user dir, so scan/verify functionality is unaffected. VulnHunter-Finding: CANON-19 Co-Authored-By: Claude Code (VulnFix)
…l set Follow-up to 01cb350 after independent fix-verify found the prior readonly-gated fix did not cover the default scan path (readonly defaulted to False and the untrusted-repo scan entry points never passed it), so the RCE-capable tool set was still used by default. - scan_folder / scan_folder_with_retry / scan_targets now default readonly=True. - Read-only tool set = Read, Write, Edit, Agent, AskUserQuestion, Grep, Glob (no Bash) — prompt-injected scanned-repo content can no longer drive host command execution by default. Only an explicit opt-out (readonly=False) re-adds Bash for scans that must execute. - batch CLI: `--readonly` (opt-in) replaced by `--execute` (opt-out); default is now a read-only scan. - Updated pre-existing test_batch_run.py for the --execute semantics. Residual (documented, out of scope for a code patch): a read-only scan still permits Write/Edit within the --add-dir roots; full isolation of untrusted-repo scanning requires process sandboxing. Co-Authored-By: Claude Code (VulnFix)
…scan Benchmark's phase_scan inherited the new readonly=True default with no way to restore execution. Add --execute (store_true, default False) to main(), thread it through phase_scan(..., execute=False), and pass readonly=not execute at the scan_targets call. Safe default (read-only) preserved; opt-out is now explicit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ermission_mode Fix B: a read-only scan still granted write to the parent dir via --add-dir os.path.dirname(folder_path). Under acceptEdits + Write/Edit, injected scanned-repo content could overwrite sibling clones. Remove that --add-dir entirely (results are written under folder_path=cwd; sibling access unneeded). Keep folder_path/SKILLS_DIR/PHASES_DIR. Update CANON-03 comment to disclose the residual: SKILLS_DIR/PHASES_DIR remain readable AND writable under acceptEdits; closing that needs finer per-path controls (out of scope). Add test asserting the parent dir is NOT among --add-dir values for a readonly scan (folder_path is). Fix E(1): permission_mode was identically 'acceptEdits' in both branches; inline it at the Popen call so it is not misleadingly branch-shaped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bash(find:*) permits 'find -exec <cmd>' = arbitrary command execution over the untrusted repo_dir. Remove it from invoke_diagnostic's --allowedTools (keep the read-only grep/wc/ls/head/tail/cat). Comment notes the residual: remaining Bash read commands can still read host files; full hardening would drop Bash for Read/Grep/Glob. Add test asserting Bash(find:*) is not in the constructed cmd. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oken_path_prefixes parse Fix D: CANON-19 verify half was untested. Assert the recorded SDK options.setting_sources == ['user'] so a regression to project/local is caught. Fix E(2): guard token_path_prefixes semantics (not just importability) with a config that includes a whitespace-only prefix; assert the blank is dropped and the rest kept/stripped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Adversarial review + fixes applied (pushed
Suites: harness 162 passed, |
3 tasks
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.
Harden agent capabilities/settings against untrusted scanned repos
Tightens two places where content from the scanned repository can influence the launched Claude agent's capabilities or settings, plus a small prerequisite build fix. These came out of the cross-model
/vulnhuntevaluation (eval_rep/). Each change is developed test-first: a security test that fails on the current code and passes after the fix, with the existing suite kept green.Prerequisite —
config.pycurrently doesn't parseCommit
a35b0ff.vulnhunter-agent/agent/config.pyhas an unclosed paren — thetoken_path_prefixes=tuple(...)generator is missing its),beforemax_comment_pages=int(— soimport agent.configraisesSyntaxErrorand the agent package (and its test suite) can't be imported. This adds the missing),and a small regression test (tests/test_config.py) that the module imports andload_configis callable.CANON-03 — Default scans to read-only; drop Bash from the scan tool set (CWE-94/CWE-78/CWE-77/CWE-269)
Commits
01cb350,948cf42.harness/local_harness/scan.pylaunchedclaudewith--allowedTools Read Write Edit Bash Agent --permission-mode acceptEdits, and thereadonlyargument only adjusted prompt text — it defaulted toFalseand the untrusted-repo scan entry points (batch,benchmark) never set it, so the default scan grantedBash(command execution) on the scanned repo.scan_folder/scan_folder_with_retry/scan_targetsnow defaultreadonly=True. The read-only tool set isRead Write Edit Agent AskUserQuestion Grep Glob— noBash— so prompt-injected scanned-repo content can no longer drive host command execution by default. Only an explicit opt-out (readonly=False) re-addsBashfor scans that must execute code. ThebatchCLI flag--readonly(opt-in) is replaced by--execute(opt-out).harness/tests/test_scan.py::test_scan_folder_default_is_readonly_no_bash/::test_scan_folder_readonly_no_bash/::test_scan_folder_execute_optin_grants_bash;test_batch_run.pyupdated for the--executesemantics. Fail on the pre-rework code, pass after.Write/Editwithin the--add-dirroots; fully isolating untrusted-repo scanning requires process sandboxing, which is out of scope for a code patch. (This scoping followed an independentvulnhunt-fix-verifypass that found the initialreadonly-gated fix left the default path unprotected.)CANON-19 — Only load trusted (user) settings sources (CWE-913/CWE-269)
Commit
f57db26.run_vulnhuntbuiltClaudeAgentOptions(setting_sources=["user","project","local"], skills="all", cwd=clone_dir). Becausecwdis the cloned repo under scan,"project"/"local"cause the scanned repo's.claude/settings*.jsonand skills to be loaded from that directory.setting_sources=["user"]in bothagent/runner.pyandagent/verify_runner.py(the pattern appeared in both). Checked against the installed SDK: passing["user"]explicitly meansskills="all"discovers only user-dir skills (the trusted vulnhunt skills), notclone_dir/.claude/skills— so scan/verify behavior is preserved.vulnhunter-agent/tests/test_runner.py::test_run_vulnhunt_does_not_load_cloned_repo_settings. Fails on current code (['user','project','local']), passes after (['user']).Verification
harness: 160 passedvulnhunter-agent: 1111 passed, 1 skippedvulnhunt-fix-verifypass.🤖 Generated with Claude Code