aws-guard-hook
A Claude Code PreToolUse hook that blocks AWS CLI write/mutation operations.
Only read-only commands are permitted. All infrastructure changes must go
through Infrastructure as Code (IaC).
Why
When Claude Code runs inside an AWS environment, it can inadvertently execute
destructive CLI commands (aws ec2 terminate-instances, aws s3 rm, etc.).
This hook intercepts every Bash tool call before it runs and rejects any AWS
command that is not provably read-only, giving Claude the policy reason so it
can pivot to editing IaC instead.
How it works
The hook implements an allowlist strategy:
- If the shell command contains no
aws invocation → allow.
- Each
aws invocation is extracted from:
- Compound commands (
&&, ||, ;, |)
- Piped-to-aws patterns (
cat data.json | aws s3api put-object ...)
- Command substitutions (
$(aws ...), `aws ...`)
- Env-var prefixed forms (
AWS_PROFILE=prod aws ...)
- Heredoc bodies passed to a shell interpreter (
bash <<EOF\naws ...\nEOF)
- Shell
-c inline strings (bash -c 'aws ...')
- AWS commands with heredoc stdin (
aws sqs send-message ... <<EOF)
- Heredoc content written to files (not executed) is stripped before checking,
preventing false positives when a script file happens to mention
aws.
- The service and subcommand are parsed (skipping global flags like
--region, --profile).
- Always-allowed pairs pass immediately: all
aws sts * commands,
aws configure get/list, aws s3 ls, aws s3 presign,
aws s3 cp <s3://src> <local-dest> (downloads only), aws logs tail.
- For everything else the subcommand must start with a recognised read-only
prefix:
get-, list-, describe-, query, search-, check-,
validate-, scan, batch-get-, generate-presigned-, estimate-,
preview-, export-, filter-, lookup-, calculate-, resolve-,
summarize-.
- Anything that does not match is hard-blocked (exit 2). Claude receives
the block message and is expected to identify the correct IaC change.
There is no override. The block is absolute.
IaC deploy commands (terraform apply, cdk deploy, pulumi up, etc.) are
not intercepted — they are intentional operations in an IaC workflow.
Requirements
- Python 3.8+ or uv (preferred — zero-latency startup)
- No third-party dependencies (stdlib only)
The script has a uv-compatible PEP 723 shebang, so uv run hooks/aws_guard.py
works with no virtualenv setup.
Installation
Option A — Claude Code plugin (recommended)
This repo ships as a Claude Code plugin. Install it via the plugin system:
# Add a marketplace pointing at this repo (one-time setup)
/plugin marketplace add ebellefontaine/aws-guard-hook
# Install the plugin — hook is registered automatically
/plugin install aws-guard
The plugin manifest is at .claude-plugin/plugin.json and the hook
definition is at hooks/hooks.json.
Option B — user-level install (all projects on this machine)
Run the included install script. It copies the hook to ~/.claude/hooks/
and merges the configuration into ~/.claude/settings.json:
./install.sh
The hook then applies to every Claude Code project you open, not just this
repo. To uninstall, remove the corresponding entry from
~/.claude/settings.json.
Option C — project-level (this repo already configured)
The .claude/settings.json in this repository already registers the hook.
Clone the repo and open it with Claude Code:
git clone <repo-url> my-project
cd my-project
claude # hook is active immediately
Option D — add to an existing project manually
-
Copy hooks/aws_guard.py into your project's hooks/ directory.
-
Add the hook to your project's .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "uv run hooks/aws_guard.py"
}
]
}
]
}
}
Use python3 hooks/aws_guard.py if uv is not available.
- Verify the hook format against the current
Claude Code hooks documentation
if the schema has changed since this was written.
AWS Read-Only Skill (/aws-readonly)
This repo also ships a Claude Code skill that gives Claude proactive
guidance — before it ever tries a write command — rather than relying solely
on the hook to block it after the fact.
What the skill does
Invoking /aws-readonly loads a system-level prompt that instructs Claude to: