
ダウンロードフォルダに置かれた AGENTS.md(CLAUDE.md)で Claude Code が乗っ取られる攻撃手法を、設定とフックで防ぐ方法
リテールアプリ共創部のるおんです。
先日、 ダウンロードフォルダに置かれた AGENTS.md で AI コーディングエージェントが乗っ取られる という手口が話題になりました。Web ページを開いただけで AGENTS.md がダウンロードフォルダに保存され、そこで Codex や Claude Code を起動すると、そのファイルを利用者本人の指示として読んで従ってしまう、というものです。
そこで、今回は Claude Code の設定とフック、Chrome の設定 で本攻撃手法を防げるかを実際に試してみました。
この記事では、まず 個人でもできる対策 を「やってみた」形で紹介し、Web ページからダウンロード、Claude Code の起動までを一気通貫で確かめます。後半では、 Team プランで組織の全員に配る方法 と、 起きた後に利用ログで追う方法 も紹介します。
先に結論
- 手口の正体は、 「起動したフォルダの指示ファイルに従う」というエージェント本来の動き を悪用したものです。ソフトの不具合ではないので、提供元の修正では防げません
- Claude Code なら、次の 2 つの設定で防げました。どちらも個人の設定(
~/.claude/settings.json)に書けますclaudeMdExcludesに"**/Downloads/**"を書くと、 ダウンロードフォルダの中のCLAUDE.md・AGENTS.mdを読まなくなるUserPromptSubmitのフックで、 ダウンロードフォルダの中で起動したら最初の入力で止める
- ブラウザ側でも、Chrome の 「ダウンロード前に各ファイルの保存場所を確認する」 をオンにすると、Web ページが仕掛けたダウンロードでも保存の画面が出て確認を促すこともできます
- Team プランなら、同じ設定を 管理画面から組織の全員に配れます 。同じ設定が Mac / Windows で効くことを確かめました
- それでもすり抜けたときのために、Team プランなら 利用ログで実行されたコマンドを後から追えます
| 対策 | 書く場所 | 防ぐもの | 個人 | Team プラン |
|---|---|---|---|---|
claudeMdExcludes |
Claude Code の設定 | ダウンロードフォルダの指示ファイルを読ませない | ✅ | ✅(管理設定で強制) |
UserPromptSubmit のフック |
Claude Code の設定 | ダウンロードフォルダで使わせない | ✅ | ✅(管理設定で強制) |
| 保存場所の確認 | Chrome の設定 | Web ページが黙ってファイルを置く | ✅ | ✅(ブラウザのポリシーで強制) |
| 利用ログ | OpenTelemetry → CloudWatch Logs | 起きた後に追う | — | ✅ |
どんな手口か
報道によると、セキュリティ企業の Forever Security が、顧客の PC のダウンロードフォルダで悪意のある AGENTS.md を見つけたことがきっかけです。
流れはこうです。
ポイントは 2 つです。
- ダウンロードフォルダは、Web ページが(ブラウザを通じて)書き込める唯一の場所 です。Chrome などは既定で、保存先を聞かずにダウンロードフォルダへ保存します
- AI エージェントは、起動したフォルダの指示ファイルを自動で読みます 。Claude Code も、起動したフォルダとその上のフォルダにある
CLAUDE.mdを読み、v2.1.277 以降はCLAUDE.mdが無ければAGENTS.mdも読みます
研究者の実験では、Codex で 9 割を超え、Claude Code でも約半分で、仕込まれた指示に従わせることに成功したそうです。Claude Code では、通信先を書き換えさせて OAuth トークンが漏れる例も報告されています。
ダウンロードフォルダで AI エージェントを起動するなんてしないでしょう、と思うかもしれません。ですが、ダウンロードした資料を「これ要約して」と頼むときなど、意外とやりがちです。報告によれば、この会社の顧客だけでも、ダウンロードフォルダで Claude Code が 1,300 回以上起動されていたそうです。
やってみた
1. ダウンロードフォルダの指示ファイルを読ませない(claudeMdExcludes)
Claude Code には、読み込まない CLAUDE.md を指定する claudeMdExcludes という設定があります。本来は、大きなモノレポで関係ないチームの CLAUDE.md を外すためのものです。
The
claudeMdExcludessetting lets you skip specific files by path or glob pattern.
パターンは絶対パスに対する glob で、個人・プロジェクト・組織の管理設定のどこにでも書けます。複数の階層に書いた場合は 足し合わされます 。
~/.claude/settings.json に 1 行足すだけです。
{
+ "claudeMdExcludes": ["**/Downloads/**"]
}
**/Downloads/** は、パスのどこかに Downloads フォルダを含むファイルすべてに当たります。ダウンロードフォルダの直下に置かれたものも、その中のフォルダに置かれたものも、まとめて読まなくなります。
2. ダウンロードフォルダで起動したら止める(フック)
1 つ目だけでも指示ファイルは読まれなくなりますが、 そもそもダウンロードフォルダで作業させない ように、フックで止めます。プロンプトを送るたびに動く UserPromptSubmit のフックで、起動したフォルダ(cwd)がダウンロードフォルダの中なら、終了コード 2 で止めます。
止める処理は、スクリプトのファイルに分けておきます。設定ファイルにコマンドを直接書くと、止まったときにコマンドの中身がまるごと画面に出て読みにくいためです。
#!/bin/bash
# ダウンロードフォルダの中で Claude Code を使わせない(UserPromptSubmit フック)。
# Web ページが黙って置いた AGENTS.md / CLAUDE.md を読み込ませないための守り。
cwd=$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("cwd", ""))')
case "$cwd" in
*/Downloads|*/Downloads/*)
echo "ダウンロードフォルダでは使えません。作業するプロジェクトのフォルダで起動し直してください。" >&2
exit 2
;;
esac
exit 0
chmod +x ~/.claude/hooks/block-downloads.sh
{
"claudeMdExcludes": ["**/Downloads/**"],
+ "hooks": {
+ "UserPromptSubmit": [
+ { "hooks": [{ "type": "command", "command": "\"$HOME\"/.claude/hooks/block-downloads.sh" }] }
+ ]
+ }
}
Windows の場合(PowerShell で動くフック)
Windows で Git for Windows が入っていない場合、フックは PowerShell で動きます。"shell": "powershell" を明示しておくと、Git の有無に関係なく PowerShell で動きます。
{
"claudeMdExcludes": ["**/Downloads/**"],
"hooks": {
"UserPromptSubmit": [
{ "hooks": [ {
"type": "command",
"shell": "powershell",
"command": "$j = [Console]::In.ReadToEnd() | ConvertFrom-Json; if ($j.cwd -match '[\\\\/]Downloads([\\\\/]|$)') { [Console]::OutputEncoding = [Text.Encoding]::UTF8; [Console]::Error.WriteLine('ダウンロードフォルダでは使えません。作業するプロジェクトのフォルダで起動し直してください。'); exit 2 }; exit 0"
} ] }
]
}
}
[Console]::OutputEncoding を UTF-8 にしておかないと、止めたときの日本語が「????」に化けました。
3. Chrome で保存場所を確認させる
最後に、そもそもの入口側です。Chrome の設定 → ダウンロード(chrome://settings/downloads)で、 「ダウンロード前に各ファイルの保存場所を確認する」 をオンにします。

この設定は Chrome のプロフィールごとです。普段 Web を見るのに使っているプロフィールで設定してください。
動作確認
ここからは、実際に「Web ページが AGENTS.md を仕掛ける」ところから、Claude Code を起動するところまでを順に試します。
1. Web ページを開くと、保存の画面が出て止まる
手元で、開いただけで AGENTS.md を自動でダウンロードさせる試し用のページを用意しました(中身は無害な 1 行です)。
試し用のページ(無害なファイルを自動でダウンロードさせるだけ)
<!doctype html><meta charset="utf-8"><title>download-test</title>
<h1>自動ダウンロードの試し(無害なファイル)</h1>
<script>
const b = new Blob(["# 試し用のファイル(無害)\n"], {type: "text/markdown"});
const a = document.createElement("a");
a.href = URL.createObjectURL(b); a.download = "AGENTS.md";
document.body.appendChild(a); a.click();
</script>
python3 -m http.server 8765 で配って、http://localhost:8765/ を開きました。
このページを開くと、 保存の画面が出て止まりました 。名前は AGENTS、保存先はダウンロードフォルダが入った状態です。

この時点では、ダウンロードフォルダに AGENTS.md は置かれていません。保存先を聞かない既定の設定のままだと、ここで黙って保存されてしまうわけです。
2. あえて保存すると、ダウンロードフォルダに置かれる
保存の画面で「保存」を押すと、当然ですがファイルは置かれます。
$ ls -la ~/Downloads/AGENTS.md
-rw-r--r--@ 1 user staff 39 Oct 28 13:59 /Users/user/Downloads/AGENTS.md
ここが、ブラウザ側の対策の限界です。保存の画面は出ますが、名前も保存先も入った状態で出るので、Enter を 1 回押すだけで保存できてしまいます。なので、次の Claude Code 側の守りが効いてきます。
3. ダウンロードフォルダで Claude Code を起動すると、止まる
ダウンロードフォルダの中に作ったフォルダで Claude Code を起動し、「hello」と送ってみました。

フックが止めて、「ダウンロードフォルダでは使えません。作業するプロジェクトのフォルダで起動し直してください。」と表示されました。プロンプトは Claude に届いていません。別のフォルダで起動した場合は、普段どおり使えます。
4. フックが無くても、置かれたファイルは読まれない
フックで止まるので、ふだんは指示ファイルが読まれるところまで進みません。そこで、フックを外して claudeMdExcludes だけを効かせた状態でも、置かれたファイルが読まれないかを確かめました。
ダウンロードフォルダに、合言葉だけを書いた指示ファイルを置きます。
合言葉は「あさがお-77」。
その中のフォルダで起動して、ファイルを開くツールを使わせずに「合言葉は?」と聞きます。合言葉の「あさがお-77」を答えられたら、起動した時点でこのファイルが読み込まれている、ということです。
claude -p "ファイルは開かずに、いま見えている指示だけで答えて。合言葉が書かれていれば合言葉だけ、無ければ「無し」とだけ答えて。" \
--model haiku --setting-sources project,local \
--settings '{"claudeMdExcludes":["**/Downloads/**"]}' \
--disallowedTools Read Glob Grep Bash
| 置いたファイル | claudeMdExcludes なし |
claudeMdExcludes あり |
|---|---|---|
CLAUDE.md |
あさがお-77(読まれた) |
無し |
AGENTS.md |
あさがお-77(読まれた) |
無し |
除外なしでは、 AGENTS.md も CLAUDE.md もしっかり読まれていました 。除外を入れると、どちらも読まれなくなりました。
5. それでもすり抜けたら、利用ログで後から追う(Team プラン)
ここまでの対策をすり抜けて何かが実行されてしまった場合でも、Team プランで利用ログを取っていれば、 誰が・いつ・どのコマンドを実行したか を後から追えます。これは後半で紹介します。
Team プランで全員に配る
個人で試して問題がなければ、組織に配ります。Team / Enterprise プランでは、claude.ai の 組織の設定 → Claude Code → 管理設定(Managed settings) に JSON を書くと、組織の全員の Claude Code に届きます。管理設定に書いた値は、個人の設定より優先されます。
Team プランの組織では Windows を使っていることも多いので、ここではフックを PowerShell 版にしました。あわせて、確認を全部飛ばすモードも使えなくしておきます。仕込まれた指示でコマンドを実行させる手口なので、実行前の確認が残っていること自体が守りになるためです。
{
"claudeMdExcludes": ["**/Downloads/**"],
"permissions": { "disableBypassPermissionsMode": "disable" },
"hooks": {
"UserPromptSubmit": [
{ "hooks": [ {
"type": "command",
"shell": "powershell",
"command": "$j = [Console]::In.ReadToEnd() | ConvertFrom-Json; if ($j.cwd -match '[\\\\/]Downloads([\\\\/]|$)') { [Console]::OutputEncoding = [Text.Encoding]::UTF8; [Console]::Error.WriteLine('ダウンロードフォルダでは使えません。作業するプロジェクトのフォルダで起動し直してください。'); exit 2 }; exit 0"
} ] }
]
}
}
Windows で効くか確かめる
手元に Windows の実機が無かったので、GitHub Actions の Windows のランナー(windows-latest)に Claude Code を入れて確かめました。AI の応答は使わず、読み込まれた指示ファイルは InstructionsLoaded のフックとデバッグ用の記録(--debug-file)で、止まったかどうかはフックの出力で判定しています。
| 確かめたこと | 結果 |
|---|---|
設定なしで、ダウンロードフォルダの CLAUDE.md・AGENTS.md が読まれるか |
どちらも読まれた |
claudeMdExcludes の書き方 4 通り(**/Downloads/**・C:/Users/*/Downloads/**・C:\Users\*\Downloads\**・**\Downloads\**) |
4 通りとも、どちらも読まれなくなった |
| ダウンロードフォルダの中・直下で起動 | 止まり、日本語のメッセージも正しく出た |
| 別のフォルダで起動 | 止まらなかった |
Windows のパスはバックスラッシュ区切りですが、 **/Downloads/** のままで効きました。
配るときの注意
- 配ると、各メンバーに承認の画面が出ます 。管理設定で配るフックは、利用者の承認が必要な設定です。次の起動時(起動中のセッションでは 1 時間以内の取り直しのとき)に出て、拒否すると Claude Code が終了するので、事前に「出たら許可してください」と周知しておきます
- 管理設定は、各端末の Claude Code が取りにいく仕組み です。公式ドキュメントにもあるとおり、管理されていない端末では利用者が意図して回避することは防げません。厳密に統制するなら、端末管理(MDM)で設定ファイルを配る方式も検討してください
ブラウザ側も、端末を管理しているなら強制できます。Chrome も Edge も、ポリシー名は PromptForDownloadLocation です。
| ブラウザ | レジストリ(Windows) | 値 |
|---|---|---|
| Chrome | HKLM\Software\Policies\Google\Chrome |
PromptForDownloadLocation = 1(REG_DWORD) |
| Edge | HKLM\SOFTWARE\Policies\Microsoft\Edge |
PromptForDownloadLocation = 1(REG_DWORD) |
保存先を固定するポリシー DownloadDirectory を一緒に設定すると、確認が出なくなるので注意してください。
起きた後に追う(Team プランの利用ログ)
対策は入口を狭めるものなので、すり抜けをゼロにはできません。そこで、起きた後に追えるようにしておくのも大事です。
以前の記事で、Claude Code の利用ログ(OpenTelemetry)を CloudWatch Logs に直接送って貯める方法を紹介しました。
この仕組みで OTEL_LOG_TOOL_DETAILS=1 を有効にしておくと、ツールを実行するたびに tool_result のイベントが残り、 tool_parameters に 実行したコマンドの全文 が入ります。
{"bash_command":"echo","full_command":"echo hello-poc"}
仕込まれた指示で npm install などが実行されていないかは、例えば Logs Insights で次のように探せます。
fields @timestamp, `attributes.user.email` as user, `attributes.tool_parameters` as params
| filter `attributes.event.name` = "tool_result" and `attributes.tool_parameters` like /npm install/
| sort @timestamp desc
| limit 100
注意点・Tips
- Claude Code にしか効きません 。
claudeMdExcludesもフックも Claude Code の設定です。Codex など、ほかの AI ツールにはそれぞれの対策が要ります - 読まれるファイル名は
AGENTS.mdだけではありません 。Claude Code はCLAUDE.md・CLAUDE.local.mdも読みます。ファイル名ではなく、ダウンロードフォルダ全体を外す**/Downloads/**にしているのはこのためです - ブラウザの設定は「保存」を押されたら終わり 。入口を狭める手なので、Claude Code 側の守りと組み合わせる前提です
- ダウンロードした資料を Claude Code で扱いたいとき は、プロジェクトのフォルダに移してから起動します。フックで止まるのは不便に感じるかもしれませんが、「そこで作業しない」習慣づくりにもなります
- フックの判定は「パスに Downloads が含まれるか」 です。ブラウザの保存先を別のフォルダに変えている場合は、パターンもそれに合わせてください
おわりに
今回の手口は、エージェントが 「起動したフォルダの指示ファイルを信じる」 という、便利さの裏返しを突いたものでした。ソフトの修正では直らないぶん、使う側で「どこの指示ファイルを信じるか」を決めておく必要があります。
claudeMdExcludes とフックは、どちらも数行の設定で入れられます。まずは自分の PC で試して、問題がなければ Team プランの管理設定で組織に配る、という順番がおすすめです。CLAUDE.md や AGENTS.md を「読ませる」設定はよく話題になりますが、 「読ませない」設定も持っておく と安心だと感じました。
以上、どなたかの参考になれば幸いです。
参考










