AIエージェントにWeb情報の検索やリサーチを委譲し、生成された回答の末尾に出典URLを添えて報告させる仕組みは、業務自動化の現場で広く使われています。エージェントが「存在しない架空のURL」をハルシネーション(嘘)で出力するリスクを防ぐため、curl コマンド等を用いた自動検収スクリプトを組み込み、「HTTP 200 OK が返ってくれば検証成功」と判定して安心している開発者も多いのではないでしょうか。

しかし、その自動検収システムは本当に機能しているでしょうか。

当プロジェクトで運用している出典自動検証ツールにおいて、29 件の検収対象 URL を再検証したところ、単純な HTTP ステータスコードチェックと甘い正規表現パターンでは防ぎきれない 2件の Soft 4045件の正規表現誤抽出バグ(計 7 件のすり抜け)を特定しました。

本記事では、単純な HTTP 200 OK チェックの裏で静かに起きる「検証すり抜け」のメカニズムと、実機ログに基づくロバストな検収スクリプト設計のプラクティスを解説します。


罠1: ステータス 200 OK で騙す「Soft 404」の構造

Webサイトの検証において、最も素朴で強力に思える指標が HTTP ステータスコードです。

なぜ HTTP 200 なのに Not Found なのか?

Web の標準規格である RFC 9110(HTTP Semantics)では、リクエストされたターゲットリソースが存在しない場合、サーバーは 404 Not Found ステータスコードを返すべきであると定義されています12

しかし、現代の Web アプリケーション(Single Page Application や CMS、S3 + CloudFront による静的サイト配信など)では、存在しないパスに対してもルーティング層で 200 OK301/302 リダイレクトを返し、共通のトップページやエラー表示用コンポーネントを描画する実装が横行しています。このような「レスポンスは 200 OK だが画面上はコンテンツが存在しない」状態は Soft 404 と呼ばれ、Google Search Central でも検索エンジンのクローリングリソースを浪費する問題として警告されています3

AIエージェントが出力した URL に対して curl でステータスコードのみを取得していると、この Soft 404 ページをすべて「実在する有効な出典」として検収通過させてしまうことになります。

実測ログに見る Soft 404 の2大パターン

当プロジェクトの実測ログにおいても、29 件中 2 件の典型的な Soft 404 が特定されました。

Note

実測された Soft 404 の事例

  • パターンA(リダイレクト後 200 OK + プロフィール返却): https://hidekazu-konishi.com/blog/ai-agent-memory-design-guide 存在しない /blog/<任意> パスへアクセスすると、301 リダイレクト後に HTTP 200 OK を返し、プロフィール画面を表示する(正しい URL は /entry/ai_agent_memory_design_guide.html)。AI調査エージェントはドメインは正しく取得していたものの、パス構造を捏造していました。
  • パターンB(ステータス 200 OK + 本文中エラーメッセージ): https://arxiv.org/abs/2603.99999 存在しない論文 ID を指定しても HTTP 200 OK を返し、HTML 本文および <title> タグ内に [2603.99999] Article not found を出力する。

どちらのケースも、ステータスコードだけを見ている検収スクリプトは 200 OK を受信するため、「正常な出典」として台帳に登録してしまいました。

対照群(dummy path)リクエストと <title> 比較アルゴリズム

この Soft 404 の罠を機械的に見破るため、検収スクリプトに 対照群(dummy path)リクエスト アルゴリズムを導入しました。

  1. 対象 URL へリクエストを送り、ステータスコード 200 OK を確認する。
  2. 同一ドメイン・同一階層の明らかに存在しないランダムなダミーパス(例: /blog/zzz-definitely-not-a-real-page-12345)へ対照群リクエストを送信する。
  3. 両者の HTML から <title> タグや特定のエラーメッセージ文言を抽出して比較する。

検証対象 URL とダミーパスの <title> が完全一致した場合、そのドメインは不在パスに対しても同一の共通ページを返す Soft 404 サイトと判定し、自動的に verdict=捏造 として台帳に記録します。

ただし、サイト全体で固定の共通タイトルを表示する設計のWebサイト(偽陽性のリスク)や、論文IDごとに動的なエラータイトルを出力するサイト(偽陰性のリスク)も存在するため、タイトル比較に加えて「Article not found」等のエラーキーワード検索や DOM 本文の差分確認を併用することが推奨されます。


罠2: 検収スクリプト自身が汚染源になる「正規表現パースバグ」

Soft 404 が「Webサーバー側の挙動による罠」であるとすれば、もう一つの罠は「検収スクリプト側の実装不備による自爆」です。

甘い正規表現 [^ )>"]+ が引き起こした惨事

エージェントが出力したテキスト(Markdown フォーマット)から URL を抽出する際、多くの場合で正規表現が使われます。しかし、以下のような簡易なパターンを使っていると大きな落とし穴にハマります。

# 失敗例: 甘い URL 抽出正規表現
grep -ohE 'https?://[^ )>"]+' text.md

この正規表現は、「空白・閉じ括弧・山括弧・ダブルクォート以外の文字」をすべて URL の一部として認識します。その結果、Markdown の強調記号 ** やコードブロック記号 `、さらに日本語文章中の全角句読点 までを URL 末尾に巻き込んで抽出してしまいました(検収対象 29 件中 5件 で発生)。

実際に抽出されてしまった汚染 URL の例:

  • https://hidekazu-konishi.com/entry/ai_agent_memory_design_guide.html** (Markdown の太字装飾が末尾に付着)
  • https://towardsdatascience.com/.../ai-agent-memory-12345`、`https://hidekazu-konishi.com/blog/ai-agent-memory-design-guide`、および (複数の URL がバッククォートと接続詞「、および」ごと連結され、1本の巨大な文字列として認識)

これらは URI の汎用構文規格である RFC 3986 や CommonMark 仕様に照らしても明らかに不正な文字列です45

なぜ汚染された URL が「採用(実在)」判定を通過したのか?

問題は、なぜこの末尾に ** や日本語が混入した無効な文字列が「検収成功」となってしまったのかという点です。

原因は、汚染された文字列のまま curl でリクエストを送信した際、Web サーバー側の挙動(ルーターによるリダイレクトやルートアクセス処理、あるいは WAF による 403 レスポンス)によって、たまたま 200403 が返却されたことにあります。検収スクリプトはこれを「実在する有効な URL」と誤判定し、台帳に登録してしまいました。

「エージェントが嘘の URL を生成した」のではなく、「検収スクリプト自身のパースバグによって無効な URL が生成され、それが検収を通過していた」 という盲点でした。

末尾記号のクレンジング処理

この正規表現汚染を防ぐため、抽出ロジックの強化と決定論的な末尾クレンジング処理を追加しました。

# 1. 抽出パターンの強化(アスタリスクやバッククォートを除外対象に追加)
urls=$(grep -ohE 'https?://[^][ )>"`*<]+' target.md)

# 2. sed による末尾装飾・全角句読点の切除
clean_url=$(echo "$urls" | sed 's/[、。].*$//; s/[.,、。)*`]*$//')

日本語の句読点 以降を即座にカットし、末尾に残った . , * ` などの装飾記号を除去することで、正確な URL のみを抽出できるようになります。


実践: ロバストな検収スクリプト verify-urls.sh の設計

Soft 404 の自動検知と正規表現クレンジングを組み込み、無駄な通信コストを最小化する検証スクリプト verify-urls.sh の全体設計パターンを紹介します。

追記専用台帳 sources.tsv による永久ブロック構造

検証結果は、追記専用の TSV 台帳(sources.tsv)に記録します。

checked_at	url	status	verdict	origin
2026-08-06T11:30:15+09:00	https://hidekazu-konishi.com/blog/ai-agent-memory-design-guide	200	捏造	blog-source-check

一度 verdict=捏造 と確定した URL は、次回以降の検証時にネットワークリクエストを行わず、即座にブロック対象としてスキップします。これにより、無駄なクローリング通信を削減し、検収処理を高速化できます。

キャッシュとフレッシュネス(30日)管理

過去に 200 または 403 で確認済みの実在 URL についても、検証のたびに毎回リクエストを送るとサーバー負荷やアクセス制限の原因になります。

# 30日前のアカウントカットオフ日付を計算(macOS / Linux 互換構文)
cutoff=$(date -u -v-30d +%Y-%m-%d)

台帳上の最終検証日が 30 日以内である場合は、過去の検証結果をそのまま再利用するフレッシュネス管理を行っています。

cURL オプションと HTML 取得の分離設計

検証リクエストを実行する際、ステータスチェックとコンテンツ取得を分離した二段階構成にすることでパフォーマンスを最大化しています。

# 1. 高速ステータス確認(ボディを破棄してヘッダーのみでチェック)
code=$(curl -s -o /dev/null -w '%{http_code}' -L --max-time 20 \
  -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" "$url")

# 2. 200 OK 返却時のみ HTML ボディを取得して Soft 404 検証
if [ "$code" = "200" ]; then
  html=$(curl -s -L --max-time 20 \
    -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" "$url")
  # <title> パースおよび対照群比較ロジックを実行
fi
  • -s: プログレスメーターを非表示にする静音モード
  • -o /dev/null: レスポンスボディを破棄し高速化
  • -w '%{http_code}': レスポンスの HTTP ステータスコードのみを出力
  • -L: 301/302 リダイレクトを自動追従
  • --max-time 20: 20秒のタイムアウトを設定しハングアップを防止
  • -A "...": Bot 遮断(403)を回避するための標準的な User-Agent 設定

まとめ: 単純ステータスチェックからの脱却

AIエージェントの自動化パイプラインにおいて、出力された出典情報の正確性を担保することはシステムの信頼性に直結します。

しかし、「curl で HTTP 200 OK が返ってきたから大丈夫」という単純な前提は、今回解説した Soft 404 や正規表現パースバグによって簡単に破綻します。

  1. HTTP 200 OK を過信せず、対照群リクエストやタイトル/文言検証による Soft 404 判定を入れること
  2. 抽出正規表現の境界条件を厳密にし、末尾記号の決定論的クレンジングを行うこと
  3. 検証結果を台帳管理し、無駄な再検証コストを削ること

自動化パイプラインの検収ロジックを見直し、すり抜けのないロバストなシステムを構築していきましょう。

AIエージェントの自動化パイプライン構築や品質管理スクリプトの導入支援については、コンタクトフォーム からご相談ください。

Footnotes

  1. RFC 9110: HTTP Semantics

  2. MDN Web Docs - 404 Not Found

  3. Google Search Central - Soft 404 エラーについて

  4. RFC 3986: Uniform Resource Identifier (URI): Generic Syntax

  5. CommonMark Spec 0.31.2