Block Tool Calls Outside the Session's Declared Scope

OWASP's Agentic Applications Top 10 ranks Agent Goal Hijacking — an injected instruction redirecting the agent toward a goal the user never asked for — as the single biggest risk in this category. A hook can't read intent, but it can enforce a boundary: declare the session's allowed file-path prefixes and network domains once in .claude/session_scope.json, and block any file or network tool call that falls outside it. This won't catch a hijack that stays inside the declared scope, but it stops the common real pattern — a prompt-injection payload trying to make the agent touch something far outside the stated task (an unrelated repo, an external domain, a path outside the project).

Model & Permission Governance Event: PreToolUse Claude Code Open source · Free

1. Save this as the hook script

#!/usr/bin/env python3
"""PreToolUse hook — reads a declared allowlist of path/URL prefixes for
this session from .claude/session_scope.json and blocks any file or
network tool call whose target falls outside it. A deterministic scope
fence, not intent detection: it approximates OWASP Agentic Top 10's #1
risk (Agent Goal Hijacking) without claiming to read the model's mind."""
import json, sys
from pathlib import Path

data = json.load(sys.stdin)
cwd = Path(data.get("cwd", "."))
scope_path = cwd / ".claude" / "session_scope.json"
if not scope_path.exists():
    sys.exit(0)  # no declared scope for this session — nothing to enforce

scope = json.loads(scope_path.read_text())
allowed_paths = scope.get("allowed_path_prefixes", [])
allowed_domains = scope.get("allowed_domains", [])

tool_input = data.get("tool_input", {})
target = tool_input.get("file_path") or tool_input.get("path") or tool_input.get("url", "")
if not target:
    sys.exit(0)

if target.startswith(("http://", "https://")):
    ok = any(d in target for d in allowed_domains) if allowed_domains else True
else:
    ok = any(str(Path(target)).startswith(p) for p in allowed_paths) if allowed_paths else True

if not ok:
    print(f"BLOCKED: tool target '{target}' falls outside this session's "
          f"declared scope in {scope_path} — possible goal hijacking via "
          f"injected content. Widen the scope file deliberately if this is "
          f"legitimate work.", file=sys.stderr)
    sys.exit(2)

sys.exit(0)

Save to a path inside your project, e.g. .claude/hooks/block-tool-calls-outside-declared-scope.sh, then chmod +x it.

2. Wire it into .claude/settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit|Bash|WebFetch",
        "hooks": [
          {"type": "command", "command": "python3 .claude/hooks/block-tool-calls-outside-declared-scope.py"}
        ]
      }
    ]
  }
}

Before you use this

A hook runs with your local shell's permissions — review the script above before adding it, the same way you'd review any script you didn't write yourself. See our guide on hook security risks for the specific CVEs this category of feature has already produced.

Related hooks

← All hooks
FEEDBACK