0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Code の UserPromptSubmit hook でプロンプトインジェクション対策を実装する ― additionalContext 差し込みと実行順序の3つのハマりどころ【2026】

0
Posted at

はじめに / 対象と前提

Claude Code の hook 機能はいくつか種類があるが、今回扱う UserPromptSubmit は使っている人がまだ少ない印象がある。「ユーザーがプロンプトを送信した瞬間」に割り込めるフックで、危険な指示をブロックしたり、逆に毎回自動で前提情報を差し込んだりできる。

対象読者は以下を想定。

  • Claude Code を業務で日常的に使っている
  • .claude/settings.json の hooks 設定を一度は触ったことがある(PreToolUsePostToolUse は知っている)
  • チームで Claude Code を使わせる際に、危険な指示や機密情報の書き込みを事前にガードしたい

検証環境は以下。

  • Claude Code v2.x 系(hooks 設定フォーマットは 2025 年末以降のもの)
  • macOS / zsh
  • Python 3.13(hook スクリプトの実装に使用)

TL;DR

  • UserPromptSubmit はユーザーの入力がモデルに渡るに発火する唯一の hook で、入力の拒否と追加コンテキストの注入の両方ができる
  • ブロックしたい場合は exit code 2 + stderr にメッセージ、コンテキストを足したい場合は stdout に JSON で additionalContext を返す、という 2 系統の使い分けが肝
  • 他の hook(PreToolUse など)と違い、tool_name が存在しないので matcher の書き方を間違えると設定自体が無視される

手順 / 動かし方

1. hook スクリプトを用意する

標準入力から JSON が渡ってくるので、prompt フィールドを見て判定する。

#!/usr/bin/env python3
import json
import re
import sys

data = json.load(sys.stdin)
prompt = data.get("prompt", "")

DANGEROUS_PATTERNS = [
    r"rm\s+-rf\s+/",
    r"git\s+push\s+.*--force",
    r"\.env\b.*(cat|print|show)",
]

for pattern in DANGEROUS_PATTERNS:
    if re.search(pattern, prompt, re.IGNORECASE):
        print(f"危険な操作の可能性があるプロンプトを検知: {pattern}", file=sys.stderr)
        sys.exit(2)  # ブロック。Claude には渡らない

# ブロックしない場合は、前提情報を追加コンテキストとして注入
output = {
    "hookSpecificOutput": {
        "hookEventName": "UserPromptSubmit",
        "additionalContext": "このリポジトリは本番DBに直結しているため、破壊的操作の前は必ず確認を取ること。",
    }
}
print(json.dumps(output))
sys.exit(0)

2. settings.json に登録する

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/guard_prompt.py"
          }
        ]
      }
    ]
  }
}

PreToolUse と違って matcher にツール名を指定する概念がないので、配列の中に hooks だけを持つオブジェクトを 1 つ置けば全プロンプトに適用される。

3. 動作確認

危険パターンに該当するプロンプトを投げてみると、Claude 側の応答が生成される前にブロックされ、stderr の内容がターミナルに表示されて処理が中断する。該当しないプロンプトでは、additionalContext に書いた文字列がシステム側の文脈として自然に付与され、Claude の回答にそれを踏まえた注意書きが混ざるようになる。実際に「本番DBを触る系の依頼」を送ってみて、確認を挟むようになったことを目視で確認した。

ハマりどころ

1. exit code とJSON出力を混同して両方書いてしまう

ブロックは exit code 2、コンテキスト注入は stdout の JSON、と経路が別なのに、同じスクリプト内で「ブロックもしたいし通常時はコンテキストも足したい」を素朴に書くと、ブロック時に stdout の JSON も出力してしまい、Claude Code 側のパースでエラーログが出ることがあった。ブロック分岐では sys.exit(2) の前に余計な stdout を書かないよう、早期リターンで分離するのが安全。

2. additionalContext が思ったタイミングで効かない

additionalContext はその回のプロンプト処理には効くが、次のターン以降は毎回 hook が再実行されない限り引き継がれない。「一度注入したら以降もずっと有効」と誤解して、2 ターン目で情報が消えたように見えて焦った。毎ターン発火する hook である以上、必要な情報は毎回付け直される前提で設計する必要がある。

3. 他の hook との実行順序でタイムアウトを食う

SessionStart で重い初期化処理をする hook を別途仕込んでいる場合、UserPromptSubmit はそれとは独立に毎回同期的に実行される。プロンプト検査に外部 API 呼び出しなどを挟むと、体感のレスポンスが毎回そのぶん遅くなる。hook のタイムアウトはデフォルトで短めに設定されているため、重い処理を書くとタイムアウトでフック自体が失敗し、結果的にブロックもコンテキスト注入も効かないまま素通りしてしまう。外部通信を伴う判定は避け、正規表現などのローカル処理に留めるのが無難だった。

背景・補足

PreToolUse はツール実行そのものをガードする hook なので「Bash でこのコマンドを実行しようとしている」段階で止められるが、UserPromptSubmit はそれよりさらに手前、Claude が何も考える前の入力の時点で介入できる。プロンプトインジェクション対策や、チーム利用時のガードレールとして使うなら、ツール実行を待たずに弾ける分こちらの方が早い段階で防御できる。両方を組み合わせて多層防御にするのが実務的には現実的だと感じた。

まとめ

  • UserPromptSubmit は入力そのものを検査できる唯一の hook で、ブロックと追加コンテキスト注入の 2 系統を使い分ける
  • ブロックは exit code 2 + stderr、コンテキスト注入は stdout の JSON、経路を混ぜると事故る
  • additionalContext は毎ターン再注入される前提で設計する
  • 判定処理は重くしすぎるとタイムアウトで素通りするので、ローカルで完結させるのが安全
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?