namanparikh11/threatpulse-radar
GitHub: namanparikh11/threatpulse-radar
一款聚合 CISA KEV、NVD CVSS 与 FIRST EPSS 三大公开漏洞情报源的防御性安全仪表板,提供漏洞优先级排序与可视化态势感知。
Stars: 0 | Forks: 0
# ThreatPulse Radar



**在线演示:** [https://threatpulse-radar.netlify.app](https://threatpulse-radar.netlify.app)
## 仅限防御用途
ThreatPulse Radar 是**专为防御性安全工作而构建的** ——
漏洞优先级排序、补丁规划、暴露感知以及
安全态势报告。
它**不包含任何漏洞利用代码、攻击性工具或武器化的
payload**。仪表板中的每一条“建议操作”都是
通俗易懂的防御性指导(“应用最新的供应商补丁,
轮换凭证,审查访问日志”)。
这不是一个企业级的漏洞管理平台。它也不是一个
实时的零日检测器。它不“保证”提供 NVD 丰富化数据 ——
NVD 的匿名 endpoint 受到速率限制并可能返回错误;
仪表板会如实展示这些错误,而不是掩盖它们。
## 它的功能
ThreatPulse Radar 将三个公开的防御性情报源整合到一个
可过滤的仪表板中:
| 数据源 | 提供 | 来源 |
| --- | --- | --- |
| **CISA KEV** | “此 CVE 正在被野外积极利用” | [CISA 已知被利用漏洞](https://www.cisa.gov/known-exploited-vulnerabilities-catalog) |
| **NVD CVE 2.0** | CVSS 基础分数 + 严重程度 (v3.1 → v3.0 → v2) | [NVD](https://nvd.nist.gov/) |
| **FIRST EPSS** | 未来 30 天内的利用概率 | [FIRST EPSS](https://www.first.org/epss/) |
结果就是一个单页的指挥中心:
- **6 个统计卡片** — 总计、严重、高危、KEV 列表、平均 EPSS、本周新增
- **4 个图表** — 严重程度分布、EPSS 风险分布、14 天趋势、KEV 与非 KEV 对比
- **可过滤表格** — 按 CVE ID / 供应商 / 产品 / 摘要搜索,
严重程度过滤器,仅 KEV 切换,最低 EPSS 滑块,按
最新 / CVSS / EPSS / KEV 排序
- **详情抽屉** — 完整描述、指标、建议的防御
操作,以及指向 CISA KEV + NVD 的外部链接
- **深色网络安全指挥中心主题** — 霓虹青色 / 琥珀色
点缀,桌面优先响应式
端到端使用了相同的 `Vulnerability` 数据结构,因此 filter / sort
/ chart pipeline 并不关心哪条记录是由哪个上游供应商提供的。
## 架构
```
Visitor's browser
│
▼
┌─────────────────────────────────────────────────────────┐
│ GET /.netlify/functions/dataset (Netlify Function) │
│ │
│ 1. Try `latest-dataset` blob (prebuilt envelope) │
│ ├─ hit → return immediately │
│ └─ miss → build live (bootstrap path) │
│ │
│ Response tagged with: │
│ dataSource: "prebuilt-store" | "live-build" │
│ refreshInProgress: (from refresh-lock blob) │
└─────────────────────────────────────────────────────────┘
│ ▲
│ writes │ acquires
▼ │ refresh-lock
┌──────────────────────────┐ ┌──────┴──────────────┐
│ Netlify Blobs store │ │ refresh-lock blob │
│ (name: tpr-dataset) │◄────────┤ (TTL: 15 min) │
│ ┌────────────────────┐ │ └──────▲──────────────┘
│ │ latest-dataset │ │ │ acquires
│ │ (FetchResult) │ │ ┌────────────┴───────────────┐
│ └────────────────────┘ │ │ │
└──────────────────────────┘ │ │
▲ │ │
│ ▼ ▼
┌────────┴─────────┐ ┌────────────────────┐ ┌────────────────────┐
│ Cron scheduled │ │ Background fn │ │ Browser manual │
│ (every 30 min) │ │ (manual refresh │ │ "Refresh live │
│ reads + writes │ │ from dashboard) │ │ data" button │
│ the prebuilt │ │ reads + writes │ │ POSTs to the │
│ envelope │ │ the prebuilt │ │ background fn │
└──────────────────┘ │ envelope │ └────────────────────┘
│ └────────────────────┘
│ build build
▼ ▼
┌──────────────────────────────────────────────────────┐
│ Shared build pipeline: │
│ CISA KEV → NVD CVE 2.0 → FIRST EPSS │
│ │
│ • Optional NVD_API_KEY (server-side only) raises │
│ NVD's rate-limit allowance from 5 to 50 req / 30s │
│ • Rate-limited (429) builds are blocked from │
│ overwriting a better existing prebuilt blob │
│ • 15-min NVD cooldown marker avoids hammering a │
│ known-flaky NVD │
└──────────────────────────────────────────────────────┘
```
**此架构保证了三个特性:**
1. **构建在服务器上运行一次**,而不是每个访客访问时运行。预构建的
blob 是正常流量的唯一事实来源;cron + 手动
刷新保持其最新。
2. **糟糕的刷新永远不会覆盖更好的数据。** 如果 NVD 进行速率限制
(HTTP 429),质量防护会将新构建与
现有的 blob 进行比较,并保留更好的数据包络。
3. **浏览器永远不会阻塞在上游 pipeline 上。** 冷部署后的第一个
访客只需支付一次引导成本;之后的每个人
都直接读取预构建的 blob。
## 可靠性与透明度
代码库的立场是:**故障是可见的,永远不会被隐藏。**
### 标题栏中的来源标签
每个响应都标记了促成它的实际传输方式和供应商:
- **来源:** `CISA KEV + NVD + FIRST EPSS` — 所有三个供应商
都提供了数据
- **来源:** `CISA KEV + FIRST EPSS` — NVD 不可用,仪表板
仍能正常工作(CVSS 回退到 CISA 派生的严重程度)
- **来源:** `CISA KEV` — NVD 和 EPSS 均不可用
- **代理:** `Netlify`(青色药丸)— 数据来自 serverless
endpoint,而不是浏览器直接获取
- **数据集存储:** `最新可用` / `正在引导` /
`后台正在刷新` — 预构建的
blob + 刷新锁的可见状态
### 各供应商状态标签
`NVD` 和 `EPSS` 各自都有自己的状态标签,当特定
供应商发生故障时,该标签会变为琥珀色并显示
人类可读的原因。429 错误显示为
`NVD: unavailable — rate limit reached (HTTP 429)`。网络
超时显示为 `EPSS: unavailable — request timed out`。其余
供应商的数据仍会渲染出来;故障会被通报,而
不会被掩盖。
### 没有伪造的分数
CVE 尚未出现在 NVD 中的 CISA 记录会保留 `cvssScore: 0` 以及
CISA 派生的严重程度(KEV 记录默认为 `High`;已知的勒索软件
记录默认为 `Critical`)。CVE 尚未被 FIRST 评分的 CISA
记录会保留 `epssProbability: 0`。**仪表板永远不会捏造丰富化
数据** —— 当某个供应商发生故障时,该字段为零,并且会有横幅解释
原因。
### NVD 速率限制防护
如果 NVD 返回 HTTP 429(其匿名 endpoint 仅允许 5 次请求 / 30 秒):
- Orchestrator 会检测到速率限制的原因
- 它将新构建与现有的预构建 blob 进行比较
- 如果现有的 blob 更好(CVSS 为正的记录更多,NVD
已丰富化),新构建将被**丢弃**,现有的 blob
将继续为访客提供服务
- 设置一个 15 分钟的冷却标记,以便下一次刷新
会短路注定失败的 NVD 获取
- 当冷却到期时,恢复正常刷新行为
### 手动刷新永远不会阻塞用户
点击“Refresh live data”会向
Netlify Background Function 发送 POST 请求,该函数会立即返回 `202 Accepted`。当前数据集保留在屏幕上。当新的
blob 准备好后,v5.1 轮询效应会检测到它,
并显示一个“New dataset available. Updated 2 min ago.”的横幅,并带有明确的 **Apply update** / × 控件 —— 过滤器、搜索、排序和打开的
详情视图在应用更新时都会被保留。
### API key 保留在服务端
可选的 `NVD_API_KEY` 仅从 Netlify Function 内部的 `process.env` 中读取,
作为请求头
(`apiKey: `) 传递给 NVD,并且**永远不会**出现在函数响应体中、
任何 URL 中、任何日志中或前端 bundle 中。该 key 是可选的
— 即使没有它,仪表板也能同样正常工作(对于每个 15 分钟内某区域内的
第一个访客来说只是稍慢一些;重复访客会利用 CDN
缓存)。
### 预构建存储中没有 mock 回退
预构建的 `latest-dataset` blob **仅**由成功的
实时构建写入。Mock 数据永远不会取代真实的上游数据。仪表板
提供了一个包含 60 条记录的精选 mock 数据集,用于离线开发,并作为
当*所有*传输路径均失败时的最后手段的浏览器回退;它
永远不会被静默混入实时结果中,并且标题栏中的药丸标签会在其
被使用的那一刻变成琥珀色。
## 技术栈
| 层级 | 选择 |
| --- | --- |
| 框架 | [React 18](https://react.dev) |
| 构建工具 | [Vite 5](https://vitejs.dev) |
| 语言 | TypeScript (strict) |
| 样式 | [Tailwind CSS 3](https://tailwindcss.com) |
| 图表 | [Recharts 2](https://recharts.org) |
| 图标 | [Lucide React](https://lucide.dev) |
| 后端 | [Netlify Functions](https://docs.netlify.com/build/functions/overview/) (Node 20 ESM) |
| 存储 | [Netlify Blobs](https://docs.netlify.com/build/data-and-storage/netlify-blobs/) |
无需登录。无数据库。无支付。无漏洞利用代码。无攻击性
功能。Netlify Function 是只读且幂等的;Blobs
存储是一个托管的键/值存储,用作预构建数据包络的缓存。
## 项目结构
```
threatpulse-radar/
├── index.html
├── netlify.toml # Netlify build + functions + cron
├── package.json
├── tailwind.config.js
├── postcss.config.js
├── tsconfig.json
├── tsconfig.app.json
├── tsconfig.node.json
├── vite.config.ts
├── public/
│ ├── .htaccess # SPA fallback, cache, security headers
│ └── radar.svg
├── netlify/
│ └── functions/
│ ├── dataset.mjs # read endpoint (blob-first, else bootstrap)
│ ├── refresh-dataset-background.mjs # manual refresh (BG fn, 15-min timeout)
│ ├── refresh-dataset-scheduled.mjs # cron refresh (every 30 min)
│ └── _shared/
│ ├── store.mjs # Netlify Blobs + lock + cooldown helpers
│ ├── refresh.mjs # lock + write orchestrator + quality guard
│ └── liveBuild.mjs # shared CISA → NVD → EPSS pipeline
└── src/
├── main.tsx # entry
├── App.tsx # thin shell
├── index.css # Tailwind + global polish
├── vite-env.d.ts # import.meta.env typing
├── components/ # presentational + container pieces
│ ├── Header.tsx # + source / provider / cache pills
│ ├── StatsCards.tsx
│ ├── FiltersPanel.tsx
│ ├── VulnerabilityTable.tsx
│ ├── DetailDrawer.tsx
│ ├── EmptyState.tsx
│ ├── LoadingState.tsx
│ ├── ErrorState.tsx
│ ├── SearchStatus.tsx
│ ├── CachedDataBanner.tsx # v4 cache banner + refresh button
│ ├── UpdateAvailableBanner.tsx # v5.1 "New dataset available" banner
│ ├── RefreshInProgressBanner.tsx # v5.2 background-refresh indicator
│ ├── NvdUnavailableBanner.tsx
│ ├── EpssUnavailableBanner.tsx
│ ├── FallbackBanner.tsx
│ └── charts/
│ ├── SeverityChart.tsx
│ ├── TrendChart.tsx
│ └── KevChart.tsx
├── data/
│ └── mockVulnerabilities.ts # 60 fictional CVEs (offline fallback)
├── pages/
│ └── DashboardPage.tsx # single-screen dashboard
├── services/
│ ├── vulnerabilityService.ts # proxy-first orchestration
│ ├── datasetCache.ts # 1-hour localStorage cache layer
│ └── providers/
│ ├── cisaKev.ts # CISA KEV fetch + normalize
│ ├── epss.ts # FIRST EPSS batched fetch + enrich
│ └── nvd.ts # NVD CVE 2.0 batched fetch + enrich
├── types/
│ └── vulnerability.ts # shared domain types
├── hooks/
│ ├── useDebouncedValue.ts
│ └── useVulnerabilityFilter.ts
└── utils/
├── analytics.ts # filter, sort, aggregate
├── format.ts # date / score formatters
└── severity.ts # color + ordering helpers
```
## 在本地运行
前置条件:**Node.js 18+**(已在 Node 24 上测试)。对于 Netlify
Function 代理:**Netlify CLI** (`npm i -g netlify-cli`)。
```
# 1. 安装依赖
npm install
# 2a. 普通 Vite 开发服务器(无 Netlify Function)。
# 仪表盘可以正常工作,但 proxy endpoint 将返回 404。
# 客户端将透明地回退到浏览器直接
# 获取 (proxyStatus === 'browser-direct')。
npm run dev
# -> http://localhost:5173
# 2b. Netlify dev(Vite + Netlify Functions 一起运行)。
# 使用此命令端到端测试 v5.2 prebuilt-store 路径。
npx netlify dev
# -> http://localhost:8888
# 3. Production build (type-check + bundle)
npm run build
# 4. 预览 production build
npm run preview
```
完整的部署指南(Netlify 配置、环境变量、计划
函数、自定义域名、故障排除)位于
[`DEPLOYMENT.md`](./DEPLOYMENT.md) 中。
## 作品集备注
**由 [Naman Parikh](https://github.com/namanparikh11) 构建。**
这是一个**防御性**的漏洞情报项目,既是
一个真正的分类处理工具,也是用于
安全 / 前端工程面试的公开作品集展示。其中的工程决策
才是真正的展示品 —— 而不是功能列表。
在评审中我会重点指出这些:
- **各供应商的状态旁路通道。** CISA、NVD 和 FIRST 是
三个具有三种独立故障模式的独立服务。
`FetchResult` 结构具有独立的 `nvdStatus` 和 `epssStatus`
字段,因此局部故障会诚实地降级,而不是错误地表示
数据。
- **预构建 blob + 质量防护。** 一个共享的 `latest-dataset`
Netlify Blobs 条目将上游 pipeline 与每次请求的延迟
解耦。Orchestrator 拒绝用速率限制下的降级来覆盖更好的数据包络
— 这是一个大多数教程都会跳过的
现实世界中的可靠性 bug
(NVD HTTP 429 静默恶化缓存的数据)。
- **前端 bundle 中没有 API key。** `NVD_API_KEY` 仅从
Netlify Function 内部的 `process.env` 读取,作为请求
头传递,从不暴露。没有它,应用程序也能同样工作。
- **透明的时效性。** “Last refresh”反映了
函数实际运行的时间,而不是 CDN 提供响应的时间。
“Refresh live data”按钮使用 `?t=` 缓存清除机制,因此
其名称保持诚实。
- **软刷新时的明确用户同意。** v5.1 轮询
在“应用更新”横幅后呈现更新的上游数据;过滤器
和打开的详情视图会被保留。用户永远不会在任务中途被打扰。
该代码库刻意保持精简,全程使用严格模式的 TypeScript,
并在仅限防御性用途的许可证下发布。有关更长的
叙述,请参阅
[`PORTFOLIO_WRITEUP.md`](./PORTFOLIO_WRITEUP.md)。
## 许可证与使用
本项目仅用于**防御性安全工作**。请勿使用此处
展示的任何信息来开发、分发或执行攻击性
工具。`src/data/mockVulnerabilities.ts` 中的 mock CVE 是
虚构的,仅提供用于可视化目的;在采取行动之前,请务必
参考上游安全公告。
NVD、CISA KEV 和 FIRST EPSS 数据源是由
其各自组织运营的公共服务。ThreatPulse Radar 不附属于
NIST、CISA 或 FIRST。
_由您友好的社区防御性安全仪表板倾心打造。_
标签:React, Syscalls, TypeScript, Vite, XSS, 前端, 安全仪表盘, 安全插件, 漏洞情报, 自定义脚本