ForgeVision Engineer Blog

フォージビジョン エンジニア ブログ

Agent Plugins for AWS を安全に使うための Claude Code 制御レイヤー設計 (第2回:Sensor編)

こんにちは、AWSグループの藤岡です。

前回の記事では、Claude Codeに Agent Plugins for AWS をセットアップし、CDKコード生成を検証しました。

techblog.forgevision.com

検証にあたって、以下2つの条件で生成結果を比較しました。

  • ①プラグインのみ
  • ②プラグイン + Guide Layer 有り

生成されたCDKファイルを見て、「②プラグイン + Guide Layer 有り」では実務では注意したいリソース設定が改善されていることを確認しました。

第2回となる本記事では、以下を実施します。

  • Claude Code Hooks を使った Sensor Layer の作成
  • Sensor Layer の有無による、CDKコードの安全性を検証比較
  • 第1回・第2回の結果を踏まえた、考察・まとめ

おさらい

まず、本記事で扱う技術について、簡単におさらいします。

Guide / Sensor とは

本検証では、Claude Code の周辺に以下の2つの制御レイヤーを置いて考えます。

レイヤー 役割
Guide Layer AI の判断を誘導する CLAUDE.md / AGENTS.md
Sensor Layer 生成後・実行前に機械的に検査する Hooks + formatter / linter

Guide Layerは「こういう方針で作ってください」と伝えるためのものです。たとえば、IAM は最小権限にする、ログ保持期間を明示する、削除操作は慎重に扱う、といったルールをAIに渡します。

一方でSensor Layerは実際にできあがったものを検査するためのものです。AIが意図通りに実装したかを、formatter、linter、testなどツールで確認します。

GuideとSensorは補完し合いながらエージェントをサポートします。(以下のようなイメージとなります。)

Claude Code Hooks とは

前回ブログで Guide Layer だけを適用した検証では、強制力がない(あくまで誘導するだけ)ことが課題として残りました。

これを解決するために、Sensor Layerとして Claude Code HookをCDK 検証ツールの実行ポイントとして使います。Claude Code Hooksは、Claude Codeがツールを使う前後に任意のコマンドを実行できる仕組みです。

code.claude.com

たとえば、ファイル書き換え直後にテストを実行したり、cdk deploy 直前に cdk diff コマンドを実行・確認したりできます。このようにHookの結果に応じて、Claude に修正を依頼したり、危険なコマンド実行をブロックしたりできます。


Sensor Layer の方針

本記事では、Sensor Layer を CDK(TypeScript) の生成・デプロイを検査するレイヤーとして位置付け、Hook から以下を呼び出します。

  • formatter / linter
  • CDKテスト(Jest)
  • cdk synthcdk-nag 評価を含む)
  • cdk diff

これらをエージェントの作業タイミングに合わせて実行します。

タイミング Hook 実行する検査 目的
ファイル生成後 / 保存後 PostToolUse on Write / Edit npm run format:check, npm run lint, npm test, npm run synth 生成直後に CDK コードの破綻、テスト失敗、cdk-nag finding を検知する
cdk deploy 直前 PreToolUse on Bash npm run synth, npm run diff cdk-nag finding と削除・置換差分をデプロイ前に検知する

ここでの cdk-nag は独立した Hook コマンドではなく、CDK 実装に AwsSolutionsChecks を組み込んで、npm run synth の中で評価する前提とします。そのため、ファイル生成後と cdk deploy 直前の両方で cdk-nag がトリガーされます。

また、本検証で意識したポイントは以下です。

  • IAM ワイルドカードなど、ベストプラクティスに沿わない箇所の検出は、自前実装せずに、cdk-nag による検出(AwsSolutions-IAM5 など)に任せる
  • CloudWatch Logs の保持期間など、プロジェクト依存の確認は CDK assertions / Jest で検証する
  • デプロイ前に cdk synthcdk diff を実行し、cdk-nag finding と削除・置換差分を検知する
  • Hook は標準ツールを呼び出す薄いゲートにして、CI/CD にも移植しやすくする

CDK プロジェクトの準備

1. CDK TypeScript プロジェクトをセットアップする

本記事では CDK TypeScript のみを扱います。CDK 自体や cdk-nag は他言語でも利用できますが、前回記事と同様に、TypeScript の CDK プロジェクトを前提に、以降の設定を行います。

既存プロジェクトがない場合は、検証用ディレクトリで CDK app を作成します。(第1回の手順と変わりません)

mkdir aws-plugin-guide-validation
cd aws-plugin-guide-validation

npx cdk init app --language typescript

あわせて、jq が使えることも確認します。(Claude Code が Hook の返り値を扱いやすくするため)

jq --version

2. cdk-nag を導入する

cdk-nag は、Well-Architectedなどを参考にしたルールと、CDK実装との準拠をチェックするツールです。本記事では、IAM ワイルドカードや API Gateway まわりの設定確認に使います。

npm install --save-dev cdk-nag

CDK app の entry point で AwsSolutionsChecks を有効化します。例として bin/cdk.ts に追加します。

#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib/core';
import { AwsSolutionsChecks } from 'cdk-nag'; // 追加
import { CdkStack } from '../lib/cdk-stack';
  
const app = new cdk.App();
new CdkStack(app, 'CdkStack', {
});
  
cdk.Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true })); // 追加

これにより、cdk synth 実行時(合成テンプレート作成時)に、ルールセットをもとにしたチェックが実行されます。たとえば IAM ポリシーの Action: "dynamodb:*"Resource: "*" は、AwsSolutions-IAM5 の finding として検出できます。

3. npm scripts を定義する

Hook から直接 cdk-nag の内部実装を呼ぶのではなく、標準の npm scripts を実行します。これにより、手動実行、Hooks、CI/CD で同じコマンドを共有できます。

package.json に以下を追加します。

{
  "scripts": {
    "format:check": "prettier --check \"**/*.ts\"",
    "lint": "eslint . --ext .ts",
    "test": "jest",
    "synth": "cdk synth",
    "diff": "cdk diff"
  }
}

formatter / linter をインストールします。

npm install --save-dev prettier eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

npm run format:check
npm run lint
npm test
npm run synth

CloudWatch Logs の保持期間は、Guide Layer で構築前に確認させます。Sensor Layer では CDK assertions(Jest)を使い、テストケースによって合成テンプレートの RetentionInDays を検査します。

import { App } from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
import { AwsPluginGuideValidationStack } from '../lib/aws-plugin-guide-validation-stack';
  
test('CloudWatch Logs retention is explicitly configured', () => {
  const app = new App();
  const stack = new AwsPluginGuideValidationStack(app, 'TestStack');
  const template = Template.fromStack(stack);
  
  const logGroups = template.findResources('AWS::Logs::LogGroup');
  expect(Object.keys(logGroups).length).toBeGreaterThan(0);
  
  for (const resource of Object.values(logGroups)) {
    expect(resource.Properties).toHaveProperty('RetentionInDays');
  }
});

Hook 実装

ここから、Claude Code Hooks で使う2つのスクリプトを用意します。1つ目は CDK ファイル生成・編集直後のチェック、2つ目は cdk deploy 直前のゲートです。

ディレクトリ作成

Hook スクリプトを置くディレクトリを作成します。

mkdir -p .claude/hooks

⚠️以降は、package.jsoncdk.json がある CDK app のルートで Claude Code を開いている前提です。

センサー①:ファイル生成後の CDK チェック

Write / Edit の後に formatter / linter / CDK assertions / cdk synth を実行します。(cdk-nagcdk synth 実行時に評価されます)

実装ファイル: .claude/hooks/run-cdk-checks.sh

#!/usr/bin/env bash
set -euo pipefail

cd "$CLAUDE_PROJECT_DIR"

if [[ ! -f package.json || ! -f cdk.json ]]; then
  exit 0
fi

run_check() {
  local name="$1"
  shift

  if ! output="$("$@" 2>&1)"; then
    echo "CDK Sensor failed: ${name}" >&2
    echo "Command: $*" >&2
    echo "$output" >&2
    exit 2
  fi
}

run_check format-check npm run format:check
run_check lint npm run lint
if node -e "process.exit(require('./package.json').scripts?.test ? 0 : 1)" >/dev/null 2>&1; then
  run_check test npm test
fi
run_check synth npm run synth

実行権限を付与します。

chmod +x .claude/hooks/run-cdk-checks.sh

この Hook は、構文崩れ、lint 違反、テスト失敗、cdk-nag finding を Claude に返すためのものです。Hook 内では判定ロジックを増やさず、npm scripts の結果を stderr で返します。

センサー②:cdk deploy 直前の cdk diff ゲート

cdk deploy の直前に cdk synthcdk diff を実行します。cdk synthcdk-nag を再評価し、cdk diff で削除差分を検知します。

実装ファイル: .claude/hooks/guard-cdk-deploy.sh

#!/usr/bin/env bash
set -euo pipefail

HOOK_INPUT="$(cat)"
COMMAND="$(printf '%s' "$HOOK_INPUT" | jq -r '.tool_input.command // ""')"

case "$COMMAND" in
  *"cdk deploy"*|*"npx cdk deploy"*) ;;
  *) exit 0 ;;
esac

cd "$CLAUDE_PROJECT_DIR"

if [[ ! -f package.json || ! -f cdk.json ]]; then
  exit 0
fi

if ! synth_output="$(npm run synth 2>&1)"; then
  echo "$synth_output" >&2
  jq -n --arg reason "cdk synth failed before deploy. Review the hook output before running deploy." '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: $reason
    }
  }'
  exit 0
fi

if ! diff_output="$(npm run diff 2>&1)"; then
  echo "$diff_output" >&2
  jq -n --arg reason "cdk diff failed before deploy. Review the hook output before running deploy." '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: $reason
    }
  }'
  exit 0
fi

if echo "$diff_output" | grep -E '(^|\s)(destroy|Delete|DELETE|will be destroyed|Replacement)' >/dev/null; then
  echo "$diff_output" >&2
  jq -n --arg reason "cdk diff detected a destructive or replacement change. Review the hook output before running deploy." '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: $reason
    }
  }'
  exit 0
fi

exit 0

実行権限を付与します。

chmod +x .claude/hooks/guard-cdk-deploy.sh

この Hook は、cdk diff の実差分を見て、削除・置換の疑いがある場合に cdk deploy を中止します。


Hooks の設定

.claude/settings.json を作成し、2つの Hook を登録します。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-cdk-checks.sh",
            "statusMessage": "CDK format/lint/synth/cdk-nag checks are running...",
            "timeout": 120
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/guard-cdk-deploy.sh",
            "statusMessage": "CDK pre-deploy sensor is running...",
            "timeout": 180
          }
        ]
      }
    ]
  }
}

それぞれの Hook は、前述のSensor Layer の方針をベースに設定しました。

PostToolUse はフィードバック用の Hook です。保存自体はブロックせず、失敗時に Claude Code へエラー出力を返して修正させます。

PreToolUse は実行前ゲートです。cdk deploy を含む Bash コマンドに反応し、cdk synthcdk diff に問題があればデプロイを許可しません。


検証③ Sensor Layer の検証

前回の検証①(Plugin単体)、検証②(Guide適用)に続き、検証③では Hooks を適用した状態で同じシナリオを実行します。

シナリオ(ⅰ):テキストメモAPIのCDKテンプレート生成

前回の検証①・②と同様に、cdk init した初期プロジェクト状態で、以下のように入力します。

テキストメモを登録・取得するサーバーレスAPIを AWS CDK TypeScript で構築してください。実際のデプロイはまだ実行しないでください。

入力すると、まずは Guide(AGENT.md や CLAUDE.md)に則って、ヒアリングやファイル作成が始まりました。

Claude Code による生成後、Sensor によってテストが実行されますが、テストが実装を踏まえていないため、エラーをもとに CDK assertions(テストケース)を修正しようと自律的に動きました。

そのまま自律的に、生成と Sensor Layer によるチェックを繰り返し、デプロイ直前まで到達しました。テストケースによる評価は追加されていますが、ここまでは ②(Guide適用)と大きな差分はありません。

シナリオ(ⅱ):CDKデプロイの実行

続いて、Claude Code にデプロイを指示します。(生成された CDK スタックに cdk-nag が設定されていることを事前に確認済みです。)

デプロイを指示すると、Hook が先に cdk synth を実行します。その過程で cdk-nag の評価が走り、finding が出たためデプロイは中止されました。

cdk-nag では、以下を含む合計6件の finding が出ました。

  • DynamoDB テーブル:PITRが有効化されていない
  • Lambda 関数:コンテナランタイムでない場合に、最新ランタイムバージョンを使用していない
  • IAM ポリシー:AWS マネージドポリシー(AWSLambdaBasicExecutionRole)を使用しているため、権限範囲が広い

今度はもう1つのデプロイシナリオとして、先ほどの cdk-nag finding を解消したうえで、デプロイ済みリソースの削除が発生するように CDK コードを修正します。その状態で、Claude Code にデプロイを依頼します。

すると、以下のようにcdk diff によるリソース削除を検知したことで、指示のとおりにデプロイを継続せずに中止をしました。これも期待通りにSensor Layerが作動してくれました。


この検証で、Sensor Layer が「生成後に検査し、問題があれば止める」役割を担えることを確認できました。Hook で評価ポイントを固定すると、Claude Code の作業ループに検査結果を戻せます。

また、cdk deploy 直前の cdk synthcdk-nag)と cdk diff による評価ゲートは、Guide Layer では代替しにくい領域です。削除・置換差分を一度止めて確認できるため、安全側に倒しやすくなります。

検証①②③ の整理・考察

第1回の結果とあわせると、各レイヤーの違いは次のように整理できます。

観点 ①Plugin単体 ②Guideあり ③Guide+Sensor
IAMポリシー ワイルドカードが利用されるケースあり 明示的な action / resource を指定して生成 cdk-nag が設定不備として検出
CloudWatch Logs 保持期間の明示なし 未指定なら構築前に確認 CDK assertions / Jest で保持期間を検査
API Gateway アクセスログ未検討 ログ・メトリクス要否を確認 cdk-nag が設定不足を検出
デプロイ前差分 未確認 確認を促す cdk synthcdk-nag)と cdk diff で deploy を止める

Plugin単体でも、サーバーレスAPIの骨格は生成できます。簡単な検証であれば、ここまででも十分です。

Guide Layer を加えると、IAM やログ保持期間など、レビューで見たい観点を生成前に意識させられます。ただし、Guide Layer は「何に気をつけるか」を伝える層です。生成結果が基準を満たすか、デプロイ直前に危険な差分がないかまでは保証しません。

Sensor Layer を加えると、format / lint / test / synth / cdk-nag / diff の結果を Claude Code の作業ループに戻せます。問題があればその場で修正させ、deploy 前には Hook で止められます。

今回の検証では、Guide が生成前の判断を支え、Sensor が生成後・実行前の確認を担う、という両面でのClaude Code のサポートが有効だと分かりました。


まとめ

本記事では、Claude Code Hooks による Sensor Layer を、CDK の標準検査フローとして実装しました。

第1回・第2回の結果を踏まえて、以下ポイントを押さえることが重要です。

  1. Guide Layer で、CDK の設計方針を誘導する
  2. PostToolUse Hook で、ファイル生成後に format / lint / test / synth(cdk-nag)を走らせる
  3. PreToolUse Hookcdk deploy 直前に synth(cdk-nag)と cdk diff を確認する

この構成により、生成直後の不備は Claude Code の修正ループへ戻し、デプロイ直前の確認が必須な状態は Hook で止められます。

Agent Plugins for AWS は AWS の作法を渡し、Guide はプロジェクト固有の判断基準を渡し、Sensor は生成物と実行内容を機械的に確認します。まずはこの3つをすべて導入しつつ、プロジェクトに合わせて Guide と Sensor を育てていくのが現実的だと感じました。

本記事が、エージェンティックコーディングに興味を持たれた方のお役に立てば幸いです。

以上となります。2回に渡って読んでいただき、ありがとうございました。