2025年11月、Anthropic は国家支援型のスパイ活動を遮断したと公表しました。そこでは Claude Code が偵察から侵入、横展開、データの持ち出しまで、侵入作業の80〜90%を自律的に実行していました。標的は約30組織 (incident 1263)。 この作戦を止められたのは、商用APIの上で動いていて、提供元がアカウントを消せたからです。 いまやフロンティア級のモデルは重みごと無料で配布されているので、次はそうはいきません。 攻撃の「調べ上げる」工程は、あらゆるサイトに向け続けても割に合う値段まで下がりました。
導入
1ファイル、依存なし。PROOFWIRE_KEY を環境変数に置き、 ボディパーサーの後ろに差し込むだけです。判定・スコアリング・相関はすべてAPI側で行います。
curl -o proofwire.js https://layersecurity.jp/proofwire/express.jsconst proofwire = require("./proofwire")({ block: true });
app.use(proofwire.express());block: true を外せば、遮断せず通知だけになります。Node 以外なら、Cloudflare Worker 版が同じ働きをします。 PHP でも Rails でも Java でも静的サイトでも、アプリ本体に一切手を入れずに前段へ置けます。
ターミナルだけで済ませることもできます。あとからログインするためのパスワードが、 このとき一度だけ返ります。
curl -sX POST https://api.layersecurity.jp/v1/signup \
-H 'content-type: application/json' -d '{"email":"you@company.com"}'
{"key":"pw_…","password":"…","console":"https://layersecurity.jp/proofwire/app/"}実際の見え方
同じアプリ上の、2つのセッションです。
この鍵は、どこにも公開していません。リンクもされていないし、robots.txt にも、 リポジトリにも存在しません。存在した場所はただ一つ、17秒前にこのセッションへ返したページの中だけです。 正規の利用者も、正規のクローラーも、正規の連携先も、このリクエストを出す経路がありません。 これは推測ではなく自白であり、自白に誤検知率はありません。
既存の判定が効かない理由
| 判定材料 | なぜ効かないか |
|---|---|
| リクエスト頻度 | 人間が更新を連打すれば10秒で15回叩きます。逆にエージェント側が 4秒に1回へ落とせば、それだけで閾値の下に隠れます。 |
| User-Agent、ヘッドレス判定 | 設定1行で変わります。まともなツールはすでに全部詐称しています。 |
| 「自動化されて見える」 | 顧客が使っているエージェントも自動化されて見えます。ここで遮断すると、 失うのは取引先です。 |
| ハニーポット、偽の /admin | 存在を広告してしまっています。能力の高いエージェントは、アプリ本体と 表面が食い違うことに気づいて迂回しますし、しかも「見張られている」と知ります。 |
| WAFのシグネチャ | すでに誰かが見たペイロードに対して書かれたものです。モデルは、 ルールが書かれていないリクエストをその場で組み立てます。 |
| こちらからしか渡りようのない値を使った | 正規のクライアントには、原理的に、一度も、出せません。 |
仕組み
- 何も広告しません。 目印も、偽のルートも、追加のホスト名も、バナーも置きません。 スキャナから見た表面はオリジンとバイト単位で同一で、404もアプリ本体が返す本物です。 気づく手がかりが無いので、迂回のしようもありません。
- 値は、もともと返しているレスポンスの中に紛れます。AWSキー、本番APIキー、データベースURLの形をした値を、HTMLコメントとJSONフィールドに 1つずつ置きます。値はあなたの鍵からHMACで導出するので、こちらは保存していませんし、 顧客どうしで同じ値になることもありません。
- 見ているのは「使ったかどうか」で、「らしさ」ではありません。速い、スクリプトから来ている、ヘッドレスである — こうした性質は、 どれだけ揃っても確定にはなりません。確定できるのは、値を使ったときだけです。
- 遮断はローカルで即座に決まります。 クライアントは値を手元に持っているので、 その値を含むリクエストはその場で拒否できます。自白から遮断までの隙間がありません。
- 行動シグナル(連射・探索の広さ・思考時間ゼロ・404の総当たり)はスコアを上げますが、 単独では
likely止まりで、遮断はしません。 しかも「10パス以上を舐めている」ことを前提条件にしています。 人間が更新を連打すると、それ以外はほぼ全部立ってしまうからです。 - 壊れたら素通しします。 APIが落ちても、枠を使い切っても、設定が取れなくても、 レスポンスはそのまま出ます。計測はレスポンスを返した後ろでまとめて送るので、 応答時間に乗りません。
できないこと
- 人間らしく振る舞う、辛抱強い人間の攻撃者は止められません。これは割り切りです。 その代わり、通知を開いても選別する対象がありません。
- WAFではありませんし、置き換えるものでもありません。
blockを切れば、 何も遮断しません。 - 遮断すると、相手は捕捉されたことに気づきます。泳がせたい場合は通知のみで運用してください。 検知の内容は同じです。
- 値がどこまで届くかで精度が決まります。資格情報の形をしたものが攻撃者の手元に 一切渡らないなら、自白のしようがありません。
- クライアントは
text/htmlとapplication/jsonのボディを 書き換えます(コメント1つ、_kフィールド1つ)。 レスポンスに署名やハッシュを付けている場合は、対象ルートを絞ってください。 - セッションの識別は IP + User-Agent です。共有NATの内側では粗くなります。 ただし自白そのものは、セッション識別の精度に依存しません。
- v0です。APIの形は変わります。第三者によるセキュリティレビューはまだ受けていません。
他の言語から使う
HTTPで2回。プロトコルはこれで全部です。
# 何を仕込むか(キャッシュして数時間ごとに更新)
GET https://api.layersecurity.jp/v1/config Authorization: Bearer pw_…
-> { "bait": [ { "id":"aws", "placement":"html-comment", "value":"AKIA…" }, … ] }
# 何が起きたか(最大200件まとめて、投げっぱなし)
POST https://api.layersecurity.jp/v1/events Authorization: Bearer pw_…
{ "events": [ { "session":"e6dd", "path":"/api/keys", "status":403,
"dt":4, "bait":"AKIA…" } ] }
-> 202 { "alerts": [ { "level":"confirmed", "score":0.95,
"evidence":["decoy_hit x1","breadth=25_paths"] } ] }bait には、受信リクエストの中に見つけた値を入れます。無ければ null です。
料金
| 無料 | 月10万イベントまで。機能制限なし。カード登録不要。 |
| 超えたら | 429 とリセット日を返します。何も壊れません (クライアントは素通しを続けます)。連絡をもらえれば枠を上げます。 |
| 保存するもの | パス、ステータス、User-Agent、ハッシュ化したセッションID、 そして仕込んだ値が現れたかどうか。リクエストボディも、レスポンスボディも、 ヘッダも保存しません。 |