namanparikh11/threatpulse-radar

GitHub: namanparikh11/threatpulse-radar

一款聚合 CISA KEV、NVD CVSS 与 FIRST EPSS 三大公开漏洞情报源的防御性安全仪表板,提供漏洞优先级排序与可视化态势感知。

Stars: 0 | Forks: 0

# ThreatPulse Radar ![status](https://img.shields.io/badge/status-v5.3-22d3ee?style=flat-square) ![stack](https://img.shields.io/badge/stack-React%20%2B%20Vite%20%2B%20TS-0d1424?style=flat-square) ![use](https://img.shields.io/badge/use-defensive%20only-f43f5e?style=flat-square) **在线演示:** [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, 前端, 安全仪表盘, 安全插件, 漏洞情报, 自定义脚本