
Claude CodeのHooksでAWS本番環境への誤操作を自動ブロックする
はじめに
みなさん Claude Code で AWS 操作しているでしょうか。
Claude Code に AWS 操作を任せると便利です。しかし複数のプロファイルを切り替えて作業している人は、本番アカウントを誤って操作してしまうリスクがあります。
そんなリスクを抑えるために PreToolUse hook を使って、本番環境への更新操作を自動で検知し、ユーザー確認を挟む仕組みを作ってみました。
何が嬉しいのか
この仕組みでは、Claude Code に普段 ReadOnly 権限の IAM ロールを使わせます。
本番環境の調査や確認であればユーザー確認なしでスムーズに進められます。
更新権限が必要になった場合だけ hook がブロックして確認を挟みます。
ユーザーが許可すれば一定時間(デフォルト 5 分)そのプロファイルでの操作が通るので、毎回確認が出るわずらわしさもありません。
さらに IAM ロール側でも権限を絞っているため、万が一 hook を迂回されても被害を抑えられる多層防御の構造になっています。
前提: Claude Code での AWS 認証
Claude Code の Bash ツールは、各呼び出しで独立したシェルを起動します。
export AWS_PROFILE=xxx のような環境変数はコマンド間で引き継がれないため、--profile オプションを使う前提で仕組みを検討しました。
~/.aws/config に credential_process や source_profile が設定されていれば、--profile 指定だけで認証が通ります。
> aws s3 ls --profile my-dev
# → credential_process 経由で認証され、正常に実行される
以降の記事では --profile を使う前提で進めます。
設計方針
IAM ロールの使い分け
本番アカウントに対して 2 つのプロファイルを用意します。
| プロファイル | IAM ロール | 用途 |
|---|---|---|
my-prod-readonly |
claude-readonly-role |
Claude Code の通常利用(調査・確認) |
my-prod |
admin-role |
更新権限(変更操作が必要な場合) |
~/.aws/config の設定例です。
[profile my-prod]
role_arn=arn:aws:iam::111111111111:role/admin-role
source_profile=default
[profile my-prod-readonly]
role_arn=arn:aws:iam::111111111111:role/claude-readonly-role
source_profile=default
hook の判定ロジック
プロファイル名をもとに 3 パターンで判定します。
- プロファイル名に
readonlyを含む → 即通過(ReadOnly ロール) - プロファイル名に
prodを含むがreadonlyを含まない → ブロック、ユーザー確認 - それ以外(
my-dev等)→ 即通過(開発環境)
やってみる
hook スクリプトを作成する
.claude/hooks/ ディレクトリを作成し、以下のスクリプトを配置します。
Claude Code に「本番プロファイルの AWS コマンドをブロックする hook を作って」と相談しながら作りました。
.claude/hooks/aws-account-guard.sh(クリックで展開)
#!/bin/bash
PRODUCTION_PROFILES="prod"
READONLY_KEYWORD="readonly"
APPROVAL_TTL=300
APPROVAL_DIR="/tmp/aws-account-guard-approvals"
CACHE_TTL=300
CACHE_DIR="/tmp/aws-account-guard-cache"
mkdir -p "$CACHE_DIR" "$APPROVAL_DIR"
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name',''))" 2>/dev/null)
if [ "$TOOL_NAME" != "Bash" ]; then
exit 0
fi
COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null)
if ! echo "$COMMAND" | grep -qE '\baws\s'; then
exit 0
fi
if echo "$COMMAND" | grep -q "sts get-caller-identity"; then
exit 0
fi
PROFILE=$(echo "$COMMAND" | grep -oE '\-\-profile\s+[a-zA-Z0-9_-]+' | awk '{print $2}')
if [ -z "$PROFILE" ]; then
exit 0
fi
if echo "$PROFILE" | grep -qi "$READONLY_KEYWORD"; then
exit 0
fi
IS_PROD=false
for KEYWORD in $PRODUCTION_PROFILES; do
if echo "$PROFILE" | grep -qi "$KEYWORD"; then
IS_PROD=true
break
fi
done
if [ "$IS_PROD" = false ]; then
exit 0
fi
APPROVAL_FILE="$APPROVAL_DIR/$PROFILE"
if [ -f "$APPROVAL_FILE" ]; then
APPROVAL_AGE=$(( $(date +%s) - $(stat -f %m "$APPROVAL_FILE" 2>/dev/null || echo 0) ))
if [ "$APPROVAL_AGE" -lt "$APPROVAL_TTL" ]; then
exit 0
fi
rm -f "$APPROVAL_FILE"
fi
CACHE_FILE="$CACHE_DIR/$PROFILE"
ACCOUNT_ID=""
if [ -f "$CACHE_FILE" ]; then
CACHE_AGE=$(( $(date +%s) - $(stat -f %m "$CACHE_FILE" 2>/dev/null || echo 0) ))
if [ "$CACHE_AGE" -lt "$CACHE_TTL" ]; then
ACCOUNT_ID=$(cat "$CACHE_FILE")
fi
fi
if [ -z "$ACCOUNT_ID" ]; then
STS_OUTPUT=$(aws sts get-caller-identity --profile "$PROFILE" 2>/dev/null)
if [ $? -eq 0 ]; then
ACCOUNT_ID=$(echo "$STS_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['Account'])" 2>/dev/null)
echo "$ACCOUNT_ID" > "$CACHE_FILE"
fi
fi
ACCOUNT_INFO=""
if [ -n "$ACCOUNT_ID" ]; then
ACCOUNT_INFO="(Account: ${ACCOUNT_ID})"
fi
APPROVAL_MINUTES=$((APPROVAL_TTL / 60))
cat >&2 <<EOF
本番環境${ACCOUNT_INFO}への更新権限での操作を検知しました。
プロファイル: ${PROFILE}
コマンド: ${COMMAND}
ReadOnlyプロファイルで代替できないか検討してください。
それでも更新権限が必要な場合は、ユーザーに確認を取り、許可されたら以下を実行してから再試行してください:
touch ${APPROVAL_FILE}
(許可は${APPROVAL_MINUTES}分間有効です)
EOF
exit 2
スクリプトの流れを説明します。
- stdin から PreToolUse hook の JSON を受け取り、コマンド文字列を抽出する
- 前述の判定ロジックに従い、通過 / ブロックを判定する
- ブロック時は
exit 2で操作を止め、ReadOnly プロファイルの利用を促すメッセージを返す
ブロック後にユーザーが許可した場合、Claude がプロファイル単位の許可ファイル(/tmp/aws-account-guard-approvals/<profile>)を作成します。
許可は 5 分間有効で、その間は同じプロファイルでの操作が全て通過します。
作成後は実行権限を付与します。
> chmod +x .claude/hooks/aws-account-guard.sh
settings.local.json に登録する
.claude/settings.local.json に hook の設定を追加します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/aws-account-guard.sh"
}
]
}
]
}
}
"matcher": "Bash" で Bash ツールのみに限定しています。
動作確認
3 つのパターンで動作を確認してみます。
ReadOnly プロファイルで実行した場合
> aws s3 ls --profile my-prod-readonly
2024-11-12 16:02:24 my-app-assets
2025-11-05 11:15:55 my-app-logs
...
即通過して正常に実行されます。
普段の調査作業はこのプロファイルを使えばストレスなく進められます。
更新権限プロファイルで実行した場合
> aws s3 ls --profile my-prod
本番環境(Account: 111111111111)への更新権限での操作を検知しました。
プロファイル: my-prod
コマンド: aws s3 ls --profile my-prod
ReadOnlyプロファイルで代替できないか検討してください。
それでも更新権限が必要な場合は、ユーザーに確認を取り、
許可されたら以下を実行してから再試行してください:
touch /tmp/aws-account-guard-approvals/my-prod
(許可は5分間有効です)
ブロックされ、Claude にメッセージがフィードバックされます。
Claude はユーザーに「本番環境で実行して良いですか」と確認を取ります。
ユーザーが許可すると Claude が許可ファイルを作成します。
許可はプロファイル単位で 5 分間有効なので、その間は同じプロファイルでの操作が全て通過します。
5 分経過すると再度確認が入ります。
開発プロファイルで実行した場合
> aws s3 ls --profile my-dev
2026-02-17 08:57:02 my-app-dev-assets
...
即通過します。開発作業への影響はありません。
aws 以外のコマンド(echo や git など)も同様に即通過します。
注意点
プロファイル名は部分一致で判定される
PRODUCTION_PROFILES と READONLY_KEYWORD はどちらも部分一致です。
PRODUCTION_PROFILES="prod"→my-prod、another-prodなどprodを含むプロファイルが全てブロック対象READONLY_KEYWORD="readonly"→my-prod-readonlyなどreadonlyを含むプロファイルは通過対象
プロファイル命名規則を統一しておくと意図しないマッチを防げます。
AWS_PROFILE 環境変数はガードできない
このスクリプトは --profile オプションの有無でプロファイルを判定しています。
AWS_PROFILE 環境変数で制御するケースはガード対象外です。
Claude Code では --profile 指定を使うルールにしておくと安全です。
まとめ
ReadOnly の IAM ロールと PreToolUse hook を組み合わせて、本番環境への誤操作を防ぐ仕組みを作りました。
普段の調査は ReadOnly プロファイルでスムーズに進み、更新権限が必要な場面だけユーザー確認が入ります。
IAM ロール側で権限を絞った上で hook を追加する多層防御の構造になっているので、安心して Claude Code に AWS 操作を任せられます。
以上、鈴木純がお送りしました。








