脆弱性情報を「CVE番号×製品×対応方法」の日本語長尾で拾うサイト cve.autoarticles.net を運用している。要は静的サイトで、真実の定義は1本のJSON(data/vulns.json)だ。CVEを1件追記して node build.js を叩くと、一覧・詳細ページに加えて、各CVEページの構造化データ(Article / BreadcrumbList / HowTo / FAQPage)まで自動生成される。この記事では、その「データ1件から構造化データを機械生成しつつ、捏造を弾く」部分を実際に動くコードで示す。
データ構造:1CVE=1オブジェクト
各CVEは次のような素直なオブジェクトで持つ。対応状況は環境ごとに分けておく。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const vuln = { cveId: "CVE-2025-34291", title: "Langflow オリジン検証不備によるアカウント乗っ取り・RCE", cvssScore: 8.8, severity: "High", vendorProject: "Langflow", product: "Langflow (<= 1.6.9)", source: "https://nvd.nist.gov/vuln/detail/CVE-2025-34291", summary: "過度に許容的なCORSとSameSite=Noneのリフレッシュトークンが…", direction: "1.6.9より後へ更新。暫定回避としてCORSを厳格化し…", remediation: { aws: { status: "app-patch", fix: "ECS/EKS/EC2上のLangflowを更新" }, linux: { status: "app-patch", fix: "pip/コンテナで導入した版を更新" } } };
|
掲載ガード:捏造・低品質を機械的に弾く
自動運用で怖いのは「それっぽいが裏の取れないデータ」が混ざることだ。そこでビルドの入り口に検証ゲートを置く。掲載基準は「High/Critical・CVSS>=7.0・source(有効なURL)必須」。満たさなければビルドを止める。
1 2 3 4 5 6 7 8 9 10 11 12
| function validate(v) { const score = Number(v.cvssScore); if (!(score >= 7.0)) { throw new Error(`${v.cveId}: CVSS ${score} は掲載基準(>=7.0)未満`); } if (!/^https?:\/\//.test(v.source || "")) { throw new Error(`${v.cveId}: source(一次情報URL)が必須`); } if (!["High", "Critical"].includes(v.severity)) { throw new Error(`${v.cveId}: severityはHigh/Criticalのみ`); } }
|
このゲートがあると、たとえ上流のデータ収集が緩くても、最終的に公開されるのは「一次情報URL付き・CVSS 7.0以上」だけになる。SEO以前に、情報サイトとしての最低限の信頼性をコードで担保できる。
対応手順は remediation の「確認できた事実」だけからステップ化する。ここで存在しないパッチ番号などを足さないのが肝心だ(データに無いことは書かない)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function howToJsonLd(v) { const steps = Object.entries(v.remediation) .filter(([, r]) => r.fix) .map(([env, r], i) => ({ "@type": "HowToStep", position: i + 1, name: `${env.toUpperCase()} での対応`, text: r.fix })); if (!steps.length) return null; return { "@context": "https://schema.org", "@type": "HowTo", name: `${v.cveId} の対応手順`, step: steps }; }
|
FAQPage:実務クエリをそのまま質問にする
日本語の実務検索は「影響は?」「対応方法・回避策は?」「悪用されている?」に集約されやすい。回答は summary / direction の確認済みテキストから組み立て、捏造しない。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function faqJsonLd(v) { const qa = [ ["影響は?", `${v.severity}(CVSS ${v.cvssScore})の脆弱性です。${v.summary}`], ["対応方法・回避策は?", v.direction], ]; if (v.kev) qa.push(["実際に悪用されている?", "CISA KEVに掲載され、悪用が確認されています。"]); return { "@context": "https://schema.org", "@type": "FAQPage", mainEntity: qa.map(([q, a]) => ({ "@type": "Question", name: q, acceptedAnswer: { "@type": "Answer", text: a } })) }; }
|
生成したJSON-LDは <script type="application/ld+json"> として各CVEページの <head> に差し込むだけだ。データを1件足せば、詳細ページ・HowTo・FAQ・一覧のリンクまで一斉に整う。
まとめ
ポイントは2つ。第一に、構造化データは「データ1件から機械生成」してテンプレの手書きを無くすこと。第二に、生成の前に検証ゲート(CVSS>=7.0・source必須・severity限定)を置き、裏の取れないデータを公開させないこと。実際の挙動は cve.autoarticles.net の各CVEページで確認できる。自動運用の情報サイトほど、この「掲載ガード」がSEOと信頼性の両方に効いてくる。