ここのところ、静的診断や動的診断などを経て、対策はばっちりしているつもりでした。ただ、Claude Fable 5.2 はセキュリティ診断に強いと言われていたので、試してみるしかないと思いました。出たら嫌だなと思っていたら、本当に問題が見つかり、慌てて直しました。
CSRF 対策、CSP、外部スクリプト、Docker を中心に、やったことと確かめ方をまとめます。
これは私の実体験に基づく話です。その点にご留意ください。
注意: この記事で触れる診断ツールは、自分で管理している環境にだけ使ってください。許可のない相手には使わないでください。本番環境や、ほかの人と共有している環境でも使わないでください。
この記事で分かること
- フレームワークに頼らない自作ブログで、守りを固めるときの考え方
- CSRF 対策を「2枚」にする方法
- CSP を「報告だけ」から「実際に止める」に切り替える手順
- Docker のイメージを小さく・固くする方法(1150MB → 483MB)
- 直したあとに、毎回同じ手順で確かめるやり方
- AI(Claude Code)と進めたときのトークン数
はじめに
このブログは、大きなフレームワークを使わずに、ログイン・記事の編集・お問い合わせなどをほとんど私自身が実装しています。 といってもfacebook社製のossを利用しており、全てではありません。
自前ということは、有名なフレームワークが最初から用意してくれる「安全のための仕組み」を、全部自分で用意しないといけないということです。
今回は Claude Fable 5.2 の性能を確認するため、ソースコードをまるごと見直して、見つかったものをその日のうちに直しました。本当に出るとは思わなかったので慌てました(;'∀')
やったことを残しておきます。
何を調べたか
Fable 5.2 に調査依頼を出してソースコード全体を、ブラウザから入力されるところ、保存されるところ、画面に出るところまで順に隅々まで追いかけました。
見たのは、よくある5つの分類です。
- インジェクション(SQL インジェクション・XSS・コマンドの実行・パストラバーサル)
- 認証とセッション(パスワードの保存方法・セッション・CSRF)
- ファイルのアップロード
- エラーとログ(余計な情報を出していないか)
- 設定(セキュリティヘッダ・秘密情報の扱い)
結果
幸いなことに、致命的なもの(Critical)はありませんでした。
ただ、「今すぐ破られるわけではないけれど、守りが1枚しかない」ところがいくつか見つかりました。放置するのも気持ちが悪いので、今回、すべて対応しています。
直したこと
1. CSRF 対策を2枚にした
用語メモ: CSRF 別のサイトに仕込んだわなから、ログイン中の人のブラウザを使って、勝手に操作させる攻撃です。
今までは、Cookie の SameSite 属性の1枚だけで守っていました。これに加えて、データを変えるリクエストを受けたときに、ブラウザが付けてくる送信元を示すヘッダを見て、このサイトの画面から送られたものかを確かめるようにしました。別のサイトからの送信は断ります。
Cookie の設定と、送信元の確認の2枚になったので、どちらかが効かない状況でも守れます。
2. CSP を「報告だけ」から「実際に止める」にした
用語メモ: CSP と nonce CSP(Content Security Policy)は、ページで動かしてよいスクリプトをブラウザに伝える仕組みです。万一、ページにスクリプトが紛れ込んでも、動かさないようにできます。XSS に対する最後の防壁です。 nonce は、アクセスのたびに作るランダムな値のことです。この値を付けたスクリプトだけを動かします。
このブログでは、アクセスのたびに nonce を作り、その値を付けたスクリプトだけを動かす形にしています。広告が動くように、Google が AdSense 向けに案内している形に合わせました。
今までは、広告が止まるのが怖くて「違反を報告するだけ」の設定でした。実際に止める前に、自分のコードで止まってしまうところを探して直しました。
href="javascript:..."のリンクonclick="..."のように、HTML に直接書いたイベント
どちらも nonce の方式では動かなくなるので、addEventListener でイベントを登録する形に置き換えました。
確かめるために、ヘッドレスの Chrome で CSP を実際に効かせて、全部の画面を開いて操作しました。わざと紛れ込ませたスクリプトが、ちゃんと止まることも確かめています。
CSP を入れるときのコツ: いきなり止めずに、まず「報告だけ」で入れて、報告を見ながら自分のコードで引っかかるところをつぶす。それから止める。こうすると、サイトを壊さずに入れられます。
3. 外部の CDN からのスクリプトの読み込みをやめた
用語メモ: CDN と SRI CDN は、ファイルを世界中のサーバーから配信する仕組みです。SRI(Subresource Integrity)は、読み込むファイルのハッシュを
integrity属性に書いておき、書き換えられていたら実行しないようにする仕組みです。
一部のスクリプトを、外部の CDN からそのまま読み込んでいました。CDN が乗っ取られると、知らないスクリプトがこのサイトで動いてしまいます。
公式の配布物を、公式に公開されているハッシュと照合してから取り出し、自分のサーバーから配信するようにしました。さらに SRI を付けて、ファイルが書き換えられていたら実行しないようにしています。
つまずいたのは、Windows の git の改行の自動変換です。改行が変わるとハッシュが合わなくなるので、外部のライブラリのファイルだけ変換しないように、.gitattributes で指定しました(-text)。
4. アプリが自分のプログラムを書き換えられないようにした
Docker のコンテナで、プログラムのフォルダを書き込みできる状態で使っていました。アプリに穴が1つあると、プログラムを書き換えられて、再起動しても残ってしまいます。
アプリが本当に書き込む必要のある場所を調べて、そこだけ書き込めるようにし、残りは読み取り専用でマウントしました(Docker の :ro)。
5. Docker のイメージを小さく・固くした
用語メモ: マルチステージビルド Docker のイメージを作る手順を、複数の段階に分ける方法です。ビルドする段階と動かす段階を別にして、動かすほうには必要なものだけを入れます。
動かすためのイメージに、コンパイラやダウンロード用のツールが残っていました。乗っ取られたときに、攻撃者の道具になります。
ビルドを2段階に分けました(マルチステージビルド)。動かすほうには、ビルドの結果と、実行に必要なものだけを入れています。必要な共有ライブラリは ldd で調べました。ベースのイメージも latest をやめて、版を固定しました。
| 前 | 後 | |
|---|---|---|
| イメージの大きさ | 1150MB | 483MB |
| コンパイラ・ダウンロード用のツール | あり | なし |
毎週のパッチの当て直しは、ビルドの段階がキャッシュされるので、今まで通り1分くらいで終わります。
6. その他の細かいもの
- ログに残す内容を見直して、残す必要のない情報を出さないようにした
- メールを送るときに、相手のサーバーの証明書を確かめるようにした(使っている標準の機能は、既定のままだと証明書を確かめません。知りませんでした)
- エラーの内容を画面に返さず、ログにだけ残すようにした
- パスワードを変えたときに、ほかの端末のログインも切れるようにした
- ログイン後の画面を、ブラウザにキャッシュさせないようにした
おまけで、自分が記事を見ても PV が増えていたので、ログイン中は数えないようにしました。
確かめ方
直したら、毎回決まった手順で診断しています。
動的診断は、インターネットにも本番にもつながらない、隔離した Docker のネットワークの中だけで行っています。本番に疑似攻撃は送りません。
今回は3回通して、すべて合格でした。
つまずいたところ: パスワードに「$」を入れたら一部が消えた
パスワードを変えて docker compose up したら、こんな警告が出ました。
WARN[0000] The "abc" variable is not set. Defaulting to a blank string.
(表示の文字は、実際のものから変えています)
docker compose は、env_file に書いた値の中の $abc を「変数」として読んで、空の文字に置き換えてしまいます。パスワードの一部が消えて、設定と合わなくなります。
値をシングルクォートで囲むと、そのままの文字として扱われます。
EXAMPLE_VALUE='xxxx$abcxxxx'
ランダムなパスワードを作ると $ が入ることはよくあるので、注意してください。警告の文には、パスワードの一部がそのまま出てしまうので、人に見せるときも気をつけましょう。
追記: その後再度パスワードを変更しました。実は夜間バッチでコールドバックアップを取っているのですが、データベースのバックアップツールのエラーがどうしても解決できずに問題となる記号が入らないパスワードに変更してようやく解決しました。本番環境のパスワードなんてそうそう変えたくないです。お祭りになります(´;ω;`)ウッ…
AI に手伝ってもらった量(トークン数)
今回の監査と修正は、Claude Code(Anthropic の AI)と一緒にやりました。途中でモデルを切り替えています。
- Claude Fable 5.2: ソースコードの監査と、レポートの作成
- Claude Opus 5.5: 指摘の修正、検証、Docker のイメージの作り直し、この記事の草案
使ったトークン数は次の通りです(Claude Code の会話のログから集計。2026-11-11 23:37 時点)。
| モデル | API の呼び出し | 入力(キャッシュ以外) | キャッシュへの書き込み | キャッシュからの読み込み | 入力の合計 | 出力 |
|---|---|---|---|---|---|---|
| Claude Fable 5.2 | 17回 | 36 | 267,555 | 3,158,561 | 3,426,152 | 43,464 |
| Claude Opus 5.5 | 107回 | 246 | 445,119 | 40,618,523 | 41,063,888 | 117,302 |
| 合計 | 124回 | 282 | 712,674 | 43,777,084 | 44,490,040 | 160,766 |
入力のほとんどが「キャッシュからの読み込み」なのは、呼び出しのたびに、それまでの会話をまるごと読み直しているからです。会話が長くなるほど、1回あたりの入力が増えていきます。キャッシュからの読み込みは通常の入力より安く計算されるので、実際の料金は数字の見た目ほどにはなりません。
まとめ
- 自作だと、フレームワークが黙って守ってくれていたものを、全部自分で用意する必要があります。「致命的なものは無い」でも、守りが1枚だけのところは多いので、2枚目を足すのが大事です
- CSP は、先に「報告だけ」で入れて、自分のコードで止まるところをつぶしてから効かせると安全です
- 直した後に、毎回同じ手順で確かめられるようにしておくと楽です
個人のブログでも、公開した瞬間から攻撃は毎日届きます(このブログのセキュリティ日報を見るとよく分かります)。少しずつでも固めていきましょう。
余談
Proプランでこの対応はやってみたのですが、あっというまにこんなことになりました。

Fable5.2怖い・・・物凄い勢いで減るのでご注意を!