はじめに
Claude CodeやGitHub Copilot、CursorといったAIコーディングツールを使った開発(AI駆動開発)が当たり前になってきました。生産性は上がる一方で、AIに渡す情報とAIが生成するコードの両方に、従来とは違うセキュリティリスクが潜んでいます。
セキュリティチェックリストは世の中にたくさんありますが、多くは「人間がレビューで確認する」項目の羅列で、正直続きません。本記事では発想を変えて、確認作業そのものをエージェント側に寄せることをゴールにします。考え方自体は言語を問いませんが、具体例は普段触っている Spring Boot と Python(FastAPI等) の2本立てで書きます。片方しか使わない方も、対比で読むと「AIに何を規約として教えるべきか」が立体的に見えるはずです。
方針は次の3段構えです。上にあるほど楽で確実なので、できるだけ上の段に押し込みます。
| レベル | 誰が守るか | 例 |
|---|---|---|
| Lv.1 自動ブロック | ツール設定・フック | シークレットファイルの読み取り拒否、危険コマンドの実行禁止 |
| Lv.2 自動検出 | pre-commit / CI | シークレットスキャン、SAST、依存チェック |
| Lv.3 AIに自己確認させる | ルールファイル+定型プロンプト | 「セキュリティ規約に照らしてセルフレビューして」 |
| Lv.4 人が見る | レビュアー | 上の3段をすり抜けたものだけ |
人間の仕事は「Lv.4で頑張る」ことではなく、Lv.1〜3の仕組みを一度作ることです。
リスクの全体像
| 方向 | 何が起きるか | 代表例 |
|---|---|---|
| AIへ渡す情報 | 機密情報がプロンプト経由で外部送信される | APIキーを含むコードをそのままAIに貼る |
| AIが生成するコード | 脆弱なコード・危険な実装がそのまま入る | SQLインジェクション、シークレットべた書き、実在しないパッケージ |
| AIエージェントの動作 | エージェントが意図しない操作をする | プロンプトインジェクション、権限過剰なツール実行 |
チェックリスト①: シークレット管理(べた書き禁止)
最重要かつ最頻出のリスクです。AIは「動くコード」を優先して、平気でキーをリテラルで書いてきます。
| # | チェック項目 | 回避策 | 確認方法(レベル) |
|---|---|---|---|
| 1-1 | シークレットがソースにべた書きされていない | 下記の言語別パターンを標準化し、ルールファイルにも明記(②参照) | Lv.2: gitleaksをpre-commitフックで自動検出 |
| 1-2 | 設定ファイル自体がリポジトリに入っていない |
.env や application-local.properties を .gitignore に登録 |
Lv.2: 混入時はgitleaksでcommit自体が失敗する |
| 1-3 | AIがシークレットファイルを読まない | Lv.1: ツール側設定でアクセス自体をブロック(④参照) | 設定してしまえば確認不要(読めないので漏れない) |
| 1-4 | 過去コミットにシークレットが残っていない | 混入したら鍵をローテーション+履歴除去 |
Lv.2: CIで gitleaks detect --log-opts="--all" を定期実行 |
Spring Bootの場合
// NG: べた書き。AIはこう書きがち
String apiKey = "sk-xxxxxxxxxxxx";
// OK その1: @Value でプロパティから注入
@Value("${external.api.key}")
private String apiKey;
// OK その2: @ConfigurationProperties で型安全にまとめる(項目が増えたらこちら)
@ConfigurationProperties(prefix = "external.api")
public record ExternalApiProperties(String key, String endpoint) {}
# application.properties(リポジトリ管理OK。値は環境変数参照のみ)
external.api.key=${EXTERNAL_API_KEY}
Spring Bootは ${環境変数} プレースホルダで「参照の仕組みだけをリポジトリに残す」形が綺麗に作れます。ローカル用の実値は application-local.properties に書いて .gitignore へ。プロファイル機構がそのまま秘匿の境界線になります。
Pythonの場合
# NG: べた書き
API_KEY = "sk-xxxxxxxxxxxx"
# OK: pydantic-settings で環境変数 / .env から読み込む(FastAPIと相性◎)
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
external_api_key: str # 環境変数 EXTERNAL_API_KEY を自動マッピング
class Config:
env_file = ".env"
settings = Settings()
Pythonは os.environ 直読みでも成立しますが、pydantic-settingsにすると未設定時に起動が失敗してくれる(=設定漏れがLv.2的に検出される)のがポイントです。.env は .gitignore へ、代わりに .env.example を置きます。
両言語に共通する原則は1つで、「リポジトリには参照の仕組みだけ、実値は環境変数(またはSecrets Manager)」。AIへの規約もこの1行に集約できます。
pre-commitの設定例(一度入れたら終わり)
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
これでべた書きされたシークレットはJavaだろうがPythonだろうがコミット時点で自動的に弾かれます。
チェックリスト②: ルールファイルで「最初の決まりごと」にする
毎回プロンプトで注意するのは現実的ではありません。エージェントが常に読む場所にセキュリティ規約を書いておくことで、指示忘れを構造的に回避します。
| # | チェック項目 | 回避策 | 確認方法(レベル) |
|---|---|---|---|
| 2-1 | ルールファイルが存在する | CLAUDE.md / .cursorrules / .github/copilot-instructions.md をリポジトリ管理し、プロジェクトテンプレートに含める | Lv.2: CIでファイル存在チェック(1行のシェルで済む) |
| 2-2 | セキュリティ規約が明記されている | 下記サンプルを雛形として使い回す | Lv.3: 「このプロジェクトのセキュリティルールを列挙して」とAIに聞けば認識ズレが分かる |
| 2-3 | ルールが実際に守られている | ルールファイル+Lv.1/Lv.2の多層防御 | Lv.2: すり抜けてもgitleaks・SASTが拾う設計にする |
CLAUDE.md への記載サンプル(Spring Boot / Python両対応版)
## セキュリティ規約(必ず守ること)
### シークレット
- APIキー・パスワード・トークンをソースコードに直接書かない
- Java (Spring Boot): @Value または @ConfigurationProperties +
application.properties の ${環境変数} プレースホルダ経由で取得
- Python: pydantic-settings (BaseSettings) 経由で環境変数から取得
- .env, application-local.properties, credentials/ 配下は読み取り・編集しない
### SQL
- Java: JdbcTemplate / JPA のパラメータバインドを使う。文字列連結・String.format での組み立て禁止
- Python: SQLAlchemy のパラメータバインド or プレースホルダを使う。f-string でのSQL組み立て禁止
### 入力バリデーション
- Java: Bean Validation (@Valid + @NotNull, @Size 等) をController層で必須
- Python: FastAPI では Pydantic モデルで受ける。dict を生で受けない
### 依存ライブラリ
- 新規追加時は追加理由と公式レジストリ(Maven Central / PyPI)のURLを提示し、
インストール前に必ず存在確認の結果を報告すること
### セルフレビュー
- コード生成を終えたら、この規約に照らしたセルフレビュー結果を必ず末尾に付けること
ポイントは2つです。
-
「禁止」だけでなく「言語ごとの代わりの書き方」まで書く。「べた書き禁止」だけだとAIは独自の回避策をひねり出すことがありますが、
@ConfigurationPropertiesやpydantic-settingsと名指しすれば、プロジェクトの流儀に確実に揃います - 最後の「セルフレビュー義務化」。確認作業をAI自身のワークフローに組み込めば、人間が毎回依頼する必要すらなくなります
チェックリスト③: 生成コードの脆弱性
AIの学習データには両言語の「古い書き方」が大量に含まれています。特にJavaは歴史が長いぶん、レガシーパターンの再生産が目立ちます。
| # | チェック項目 | 回避策 | 確認方法(レベル) |
|---|---|---|---|
| 3-1 | SQLインジェクション対策 | ルールファイルでパラメータバインド必須を明記 | Lv.2: SemgrepをCIに組み込み(Java/Python両対応) |
| 3-2 | 入力バリデーション | Bean Validation / Pydanticを規約化 | Lv.2: SAST + Lv.3: セルフレビュー |
| 3-3 | 実在しないパッケージ(ハルシネーション) | ルールで「インストール前に存在確認を報告」を義務化 | Lv.3: AIに確認・報告させる |
| 3-4 | 依存ライブラリの既知脆弱性 | Dependabot / Renovateを有効化 | Lv.2: Java: OWASP Dependency-Check(Maven/Gradleプラグイン) / Python: pip-audit をCIで実行 |
| 3-5 | 古い暗号・危険な標準機能 | 下記の言語別あるあるを規約に明記 | Lv.2: SASTルールで検出 |
AIが書きがちな「言語別あるある」
Java / Spring Boot:
// あるある1: 文字列連結SQL(古いサンプルコードの再生産)
String sql = "SELECT * FROM users WHERE name = '" + name + "'"; // NG
// OK: パラメータバインド
jdbcTemplate.query("SELECT * FROM users WHERE name = ?", rowMapper, name);
ほかにも「MD5でパスワードハッシュ」(→ BCryptPasswordEncoder を規約化)、「CSRF無効化を安易に提案」(csrf().disable() はAPI専用構成のときだけ)あたりが頻出です。
Python:
# あるある1: f-stringでSQL組み立て(Pythonicに見えて危険)
query = f"SELECT * FROM users WHERE name = '{name}'" # NG
# OK: パラメータバインド(SQLAlchemy)
session.execute(text("SELECT * FROM users WHERE name = :name"), {"name": name})
ほかにも「pickle.loads で外部データを復元」「subprocess に shell=True」「yaml.load(safe_loadでない)」が定番です。これらはSemgrepやBanditの標準ルールで拾えるので、規約に書く+SASTで検出の二段構えにします。
3-3 Slopsquatting は言語を問わない
実在しないパッケージ名をAIが幻覚で出力し、攻撃者がその名前を先回り登録しておく手口です。Maven Central でも PyPI でも起こり得ます。従来は「人間がインストール前に検索」でしたが、ルールファイルで確認作業ごとAIに委譲できます。エージェントは実際にレジストリを検索できるので、「存在確認の結果を報告せよ」と規約に書くだけで運用に乗ります。
チェックリスト④(本丸): エージェント設定とプロンプトで確認を自動化する
①〜③の「確認方法」を、エージェント側の仕組みでどう実現するかを具体化します。
| # | チェック項目 | 回避策 | 確認方法(レベル) |
|---|---|---|---|
| 4-1 | シークレットファイルへのアクセス禁止 | ツール側設定で読み取り自体をブロック(下記比較表) | Lv.1: 設定後は確認不要 |
| 4-2 | 危険コマンドの実行禁止 | rm -rf、force push、本番接続等をdenyリスト化 | Lv.1: 実行しようとした時点でブロック |
| 4-3 | 生成コードのセキュリティ自己点検 | セルフレビューをルール義務化+スラッシュコマンド化 | Lv.3: コマンド1発 |
| 4-4 | プロンプトインジェクション | 外部コンテンツを読む操作と書き込み・実行操作の権限を分ける | Lv.1: 重要操作を要承認にしておけば構造的に防げる |
| 4-5 | MCPサーバ・拡張の信頼性 | 出所不明のものを入れない。導入はチーム審査 | Lv.2: 導入済み一覧を設定ファイルで管理し、差分をレビュー |
主要ツールの「ツール側設定」比較
| ツール | 設定方法 | 強制力 | 制約 |
|---|---|---|---|
| Claude Code |
.claude/settings.json(リポジトリ共有可) |
高: ファイル読み取り・コマンド実行を物理ブロック | — |
| Cursor |
.cursorignore + GUI設定(削除防止・ドットファイル保護等) |
中: エージェントのターミナル・MCP経由は除外ファイルにも届き得る | コマンドallowlistは公式にベストエフォート扱い |
| GitHub Copilot | GitHub側のリポジトリ/組織設定(Content Exclusion) | 中: 補完・Chat・コードレビューには効く | Business以上のプラン必須。CLI・エージェントモードは対象外 |
つまりツール側設定(Lv.1)はどのツールでも「可能」だが「完璧」ではない。だからこそgitleaks等のLv.2を必ず併用する——これが多層防御の実質的な理由です。
Claude Codeの設定例(Lv.1)
.claude/settings.json に書いておけば、エージェントは物理的にアクセス・実行できなくなります。
{
"permissions": {
"deny": [
"Read(.env)",
"Read(.env.*)",
"Read(**/*-local.properties)",
"Read(**/credentials/**)",
"Bash(rm -rf *)",
"Bash(git push --force*)"
]
}
}
Spring Boot案件なら *-local.properties、Python案件なら .env 系——プロジェクトの秘匿ファイル命名規則をそのままdenyに写すのがコツです。このファイル自体をリポジトリ管理すれば、チーム全員に同じガードが効きます。
セルフレビューを定型プロンプト化する(Lv.3)
「セキュリティ観点でレビューして」と毎回書くのは続かないので、スラッシュコマンドに固めます。Claude Codeなら .claude/commands/security-check.md を置くだけです。
# .claude/commands/security-check.md
直近の変更(git diff)を対象に、以下の観点でセキュリティセルフレビューを実施し、
結果を「問題なし / 要修正」の表形式で報告してください。要修正の場合は修正案も提示すること。
1. シークレットのべた書きがないか
(Java: リテラルのキー文字列 / Python: 定数への直接代入)
2. SQLの組み立て方
(Java: 文字列連結・String.format / Python: f-string を使っていないか)
3. 入力バリデーション
(Java: @Valid の付け漏れ / Python: dict を生で受けていないか)
4. 新規追加した依存パッケージの、公式レジストリ(Maven Central / PyPI)での実在確認結果
5. .gitignore すべき設定ファイルが差分に含まれていないか
運用はコミット前に /security-check を打つだけ。観点に言語別の具体パターンまで書いてあるので、AIのレビュー精度も安定します。CLAUDE.mdの「セルフレビュー義務」(②参照)と組み合わせれば、コマンドを打ち忘れてもAI側が自発的に点検します。
プロンプトインジェクションは「権限」で防ぐ
エージェントがWebページやMCP経由の外部データを読む場合、その中に「これまでの指示を無視して〜せよ」のような攻撃文が仕込まれている可能性があります。これをプロンプトの工夫だけで完全に防ぐのは困難です。
なので発想を変えて、仮に乗っ取られても実害が出ない権限設計にします。
- 破壊的操作・外部送信系の操作はdenyまたは要承認(上記の設定がそのまま効く)
- 「外部コンテンツを読むタスク」と「書き込み・実行するタスク」をなるべくセッションで分ける
- auto-approve(自動承認)は読み取り系に限定する
「攻撃を検知する」より「攻撃が成立しない構造にする」ほうが、確認コストはゼロに近づきます。
まとめ: 人間の仕事は「仕組みの初期設定」だけ
最終的に人間側に残る作業を整理すると、これだけです。
| タイミング | やること | 所要 |
|---|---|---|
| プロジェクト開始時(1回) | CLAUDE.md雛形・permissions設定・pre-commit・CIをテンプレートから配置 | 30分 |
| コミット前 |
/security-check を打つ(打ち忘れてもルールファイルがカバー) |
数秒 |
| 日常 | Lv.1〜3をすり抜けたアラートだけ対応 | 発生時のみ |
原則は言語非依存(「参照の仕組みだけをリポジトリに」「パラメータバインド必須」「確認はAIのワークフローに組み込む」)ですが、AIに守らせる規約は言語の語彙で書くほど効きます。「べた書き禁止」より「@ConfigurationProperties を使え」「pydantic-settings を使え」。JavaとPythonの両方を触ってきた身としては、この"翻訳作業"こそ中堅エンジニアがAI駆動開発に提供できる価値だと感じています。
参考
- OWASP Top 10 for LLM Applications
- gitleaks / pre-commit
- Semgrep / Bandit / OWASP Dependency-Check / pip-audit
- Claude Code: settings.json(permissions)、カスタムスラッシュコマンド、CLAUDE.md
- Cursor: Agent Security / .cursorignore
- GitHub Copilot: Content Exclusion