CDKプロジェクトのAIエージェント設計 (1) — Harness: 逸脱を検出する仕組み

記事タイトルとURLをコピーする

CDKプロジェクトのAIエージェント設計 (1) — Harness: AIの逸脱を防ぎ、検出する仕組み

はじめに

今期から統合運用サービス課になった石井です。

AIエージェントにインフラコードを書かせる時代が来ています。Claude Code、Cursor、Kiro CLI ― どのツールを使っても、CDKコードを自動生成できるようになりました。

しかしエンタープライズでは「動けばOK」ではありません。社内セキュリティ基準への準拠が必須であり、「AIが書いたコードがポリシーを満たしているか」を保証する仕組みが求められます。

人間のレビューだけに頼る体制には、設定項目の見落としやレビュアーの属人化といった従来からの課題に加え、AIの生成速度とレビュー速度の非対称性 という新しい課題が加わります。レビュー文化を否定するのではなく、「仕組みで守れる部分は仕組みに委ね、人間のレビューはより価値の高い判断 ― アーキテクチャの妥当性、リソース間の依存設計、コスト最適化、運用時の影響範囲の見極め ― に集中させる」のが現実的な狙いです。

本記事の結論を先に述べます。私たちは AWS CDK プロジェクトにおいて、cdk synth の成否を社内セキュリティ基準の準拠判定に使える領域を広げていく 取り組みをしています。「あらゆるリソースで 100% 守れる」仕組みにはなり得ないので、どこまで仕組みで担保し、どこから人間のレビューに委ねるか の境界を意識的に引くことが現実的な落とし所になります。本記事ではその境界を、L3コンストラクト(Guide)と cdk-nag カスタムルール(Sensor)の二重構造でどう設計したか、そして境界の外をどう運用するかを紹介します。

解決策の全体像: Guide と Sensor の二重構造

私たちが採用したのは、Guide(誘導)Sensor(検出) の二重構造です。これは Birgitta Böckeler 氏が Harness engineering for coding agent users で体系化したフレームワーク ― Guides(行動前に正しい方向へ誘導)と Sensors(行動後に逸脱を検出)― に基づいています。

私たちのCDKプロジェクトでは、これを以下のように実装しています:

  • Guide: セキュリティ設定を組み込み済みのL3コンストラクトを提供し、「使うだけで準拠する」状態を作ります
  • Sensor: cdk-nagのカスタムルールパックで、synth時に違反を検出してビルドを失敗させます

この2つが揃うことで、AIの自己修正ループが「正しい方向に」収束します。Sensorがエラーを出し、Guideが「こっちを使えば通る」という正解の型を示します。結果として、試行錯誤が短い回数で収束し、準拠コードに到達しやすくなります(具体的な効果は本シリーズ第3回で検証します)。

Guide: 社内L3コンストラクト

設計思想

AWS CDKの標準コンストラクト(L2)は汎用的に設計されています。暗号化の有無、VPC配置、ログ設定 ― すべてオプショナルです。これは柔軟性の裏返しとして「設定し忘れ」を許容してしまいます。

私たちはセキュリティ要件を組み込み済みのL3コンストラクトを開発し、社内のプライベートレジストリ(CodeArtifact)で配布しています。利用者(AIを含む)は業務ロジックに必要なPropsだけ渡せばよい設計です。

コード例: KMSキーの作成

import { SecureKms } from '@internal/secure-kms';

const kmsKey = new SecureKms(this, 'EncryptionKey', {
  keyAlias: 'my-service-encryption',
  description: 'Encryption key for my-service',
  // セキュリティタグ(必須Props)
  securityTags: {
    owner: 'team-a',
    environment: 'production',
  },
});

// セキュリティタグ(必須Props) ついて 1

これだけで以下が自動的に適用されます:

  • 365日ローテーション
  • 適切なキー管理者ポリシー
  • 必須タグの付与

利用者は「ローテーション期間は何日にすべきか」「キーポリシーに誰を含めるべきか」を知る必要がありません。

では、コンストラクトの内部では何が起きているのでしょうか。概念コードを示します:

// コンストラクト内部(概念コード)
export class SecureKms extends Construct {
  public readonly key: kms.Key;

  constructor(scope: Construct, id: string, props: SecureKmsProps) {
    super(scope, id);

    this.key = new kms.Key(this, 'Key', {
      enableKeyRotation: true,
      rotationPeriod: Duration.days(365),  // 社内基準: 365日
      policy: this.buildKeyPolicy(props),  // 管理者ロール・サービスプリンシパルを自動設定
    });
  }

  private buildKeyPolicy(props: SecureKmsProps): iam.PolicyDocument {
    // FederatedServiceAdmin を Key Admin に含める(社内基準)
    // ... 省略
  }
}

セキュリティ要件(ローテーション期間、キーポリシー)がコンストラクト内部に組み込まれているため、利用者側のコードには一切現れません。これがGuideの本質です。

コード例: Lambda関数の作成

import { SecureLambda } from '@internal/secure-lambda';
import { serviceConfig } from '../config';  // 環境固有値は設定ファイルに集約

const fn = new SecureLambda(this, 'MyFunction', {
  functionName: 'my-service-handler',
  handler: 'index.handler',
  codePath: 'app/lambda/handler',
  runtime: lambda.Runtime.NODEJS_22_X,
  memorySize: 128,
  timeout: Duration.seconds(3),
  environment: {
    TABLE_NAME: table.tableName,
  },
  // VPC配置(必須Props — 設定ファイルから取得)
  vpc: {
    vpcId: serviceConfig.vpcId,
    privateSubnetIds: serviceConfig.privateSubnetIds,
  },
  // 環境変数暗号化用KMSキー(必須Props)
  kmsKey: kmsKey,
});

ポイントは、VPC配置とKMSキーが 必須Props になっていることです。TypeScriptの型システムにより、これらを渡さなければコンパイルが通りません。「うっかりVPC外に配置してしまった」が構造的に起きない設計になっています。さらに、コンストラクト内部ではVPC IDやサブネットIDの形式バリデーション(vpc- プレフィックスや長さチェック等)も実行されるため、設定ファイルの記述ミスもsynth時に検出されます。

生CDKとの比較

同じLambda関数を生CDK(L2)で社内基準に準拠させようとすると、VPCの lookup、セキュリティグループの設定、KMS暗号化の設定、CloudWatch Logsの保持期間設定、各種タグ付け ― Lambda 単体でも十数行、周辺リソース(SG・Logs・KMS 等)の定義を含めれば数十行規模の設定コードが必要になります。そしてその一つでも漏れればセキュリティ違反です。

L3コンストラクトは「正解の型」を提供することで、この認知負荷をゼロにします。Guideなしで実際に何が起きるか ― 環境分岐の書き方が毎回変わる、命名規則を守らない、論理ID変更でスタックが壊れる ― といった具体的な失敗事例は第3回で扱います。

Sensor: cdk-nag カスタムルールパック

設計思想

Guideだけでは不十分です。L3コンストラクトを使わず生CDKで書かれた場合、違反を検出できません。 そこで私たちは cdk-nag を活用しています。cdk-nag は CDK の Aspects 機構を利用した静的解析ツールで、cdk synth 時にセキュリティやベストプラクティスの違反を検出できます。 AWS公式のルールパック(AwsSolutions)が同梱されていますが、私たちは社内セキュリティ基準に対応したカスタムルールパックを定義し、synth時に全リソースを検証しています。

適用方法

CDKのAspects機構を使い、デプロイ単位(Stage)全体に一括適用します。

import { Aspects } from 'aws-cdk-lib';
import { CorpSecurityChecks } from '@internal/security-nag';

// Stage内の全リソースに対してセキュリティチェックを適用
Aspects.of(this).add(new CorpSecurityChecks({ verbose: true }));

この1行で、Stage配下のすべてのスタック・すべてのリソースが検証対象になります。個別のリソースごとにチェックを書く必要はありません。

ルールIDと社内基準の対応

ルールIDは社内セキュリティ基準の要件番号と対応させています。

[Error at /MyStack/MyBucket/Resource] CORP-S3-2.1: S3バケットはサーバーサイド暗号化(SSE-KMS)が必須です。
[Error at /MyStack/MyFunction/Resource] CORP-Lambda-3.1: 社内基準により、Lambda関数はVPC内に配置する必要があります。

このエラーメッセージがAIにとっての「修正ヒント」になります。要件番号が明示されるため、AIは対応するL3コンストラクトに差し替えるだけで解決できます。

違反時の挙動

違反が1つでもあれば cdk synth が失敗します。synth はデプロイ対象の CloudFormation テンプレートを生成するステップなので、失敗すれば デプロイ可能な成果物がそもそも存在しない 状態になります。これにより「違反したまま本番に出る」経路が構造的に成立しません。

なぜ二重構造が必要か

構成 問題点
Guideだけ コンストラクトを使わず生CDKで書かれたら検出できない
Sensorだけ エラーは出るが修正方向が分からない。AIが試行錯誤を繰り返す
Guide + Sensor Sensorが違反を検出し、Guideが正解の型を示す。修正が収束する

特にAIエージェントとの相性が良いです。AIは「エラーメッセージを読んで修正する」のは得意ですが、「何が正解か分からない状態で試行錯誤する」のは苦手です。Guideという明確な正解があることで、修正ループが短く収束します。

ただし、Sensorのエラーメッセージだけでは「何を使えば正解か」までは伝わりません。AIがGuide(L3コンストラクト)に向かうには、何らかの導線が必要です。実装方法はいくつかあります:

  • エラーメッセージ自体に修正手段を含める(例: CORP-Lambda-3.1: Lambda関数はVPC内に配置する必要があります。→ @internal/secure-lambda を使用してください
  • プロジェクトのドキュメント(CLAUDE.md等)で「このコンストラクトを使え」と事前に指示する
  • 既存コードの前例から推測させる

私たちのプロジェクトではドキュメントによる事前誘導を採用しています。エラーメッセージに修正手段を埋め込む方がSensor単体で完結するため、より堅牢な設計と言えますが、ドキュメント誘導にも「AIに渡す情報を階層的に制御できる」という利点があります。この設計の詳細 ― 階層型ドキュメントと遅延ロード戦略 ― は次回記事で掘り下げます。

設計判断のトレードオフ

Propsでどこまで制約するか

L3コンストラクトのProps設計には「自由度 vs 安全性」のトレードオフがあります。

例えばLambdaのメモリサイズやタイムアウトはセキュリティとは無関係なので自由に設定できるようにしています。一方、VPC配置やKMS暗号化はセキュリティ要件に直結するため必須Propsとして強制しています。

原則は 「セキュリティに関わる設定は必須、業務ロジックに関わる設定は自由」 です。

例外を通す仕組み

現実のビジネスでは、トップダウンの判断で「一旦デプロイして後日是正」が許容されるケースがあります。そのためcdk-nagのsuppress機構で、理由を明記したうえで特定ルールの検証をスキップできるようにしています。

NagSuppressions.addResourceSuppressions(resource, [
  { id: 'CORP-S3-2.1', reason: '○○の承認に基づき後日是正予定(チケット: XXX-123)' },
]);

これはあくまで例外中の例外であり、通常の開発フローでは使用しません。濫用を防ぐため、以下の運用ルールを設けています:

  • reason の記述要件: 承認者名・チケット番号・是正期限を必ず含める。理由なしの suppress はPRレビューで却下する
  • PRレビューでの可視化: suppress を含む差分は必ずセキュリティ担当者のレビューを通す
  • 定期棚卸し: 四半期ごとに全 suppress を棚卸しし、是正期限を超過したものは対応を強制する

仕組み(Sensor)で守れない例外を、運用ルールで補完する構造です。

限界と今後

この仕組みで「synthが通れば準拠」と言い切れるのは、カスタムルールパックがカバーしているリソースの範囲内 に限られます。

  • 新しいAWSサービスを採用する際は、ルールの追加開発が必要です
  • ルールパックのメンテナンスは継続的なコストとして発生します
  • カバー外のリソースはPRレビューで補完しています

「仕組みで100%守る」のではなく「仕組みでカバーできる範囲を明示し、その外は人間が見る」という現実的な運用です。

まとめ

CDK には Harness Engineering と相性が良い要素が揃っています。Aspects による横断的な検証、L3 コンストラクトによる「正解の型」の提供、TypeScript の型システムによる必須項目の強制 ― これらを組み合わせることで、Guide と Sensor の両方を言語レベルで自然に実装できます。

冒頭で述べたとおり、この仕組みは「あらゆるリソースで100%守れる」ものではありません。カバー範囲内では cdk synth の成否が準拠判定として機能し、その外は人間のレビューで補完する ― この境界を意識的に引くことが、現実的な運用の鍵です。

なお、社内に固有のセキュリティルールがない場合でも、cdk-nag に標準で同梱されている AwsSolutions パックや PCI DSS 3.2.1 パックで同様の二重構造を始められます。カスタムルールパックは、社内基準が固まってから段階的に整備すればよいでしょう。

次回予告

本記事で扱った Guide / Sensor は、AIコーディングの文脈で近年整理されつつある Spec → Context → Harness という3層設計のうち、Harness 層に相当する取り組みです。Harness は前段の Spec・Context が揃って初めて機能するため、私たちのプロジェクトでも定義を進めています。

本シリーズは全3回で構成しています:

  • 第2回 — Context(AIに何を見せるか): エントリーポイントファイル(AGENTS.md)を起点にした階層型ドキュメント設計、トークン節約のための遅延ロード戦略
  • 第3回 — ダークファクトリー(人間不在でAIが回す環境): Guideなしで何が起きるかの実体験、MCP vs SKILL vs CLI のトークン効率比較、マルチAIツール運用

参考文献

  • Birgitta Böckeler, "Harness engineering for coding agent users", martinfowler.com, 2026年4月2日 — Guides(Feedforward Controls)/ Sensors(Feedback Controls)のフレームワークを体系化
  • Mitchell Hashimoto, "My AI Adoption Journey", mitchellh.com, 2026年2月5日 — Step 5「Engineer the Harness」セクションで "harness engineering" の語を命名

  1. securityTags は通常のAWSタグとは別に、セキュリティ管理目的で必須化しているProps。owner はインシデント時の責任所在の特定、environment はライフサイクル管理(本番/検証の区別)に使用する。