情報漏えいやランサムウェアのニュースを、ほぼ毎日のように目にします。一つひとつの報道は断片的なので、国内でどんな被害がどれくらい起きているのかを、まとめて見たいと思ったのがきっかけです。そこで、公表済みのインシデントを一覧できるダッシュボード「SecuMiru(セキュミル)」を個人開発しました。仕様と編集方針は筆者が決め、実装は AI エージェント(Claude Code)に任せています。
前編では、このサイトで一番大事にした「公式発表で確認できたものだけを、正しく数える」ためのデータ設計をまとめます。情報の集め方と運用の話は後編で書きます。
検証環境
- OS: macOS 26.7.1 / Node.js v26.12.1
- React 19.3 / React Router 8.4 / Vite 8.3.4 / TypeScript / zod 4.6 / Vitest
- ホスティング: Cloudflare(静的サイト、ビルド時にページを事前生成)
- 開発期間: 2026年10月18日〜23日(v1.1.0 まで)。数値は 2026年10月25日時点
何を作ったか
SecuMiru は、国内のサイバー攻撃・情報漏えいのうち、企業や自治体が公式に発表したものを集めたダッシュボードです。3 つのページで構成しています。
- インシデント: 公表済みの事例。種類(不正アクセス・ランサムウェアなど)、漏えいの状況、サービスの復旧状況、時系列、出典を事例ごとに載せる
- 注意喚起: JPCERT/CC と IPA が出したセキュリティの注意喚起
- 脆弱性: 注意喚起で取り上げられた CVE。深刻度と、実際に悪用されているか(CISA の KEV カタログへの登録)を表示する
5 日間で v1.1.0 まで進め、コミットは 215 件、マージした PR は 71 本でした。コミットのうち 147 件が Claude との共同コミットです。2026年10月25日時点で、公開中のインシデントは 116 件、注意喚起は 70 件、脆弱性は 62 件です。
方針1:報道だけでは載せない
最初に決めたのは「根拠は公式発表」という方針です。企業・自治体自身の発表か、JPCERT/CC や IPA などの公的機関の情報で確認できたものだけを公開します。報道は事例を見つける手掛かりにとどめ、出典として残す場合も「二次情報」として区別します。
そのため、出典には種類を持たせました。
export type Source = {
title: string;
url: string;
publisher: string;
publishedAt: string; // YYYY-MM-DD (JST)
sourceType: 'official' | 'public_agency' | 'secondary';
};公開状態の事例には、official(組織自身の発表)か public_agency(公的機関)の出典が最低 1 件必要です。報道(secondary)だけの事例は公開できません。
方針2:「確定していないこと」を混ぜない
インシデントの発表には、「漏えいを確認した」ものもあれば「漏えいの可能性がある」「調査中」のものもあります。これを 1 つの「被害あり」にまとめると、まだ確定していない組織を漏えい企業のように見せてしまいます。
そこで状態を 3 つの軸に分け、それぞれに「未公表」を用意しました。
export type LeakStatus =
'confirmed' | 'possible' | 'not_observed' | 'not_applicable' | 'undisclosed';
export type InvestigationStatus = 'ongoing' | 'completed' | 'undisclosed';
export type ServiceStatus =
'disrupted' | 'partially_restored' | 'restored' | 'not_affected' | 'undisclosed';「サービスは復旧した」と「情報は漏れていない」は別の話なので、1 つの「復旧済み」で済ませないためです。not_affected(サービスへの影響なし)は、運用を始めてから追加しました。「停止はなかった」と公表した事例を「復旧」と書いていたことに気づき、レビューの指摘を受けて分けています。
影響規模にも、確定度を持たせています(コメントは説明のために追記)。
export type Impact = {
unit: ImpactUnit; // 人・アカウント・レコード・組織など
count: number | null; // 未公表は null
certainty: 'confirmed' | 'possible' | 'unknown';
explanation: string;
sourceUrl: string;
};方針3:集計で事実をゆがめない
ダッシュボードは数字が主役です。そのぶん、数え方を誤ると、事実と違う印象を与えます。仕様書には集計のルールを先に書き、それに沿って実装させました。主なルールは次のとおりです。
- 1 件のインシデントは「同じ組織の同じ事故」。続報は件数に足さず、同じデータに追記する
- 「人」「アカウント」「レコード」など単位の違う数値は合算しない
- 各事例の対象人数を足して「被害者の実人数」として表示しない(同じ人が複数の事例に含まれうるため)。合計を出す場合は「延べ」と明記する
- 数値が未公表なら
null。0 として扱わない - 「確認済み」と「可能性あり」は別に集計する
- 件数は「このサイトが収録した公表事例の数」であり、国内全体の発生件数ではないと明記する
どれも当たり前に見えますが、データの形で強制しておかないと、表示を作る段階で簡単に崩れます。たとえば count を number 型にすると、未公表を 0 で埋める実装が書けてしまいます。null も取れる型にしておけば、未公表のときの表示(このサイトでは「集計対象なし」)を必ず考えることになります。
検証でビルドを止める
方針をデータの形にしたうえで、ビルドの前に zod でデータ全件を検証しています。ルールに反するデータがあれば、ビルドが止まって公開されません。公開状態の事例に一次情報の出典が無い場合の検証は、次のようになっています(実際のコードの一部)。
if (v.editorialStatus === 'published') {
if (!v.reviewedAt) ctx.addIssue({ /* ... */ message: 'published には reviewedAt が必要です' });
if (!noSourceException && !v.sources.some((s) => s.sourceType !== 'secondary')) {
ctx.addIssue({ /* ... */ message: 'published には一次情報(official / public_agency)が必要です' });
}
}ほかにも、事例と CVE を結びつける根拠は組織自身の公式発表に限る、脆弱性の更新日が KEV の登録日より前にならない、といった検証を入れています。人が気をつけるだけでは漏れるルールほど、検証に回すようにしました。
数字で見えてきたこと
登録した 118 件(公開 116 件・下書き 2 件)を種類別に数えると、不正アクセスが 96 件で最も多く、次がランサムウェアの 25 件でした(1 件に複数の種類が付くことがあります)。
日付にも特徴がありました。10月23日の時点で、公開 99 件のうち 44 件は発生日が公表されていませんでした。発生日の順に並べようとすると、半数近くが並べられません。そこで一覧は、発生日が無ければ検知日、それも無ければ公表日で並べ、どの日付で並んでいるかを「発生」「検知」「公表」と表示するようにしました。
まとめ
- 「公式発表で確認できたものだけ載せる」方針は、出典の種類をデータに持たせると守りやすい
- 漏えい・調査・サービスの状態は別々の軸で持ち、それぞれに「未公表」を用意する
- 未公表を
nullにし、単位の違う数値を合算しないなど、集計のルールは型で強制する - 人が気をつけるだけでは漏れるルールは、検証でビルドを止める形にする
データの検証には zod を使っています。脆弱性の深刻度と悪用状況は、NVD と CISA の KEV カタログの値をそのまま採用しています。後編では、AI に情報を集めさせる運用と、公式発表が消えてしまう問題について書きます(11月9日公開予定)。依存パッケージの脆弱性対応については「Dependabot アラート39件を解消した記録」でも書いています。




