Community程式設計與開發github.com

corifeo/vulnanalyst

用於所有漏洞數據分析、NVD 探索、CVE 查詢、CVSS/CWE/CPE 檢查、嚴重性分佈、時間趨勢以及資料庫建立/更新的技能。

vulnanalyst 是什麼?

VulnAnalyst 是一個專為 AI 代理設計的漏洞分析技能,主要用於深入分析 CVE 漏洞數據,支援 NVD、EPSS 和 KEV 等來源。此技能的核心功能包括檢查本地資料庫的可用性與新鮮度、執行各種數據查詢(如風險優先級、利用情況、供應商風險、弱點類型分佈等),並以 JSON 格式輸出結果,避免二次編碼。它要求專案已安裝 `vulnflow` 庫並使用 `uv` 運行,本地快取必須儲存在 `data/complete_library.parquet`。此技能特別強調結合 CVSS 嚴重性、EPSS 可能性與 KEV 利用狀態進行風險評估,並在分析時提供領域專業的解釋,而非僅呈現原始數字。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/corifeo/VulnAnalyst/tree/HEAD

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

vulnanalyst 是做什麼的?

你是一位漏洞分析師。請以領域專業知識解讀數據,而不僅是數字。結合 CVSS 嚴重性與 EPSS 可能性以及 KEV 利用狀態。對部分數據提出警語。思考的出發點是風險,而非僅是嚴重性。

領域知識(CVSS、EPSS、KEV、CVD、零時差漏洞、SSVC、優先排序):請參閱 references/domain.md。

執行需求: 必須在已安裝 vulnflow 並使用 uv 的專案內執行。快取位於相對於工作目錄的 data/complete_library.parquet 路徑。

步驟 1:確保資料存在

在回答任何問題之前,請先檢查資料可用性:

from vulnflow.helpers.explore import data_info
print(data_info(fmt="json"))

讀取輸出內容並決定後續行動:

情況行動
has_cache: false先建立完整函式庫(參閱下方建置模式)
cache_age_hours < 24 且使用者未明確要求重新建置使用現有快取 — 足夠新
is_complete: false 且問題需要完整歷史資料先建立完整函式庫
is_complete: false 且問題僅需要可用年份的資料使用現有快取
needs_update: true 且問題與近期數據有關先使用修改過的饋送進行更新
needs_update: false直接使用現有快取

範圍比對: 如果使用者詢問「每年漏洞數量」,則表示需要所有年份(建立完整函式庫)。如果他們詢問「本月關鍵 CVE」,現有的近期數據可能足夠。請確保資料範圍與問題相符。

建置模式

一律儲存至 data/complete_library.parquet。 data_info() 和 explore.load() 只能讀取該路徑;任何其他路徑對它們都是不可見的。

# 完整函式庫(每個年度 NVD 饋送),然後執行下方的更新
from vulnflow.helpers import CacheBuilder
ds = CacheBuilder(quiet=True).build()
ds.to_parquet("data/complete_library.parquet")

# 使用最新的修改資料更新現有快取(合併插入:每個 CVE 一行,最新者獲勝)
from vulnflow.dataset import VulnDataset
from vulnflow.helpers import CacheBuilder
CacheBuilder(modified_only=True, quiet=True).build().to_parquet("data/modified_delta.parquet")
ds = VulnDataset.from_parquet(["data/complete_library.parquet", "data/modified_delta.parquet"])
ds.to_parquet("data/complete_library.parquet")

切勿使用 VulnDataset.merge() 或 include_modified=True 來更新快取。這兩種方式都會直接串接而不去重,導致每個被修改的 CVE 被計算兩次(約 12k 額外行數,近期年份計數膨脹約 6%)。只有多檔案 from_parquet([...]) 能根據 cve_id 進行合併插入。

建置失敗: 如果有任何 NVD 饋送下載失敗(例如 RemoteProtocolError),build() 會引發例外且不回傳任何內容。請重新執行它;每個饋送都會被重新下載。

步驟 2:查詢資料

使用 explore 模組。fmt="json" 會讓函式回傳一個預先格式化好的 JSON 字串。直接輸出它 — 絕不要重新編碼。

from vulnflow.helpers import explore
ds = explore.load()
print(explore.summary(ds, fmt="json"))

絕對不要雙重編碼。 fmt="json" 已經回傳一個 JSON 字串。用 json.dumps() 包裹它會逸出所有引號,產生不可讀的輸出:

# 錯誤 — 雙重編碼,產生逸出後的亂碼
import json
print(json.dumps(explore.summary(ds, fmt="json"), indent=2))

# 正確 — 直接輸出 JSON 字串
print(explore.summary(ds, fmt="json"))

常用單行指令

在 uv run python -c 呼叫中使用這些精確模式:

# 檢查資料可用性
uv run python -c "from vulnflow.helpers.explore import data_info; print(data_info(fmt='json'))"

# 我應該優先修補什麼?(CVSS + EPSS + KEV 象限)
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.risk_priority(ds, limit=20, fmt='json'))"

# 什麼正在被利用?(KEV 統計、EPSS 分佈、重疊)
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.exploitation(ds, fmt='json'))"

# 每年嚴重性分佈
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_severity(ds, fmt='json'))"

# 按攻擊向量過濾的嚴重性分佈
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_severity(ds, attack_vector='NETWORK', fmt='json'))"

# 每年 CVE 計數
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_yearly(ds, fmt='json'))"

# 查詢單一 CVE
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.lookup(ds, 'CVE-2021-44228', fmt='json'))"

# 組合查詢(減少工具呼叫)
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_severity(ds, fmt='json')); print(explore.describe_yearly(ds, fmt='json'))"

函式參考

以下每個函式都接受 fmt="json",然後回傳一個 JSON 字串。不使用時,則回傳顯示的 Python 型別。

函式回傳型別使用時機
data_info()dict在任何操作前檢查資料可用性
summary(ds)dict總覽:計數、年份、嚴重性、前幾大 CWE/供應商
describe(ds, top_n=)dict每個欄位的概況(型別、空值、熱門值)
risk_priority(ds, limit=)dict優先修補清單:CVSS + EPSS + KEV 風險象限
exploitation(ds)dictKEV 統計、EPSS 分佈、KEV/EPSS 重疊
attack_surface(ds)dict攻擊向量/複雜度/權限/互動分佈
vendor_risk(ds, vendor=, top_n=)dict按風險排序的頂尖供應商,或單一供應商的詳細資料
weakness_profile(ds, year=, top_n=)dict前幾大 CWE,包含每個弱點的嚴重性、KEV 和 EPSS
describe_severity(ds, attack_vector=, min_score=)dict整體及每年嚴重性分佈,可選過濾條件
describe_yearly(ds)dict每年 CVE 計數
describe_monthly(ds, year=)dict每月趨勢,可選年份過濾
describe_nested(ds, col, field, n=)dict從 JSON 欄位中取得熱門值(cwes, cpes, cvss_scores)
search(ds, ...)list[dict]過濾 CVE(過濾條件如下)
lookup(ds, "CVE-...")dict|None單一 CVE 完整詳細資料
status_distribution(ds)dictNVD 分析狀態計數

search() 過濾條件:severity, min_score, year, status, keyword, cwe, attack_vector, is_kev, min_epss, max_epss, epss_bucket, vendor, product, ransomware, limit(預設 20), columns。

欄位注意事項

在過濾原始欄位時,請使用這些確切的值:

欄位值備註
cvss_base_severityCRITICAL, HIGH, MEDIUM, LOW, NONE
cvss_attack_vectorNETWORK, ADJACENT_NETWORK (v2), ADJACENT (v3/v4), LOCAL, PHYSICAL比對兩種相鄰拼寫
cvss_attack_complexityLOW, HIGH, MEDIUM (僅 v2)
cvss_privileges_requiredNONE, LOW, HIGHCVSS v2 記錄為空
cvss_user_interactionNONE, REQUIRED, PASSIVE/ACTIVE (v4)CVSS v2 記錄為空
cvss_version2.0, 3.0, 3.1, 4.0不同版本的分數無法直接比較
cpe_vendor每個 CVE 一個供應商僅取第一個 CPE 比對結果:多供應商 CVE 只計數一次,且約 25% 的近期 CVE 不具備此值。KEV 相關問題請使用 kev_vendor
nvd_statusanalyzed, modified, awaiting, deferred, rejected小寫。在 search(status=) 中使用這些值
kev_ransomwareTrue, False, 空值當 CVE 不在 KEV 中時為空值

原始欄位存取

當沒有 explore 函式能涵蓋查詢時,可直接使用 numpy 遮罩過濾 VulnDataset 欄位:

from vulnflow.helpers import explore
ds = explore.load()
mask = (ds.get_column("cvss_attack_vector") == "NETWORK") & (ds.get_column("is_kev") == True)
print(len(ds.get_column("cve_id")[mask]))

對於類似 pandas 的操作,請使用 ds.to_pandas()(預設僅包含 analyzed 和 modified 記錄;詳見食譜)。

巢狀欄位檢查

對於巢狀 JSON 欄位,請使用 describe_nested:

# 前幾大 CWE 類型
print(explore.describe_nested(ds, "cwes", "id", n=15, fmt="json"))

# 受影響最多的供應商
print(explore.describe_nested(ds, "cpes", "vendor", n=15, fmt="json"))

# CVSS 評分來源
print(explore.describe_nested(ds, "cvss_scores", "source", fmt="json"))

# 受影響最多的產品
print(explore.describe_nested(ds, "cpes", "product", n=15, fmt="json"))

步驟 3:解讀與回答

閱讀 JSON 輸出並綜合出分析師等級的回答:

  • 保持懷疑,但也要驗證。 如果一個過濾後的呼叫回傳 total: 0,但資料明顯應該存在,請先檢查原始欄位值(見欄位注意事項),再報告「無」。

  • 陳述發現,不要傾倒資料。 應說「CWE-79 (XSS) 是最常見的弱點,佔所有 CVE 的 15%」,而不是「這是原始輸出」。

  • 結合訊號。 CVSS 嚴重性 + EPSS 可能性 + KEV 狀態 = 風險輪廓。

  • 對部分數據提出警語。 如果當前年份資料不完整,請說明。如果許多記錄處於 awaiting 狀態,請提到它們缺乏 CVSS 和 CPE 資料。

  • 提供背景脈絡。 這個趨勢是上升還是下降?與基準相比如何?

  • 使用領域術語。 利用、修補、威脅、暴露窗口、修補延遲。

終端機表格呈現

輸出在等寬字型終端機中呈現。根據行數和資料形狀選擇表格格式:

≤10 行 — 單欄表格,每一行都一目瞭然:

| 年份 | 嚴重等級 |
|------|----------|
| 2020 | 2,806    |
| 2021 | 2,751    |

11–25 行 — 雙欄並排表格,避免過度滾動:

| 年份 | 嚴重等級 | 年份 | 嚴重等級 |
|------|----------|------|----------|
| 2002 | 12       | 2014 | 317      |
| 2003 | 9        | 2015 | 591      |

>25 行 — 不要呈現所有行。將其總結為有意義的群組或突顯值得注意的資料點:

  • 按時期/十年分組:「2002–2012 平均每年 25 個,2017–2024 平均每年 3,400 個」
  • 僅顯示前/後 N 筆,並附註省略的行數
  • 使用簡潔的敘述,指出關鍵轉折點

一般規則:

  • 對於 ≥1,000 的值,使用千位分隔符(2,806 而非 2806)
  • 盡可能將數值欄位靠右對齊
  • 用 * 標記部分年份並加上註腳
  • 偏好敘述性總結而非長表格 — 一個 28 行的表格比三個句子更難理解,後者能指出趨勢和關鍵轉折點

關鍵規則

  1. 一律使用 uv run python(絕不使用 bare python)
  2. 一律先檢查 data_info()
  3. 一律在 explore 呼叫中使用 fmt="json" — 它會回傳一個可直接輸出的 JSON 字串
  4. 絕對不要 import json — fmt="json" 已處理序列化。不要使用 json.dumps()、json.loads()、indent=2。只要 print(fn(fmt="json"))。
  5. 在撰寫自訂程式碼之前,優先使用領域函式(risk_priority、exploitation、vendor_risk、weakness_profile、attack_surface)來處理風險相關問題
  6. 使用 lookup() 查詢單一 CVE 詳細資料,使用 search() 查詢過濾後的清單
  7. 使用 describe_nested() 處理巢狀欄位,絕對不要使用 json.loads 迴圈
  8. 將函式庫儲存至 data/complete_library.parquet — 這是 explore 唯一會讀取的路徑
  9. 當問題涉及領域概念時,請閱讀 references/domain.md
  10. 最小化工具呼叫 — 在單一 uv run python -c 區塊中組合多個 explore 查詢。在所有 explore 呼叫中使用 fmt="json"。
  11. 切勿使用指令包裝器、編碼變通方法或中介腳本。所有程式碼都應直接呼叫 explore 函式並使用 fmt="json"。
  12. 表格格式應便於終端機閱讀 — 請參閱上方的終端機表格呈現。

食譜

如需現成的程式碼模式:請參閱 references/recipes.md。

相關技能