脆弱性まとめサイトでCVEごとにHowTo/FAQ構造化データを自動生成する(データ駆動+掲載ガード)

脆弱性情報を「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以前に、情報サイトとしての最低限の信頼性をコードで担保できる。

HowTo:remediationからステップを生成

対応手順は 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と信頼性の両方に効いてくる。


脆弱性まとめサイトでCVEごとにHowTo/FAQ構造化データを自動生成する(データ駆動+掲載ガード)
https://blog.hashito.biz/2026/07/18/fleet-cve-watch-howto-faq-jsonld-per-cve/
著者
hashito
作成日
2026年7月18日
著作権