Close scope-gate bypass: curl/wget were invisible to the PreToolUse hook - #48
Merged
Conversation
scope_gate_hook.py's TIER2_BASH_TOOLS only listed the 8 binaries HuntMCP's own MCP servers shell out to (subfinder/httpx/katana/nmap/nuclei/sqlmap/ dalfox/ffuf). curl/wget were left out on the reasoning that no dedicated MCP server wraps them -- but that meant a raw `curl https://target.com/...` bypassed scope enforcement entirely, invisible to the hook. Surfaced while reviewing an external curl-heavy skill library earlier this session. Added curl/wget to the checked set, plus three things needed to make that safe rather than just noisy: - DEV_INFRA_HOSTS allowlist (GitHub/PyPI/npm/Go-proxy/Debian-Ubuntu mirrors) so ordinary package-fetching curls stay exempt, same pattern as the existing SAFE_TEST_HOSTS exemption for example.com/localhost. - Real URL parsing (urlsplit) instead of a blanket regex over the whole command line, so a URL's own path segment (.../file.txt) is never mistaken for a second hostname requiring scope. - A file-extension denylist for the bare-hostname fallback scan, so `curl -o results.json` / `-d @payload.json` -- the two most common curl invocation shapes -- don't false-positive on the output/input filename. evil.com/attacker.com/malicious.com were added to the existing safe-host list too: a CORS/CSRF PoC's `-H "Origin: https://evil.com"` names the attacker's own probe origin in a header value, not a live target. Extraction is still a blanket regex for non-URL bare-domain args (subfinder -d target.com style) -- deliberately not trying to distinguish "target" from "quoted header value" there by stripping quotes, since that risks also stripping a legitimately-quoted target URL and failing OPEN instead of just over-blocking. A false-positive block is the safe failure mode; a false-negative allow is not.
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
User pointed out that my earlier "raw bash bypasses scope/budget/audit enforcement" claim needed verification, not just assertion — specifically asked to make sure bypass isn't possible where it actually matters (scope). Checked the real mechanism (
scripts/hooks/scope_gate_hook.py, wired via.claude/settings.json'sPreToolUsehook with matcher"*"— it does intercept every Bash call, not just MCP tool calls). Found the actual gap: itsTIER2_BASH_TOOLSset only lists the 8 binaries HuntMCP's own MCP servers shell out to.curlwas never in it — exactly the tool therecon-skillslibrary (reviewed earlier this session) uses throughout. A rawcurl https://out-of-scope-target.com/...was completely invisible to the scope gate.Fixed by adding
curl/wgetto the checked set. That surfaced two more real false-positive risks along the way (both verified with actual reproductions before fixing):curl https://raw.githubusercontent.com/...) — added aDEV_INFRA_HOSTSallowlist (GitHub/PyPI/npm/Go-proxy/Debian-Ubuntu mirrors), same pattern as the existingSAFE_TEST_HOSTSexemption.curl -o results.json https://target.com/...was extractingresults.jsonas if it were a second hostname (the existing hostname regex can't distinguish a domain from a filename by pattern alone). Fixed with real URL parsing (urlsplit) instead of blanket regex, plus a small file-extension denylist for the bare-hostname fallback path. Also addedevil.com/attacker.com/malicious.comto the safe-host list, since a CORS PoC's-H "Origin: https://evil.com"names the attacker's own probe origin, not a live target.One deliberate remaining tradeoff, documented in code comments: the fallback bare-hostname scan (for non-URL args like
subfinder -d target.com) is still a blanket regex over the whole command line, not argument-aware. Distinguishing "the real target" from "a quoted header value" by stripping quoted substrings was considered and rejected — it risks also stripping a legitimately-quoted target URL, which fails OPEN instead of just over-blocking. A false-positive block is the safe failure mode here; a false-negative allow is not.Test plan
-o fileand-d @fileshapes), attacker-origin-placeholder exemption, plus end-to-endmain()-level block/allow for a raw curlscope_gate_hooktests still pass unchangedARCHITECTURE.md's scope-enforcement row updated to describe the current binary list and the correction, matching this repo's existing "Correction" documentation convention