The Technique — Smart WebFetch Permissions with Hooks
Every time Claude Code fetches a URL, it asks for permission. After dozens of approvals for the same Python documentation page, you start looking for an auto-approve button. The naive solution is adding "WebFetch" to your permissions.allow list in ~/.claude/settings.json. This works, but it's dangerous—it auto-approves everything, including a malicious URL like https://evil.com?secret=YOUR_API_KEY that could be injected via a prompt.
The correct solution uses Claude Code's PreToolUse hook system. A hook is a script that runs before a tool executes. It can:
- Exit 0 — Silently allow the tool (no prompt).
- Exit 1 — Show a message and ask for confirmation (approve/deny).
- Exit 2 — Hard block the tool (no option to proceed).
You can write a hook that inspects the WebFetch URL and only prompts you if it contains a query parameter (a ?). Clean documentation URLs auto-fetch; anything with ?key=value gets flagged for review.
Why It Works — Granular Control Over Tool Execution
This works because the PreToolUse hook receives the full, structured tool call as JSON via stdin. You can parse this data to make intelligent decisions. The hook input looks like this:
{
"session_id": "abc-123",
"hook_event_name": "PreToolUse",
"tool_name": "WebFetch",
"tool_input": {
"url": "https://example.com/page?q=test",
"prompt": "Summarize this page"
}
}
Query parameters in a URL (?q=test) are a common vector for data exfiltration in prompt injection attacks. By treating them as a higher-risk signal, you create a simple but effective security heuristic. Legitimate uses (like fetching API docs with anchors #section) still get flagged, but you can approve them manually. The trade-off—a few manual approvals for parameterized URLs—is worth the security gain.
How To Apply It — A Zero-Dependency Hook
Add this configuration to your ~/.claude/settings.json. It uses a Python one-liner with no external dependencies.

{
"hooks": {
"PreToolUse": [
{
"matcher": "WebFetch",
"hooks": [
{
"type": "command",
"command": "python3 -c \"import sys,json; data=json.load(sys.stdin); url=data.get('tool_input',{}).get('url',''); print('URL has query params, review: '+url, file=sys.stderr) if '?' in url else None; sys.exit(1) if '?' in url else sys.exit(0)\"",
"statusMessage": "Checking WebFetch URL for query params..."
}
]
}
]
}
}
Critical Setup Notes:
- Remove
WebFetchfrompermissions.allow. IfWebFetchis in theallowlist, the hook never fires. The permission system takes full precedence. - Input is via stdin, not environment variables. The hook script must read
json.load(sys.stdin), not checkos.environ.
Once configured:
https://docs.python.org/3/library/json.html→ Auto-allowed, no prompt.https://api.example.com/data?key=secret→ Prints "URL has query params, review: ..." and pauses for your approval.
Going Further — Apply the Pattern to Other Tools
The same hook pattern can guard other powerful tools. Here’s a Bash command guard that flags dangerous operations:
{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "python3 -c \"import sys,json; cmd=json.load(sys.stdin).get('tool_input',{}).get('command',''); dangerous=any(w in cmd for w in ['rm -rf','drop table','--force','--hard']); print('Dangerous command: '+cmd, file=sys.stderr) if dangerous else None; sys.exit(1) if dangerous else sys.exit(0)\""
}]
}
You can create similar guards for the Write tool to flag edits to sensitive paths (like *.env, *config.json) or for custom MCP servers. The hook system is your programmable layer between Claude's intent and the system's execution.







