Community코딩 & 개발github.com

corifeo/vulnanalyst

모든 취약점 데이터 분석, NVD 탐색, CVE 조회, CVSS/CWE/CPE 검사, 심각도 분포, 시간별 추세, 데이터베이스 구축/업데이트에 사용합니다.

vulnanalyst란 무엇인가요?

이 스킬은 AI 에이전트(Claude Code, Cursor 등)가 취약점 데이터를 전문적으로 분석하고 해석할 수 있게 해주는 도구입니다. NVD(National Vulnerability Database)의 전체 피드를 로컬 Parquet 캐시로 구축하고, CVE 조회, CVSS 심각도 평가, EPSS 악용 확률, KEV(적극 악용 취약점) 현황, CWE/CPE 분석 등 포괄적인 취약점 인텔리전스를 제공합니다. `vulnflow` 라이브러리를 기반으로 하며, `uv` 런타임과 `data/complete_library.parquet` 경로의 캐시를 사용합니다. 사용자가 특정 CVE ID를 언급하거나 취약점 관련 질문을 할 때 자동으로 활성화되어 로컬 데이터를 기반으로 정확한 답변을 제공하며, 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에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

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")

# 최신 수정 데이터로 기존 캐시 업데이트 (upsert: 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% 부풀려짐). cve_id로 중복 제거하는 다중 파일 from_parquet([...])만 사용하십시오.

빌드 실패: 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모든 열 프로필 (타입, null, 상위 값)
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=)dictJSON 열(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_vendorCVE당 한 벤더첫 번째 CPE 일치만: 다중 벤더 CVE는 한 번만 계산되며, 최근 CVE의 약 25%는 없음. KEV 질문에는 kev_vendor 사용
nvd_statusanalyzed, modified, awaiting, deferred, rejected소문자. search(status=)에 사용
kev_ransomwareTrue, False, 비어 있음CVE가 KEV에 없으면 비어 있음

원시 열 접근

탐색 함수가 쿼리를 다루지 못할 때 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() (기본적으로 분석 및 수정된 레코드만; 레시피 참조).

중첩 열 검사

중첩된 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 행 — 과도한 스크롤을 피하기 위한 2열 나란히 테이블:

| 연도 | 치명적 | 연도 | 치명적 |
|------|----------|------|----------|
| 2002 | 12       | 2014 | 317      |
| 2003 | 9        | 2015 | 591      |

>25 행 — 모든 행을 렌더링하지 마십시오. 의미 있는 그룹으로 요약하거나 주목할 만한 데이터 포인트를 강조하십시오:

  • 시대/10년으로 그룹화: "2002–2012년 평균 25/년, 2017–2024년 평균 3,400/년"
  • 생략된 행에 대한 참고 사항과 함께 상위/하위 N개만 표시
  • 주요 변곡점을 언급하는 간결한 서술 사용

일반 규칙:

  • 값 ≥1,000에 대해 천 단위 구분 기호 사용 (2806이 아닌 2,806)
  • 가능할 때 숫자 열 오른쪽 정렬
  • 부분 연도는 *로 표시하고 각주 추가
  • 긴 테이블보다는 서술적 요약 선호 — 28행 테이블은 추세와 주요 변곡점을 식별하는 세 문장보다 읽기 어려움

중요 규칙

  1. 항상 uv run python 사용 (절대 빈 python 사용하지 않음)
  2. 항상 먼저 data_info() 확인
  3. 탐색 호출에 항상 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. 단일 CVE 세부 정보에는 lookup()을, 필터링된 목록에는 search()를 사용
  7. 중첩 필드에는 describe_nested()를 사용, 절대 json.loads 루프 사용하지 않음
  8. 라이브러리를 data/complete_library.parquet에 저장 — explore가 읽는 유일한 경로
  9. 질문에 도메인 개념이 포함되면 references/domain.md 읽기
  10. 도구 호출 최소화 — 단일 uv run python -c 블록에서 여러 탐색 쿼리 결합. 모든 탐색 호출에 fmt="json" 사용.
  11. cmd 래퍼, 인코딩 해결 방법 또는 중간 스크립트를 사용하지 마십시오. 모든 코드는 fmt="json"과 함께 탐색 함수를 직접 호출해야 합니다.
  12. 테이블을 터미널 가독성에 맞게 형식화 — 위의 터미널 테이블 렌더링 참조.

레시피

준비된 코드 패턴: references/recipes.md.

관련 스킬