LLM WikiAccess-protected knowledge portal

WIKI

OpenSSF Malicious Package (MAL) 수집·매핑 방안

AI Summary Purpose Decide how to collect OpenSSF malicious packages OSV MAL YYYY NNNN advisories into gatheringdb and map them, with version ranges, onto the existing per ecosystem vuln mapping tables TB COMP LIB VULN <eco V1 . Key points S

경로ai/decisions/2026-06-15-malicious-package-openssf-collection.md
카테고리Decision
태그#ai-review #collection #crawler #decision #malicious #openssf #package #security

# OpenSSF Malicious Package (MAL) 수집·매핑 방안

AI Summary

Purpose:

into gatheringdb and map them, with version ranges, onto the existing per-ecosystem vuln-mapping tables (TB_COMP_LIB_VULN_<eco>_V1).

Key points:

incremental raw collection is natural.

the existing (PRODUCT_KEY, VULN_RANGE, VULN_ID) model.

others (Maven groupId:artifactId, PyPI/RubyGems/crates names) are plain.

MAL tables managed separately from CVE. User prefers B (separate from CVE).

Relevant when:

Do not read full document unless:

Linked documents:

Open Questions

key=IV=b'NPMNPMNPMNPMNPMN', ai/labradorlabs/util/aesCbc.py(deterministic).

Details

작성일 2026-06-15. 본문(방안 비교)은 검토·결정용이라 한국어로 작성. 표/예시는 실제 gatheringdb에 적재된 MAL 레코드와 ossf/malicious-packages 원본에서 발췌한 것이며 임의로 만들지 않았다.

1. 배경과 목표

버전 RANGE까지 포함한 형태로 라이브러리 생태계별 취약 매핑 테이블 구조 (TB_COMP_LIB_VULN_<eco>_V1)에 맞춰 적재한다.

- 증분 raw 수집 가능 - 버전 RANGE 매핑 (단일 버전이 아니라 affected 구간) - CVE와는 별도로 관리 (사용자 결정)

osv/malicious/<ecosystem>/<package>/MAL-YYYY-NNNN.json 경로에 OSV JSON으로 존재. git 기반이라 pull diff 또는 OSV modified 타임스탬프로 증분이 자연스럽다.

2. OpenSSF/OSV MAL 데이터 메타 예시 (실데이터)

대표 레코드 (현재 DB 적재본 기준):

VULN_IDecosystempackagepurlsummary
MAL-2021-1npmcxp-jquerypkg:npm/cxp-jqueryMalicious code in cxp-jquery (npm)
MAL-2022-1crates.iorustdecimalpkg:cargo/rustdecimalMalicious code in rustdecimal (crates.io) — rust_decimal 타이포스쿼팅
MAL-2026-5772npmnpx-whoami-demopkg:npm/npx-whoami-demoMalicious code in npx-whoami-demo (npm)
MAL-2026-5773PyPIgeneratellmpkg:pypi/generatellmMalicious code in generatellm (PyPI)
MAL-2026-5768PyPIbash8pkg:pypi/bash8Malicious code in bash8 (PyPI)

원본 OSV JSON 한 건 전체 (MAL-2022-1, rustdecimal):

{
  "id": "MAL-2022-1",
  "summary": "Malicious code in rustdecimal (crates.io)",
  "aliases": ["GHSA-7pwq-f4pq-78gm", "RUSTSEC-2022-0042"],
  "details": "... 타이포스쿼팅 분석 (rust_decimal 사칭, crates.io에서 영구 삭제) ...",
  "affected": [
    {
      "package": {
        "name": "rustdecimal",
        "purl": "pkg:cargo/rustdecimal",
        "ecosystem": "crates.io"
      },
      "ranges": [
        { "type": "SEMVER", "events": [ { "introduced": "0" } ] }
      ],
      "database_specific": {
        "source": "https://github.com/ossf/malicious-packages/blob/main/osv/malicious/crates.io/rustdecimal/MAL-2022-1.json"
      }
    }
  ],
  "references": [
    { "type": "ADVISORY", "url": "https://github.com/advisories/GHSA-7pwq-f4pq-78gm" },
    { "type": "WEB", "url": "https://rustsec.org/advisories/RUSTSEC-2022-0042.html" }
  ],
  "published": "2022-08-11T15:43:35Z",
  "modified": "2023-11-08T04:16:56Z",
  "schema_version": "1.7.3",
  "database_specific": {
    "malicious-packages-origins": [
      {
        "id": "GHSA-7pwq-f4pq-78gm",
        "source": "ghsa-malware",
        "sha256": "2e33f42f05c60c6d9f9297bae15a43d6c445e2ad0fd67fa4ef144e5cc79d09c7",
        "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" } ] } ],
        "import_time": "2023-07-30T21:57:59Z",
        "modified_time": "2023-01-07T05:08:16Z"
      }
    ]
  }
}

수집·매핑에 쓰이는 핵심 필드:

OSV 필드의미매핑 대상
idMAL-YYYY-NNNNVULN_ID
affected[].package.ecosystem생태계테이블/컬럼 선택
affected[].package.name패키지명NAMEPRODUCT_KEY
affected[].package.purl표준 식별자(보조 매핑 키)
affected[].ranges[].eventsaffected 구간VULN_RANGE 변환 입력
aliasesGHSA/RUSTSEC 등참조용 (별도 저장 선택)
database_specific.source원본 advisory URLSOURCE_URL
modified / published시각증분/감사

중요한 관찰: 악성 패키지는 보통 패키지 전체가 악성이라 거의 모든 MAL 레코드의 range가 events:[{introduced:"0"}](= 전 버전)다. 즉 변환 결과의 대다수는 VULN_RANGE = (,)가 된다.

3. 타깃 스키마 현황 (기존 vuln 테이블)

TB_COMP_LIB_VULN_<eco>_V1 (정합본):

컬럼타입비고
PRODUCT_KEYvarchar(330) PKMaven=groupId:artifactId, npm=내부 해시키
VULN_IDvarchar(20) PKMAL-... 들어감 (길이 OK)
VULN_RANGEtext구간 표기 [a,b) / (,)
MAPPED_TYPEenum('RE','GA','GL','SN','SC','DE')소스 코드. OpenSSF용 값 없음 → 추가 필요
RECORD_CREATED/UPDATEDtimestamp

TB_COMP_LIB_VULN_<eco>_RAW (소스별 원본): 위 + LANGUAGE, REPOSITORY, NAME, SOURCE_URL, VULN_RANGE_HASH. 즉 기존 파이프라인은 RAW(소스별·NAME 보유) → 이름→PRODUCT_KEY 해석 → V1(product+vuln 정합) 흐름이다.

VULN_RANGE 실제 예: (,)(전 버전), (,0.15.0), [1.0.0,2.0.1), [4.0-M3,].

4. PRODUCT_KEY 처리 규칙

생태계PRODUCT_KEY 생성비고
npm내부 해시(암호화) 키 적용우리가 만든 해시 규칙으로 평문 패키지명 → 해시키 변환
MavengroupId:artifactId (평문)OSV name과 직접 매칭
PyPI / RubyGems / crates.io / NuGet / Packagist / Go평문 패키지명(생태계 정규화 규칙)

npm만 해시 변환 단계가 추가로 필요하고 나머지는 평문 키를 그대로 쓴다. takedown되어 product 인벤토리에 없는 패키지는 RAW까지만 남고 V1 승격은 보류한다 (기존 RAW→V1 철학 유지).

5. VULN_RANGE 변환 규칙 (OSV events → 구간 표기)

OSV events 배열을 정렬해 구간으로 환원한다.

VULN_RANGE|(파이프) 구분 토큰의 합집합 — 각 토큰은 구간([a,b)) 또는 단일 버전.

OSV 입력VULN_RANGE의미
[{introduced:"0"}](,)전 버전 악성 (MAL 대다수)
[{introduced:"0"},{fixed:"X"}](,X)X 미만
[{introduced:"A"},{fixed:"B"}][A,B)A 이상 B 미만 (33건)
[{introduced:"A"},{last_affected:"B"}][A,B]A 이상 B 이하 (128건)
versions:[v1,v2,...] (불연속)`v1\v2\v3`min~max 합치기 금지, `\`로 나열
range 여러 개 (한 product)union 단순화747건. (,) 있으면 (,), 없으면 `[a,b)\[c,d)`

5.5 ecosystem 정규화 (OSV → 내부 코드)

OSV ecosystem 명칭은 우리 내부 명칭과 다르고(예: PackagistCOMPOSER, crates.ioCARGO), 내부 명칭 자체도 테이블마다 케이싱이 불일치한다(PyPI: product PYPI/vuln pypi, Rust: 테이블명 CARGO/product REPOSITORY Cargo). MAL REPOSITORY대문자 정규 코드(vuln 테이블 suffix)로 통일하고, 조인 시 UPPER()로 케이싱을 흡수한다.

OSV ecosystem내부 REPOSITORYLANGUAGE조인 시 실제 REPOSITORY
npmNPMjavascriptNPM
PyPIPYPIpythonproduct PYPI / vuln pypi ⚠️
RubyGemsRUBYGEMSrubyRUBYGEMS
NuGetNUGETdotnetNUGET
GoGOLANGgoGolang ⚠️
crates.ioCARGOrustproduct Cargo ⚠️
MavenMAVENjavaMAVEN
PackagistCOMPOSERphpCOMPOSER
vscode, vscode:open-vsx.org— (매핑 없음)VS Code 확장, 미수집
git— (매핑 없음)GIT range, 패키지 아님

규칙: ① 대문자 정규 코드 + LANGUAGE 동반 저장, ② 조인은 UPPER(REPOSITORY) 비교, ③ vscode/vscode:open-vsx.org/git은 매핑 대상 없음 → 적재 스킵 + 로그(조용히 버리지 않음, MAL 합계 21건).

6. 증분 raw 수집 전략

  1. ossf/malicious-packages git clone, 이후 git pull로 증분.
  2. 변경 파일 추출: git diff --name-only <last_sha>..HEAD -- osv/malicious/ 또는

전체 워크 후 OSV modified 비교.

  1. MAL-*.json을 raw로 보존 (파일 경로 + sha256 + modified 저장 → 재처리 판단).
  2. raw → 파싱 → 생태계 분기 → RANGE 변환 → (npm 해시) → 매핑 테이블 적재.
  3. 마지막 처리 commit sha를 상태로 저장 (TB_CRAWLER_STATUS TYPE='OPENSSF_MAL'

{commit_id, commit_time}; commit_time은 committer date, TZ 포함 ISO8601).

기준점(prev) 복구. prev는 최초 NULL이거나, upstream history rewrite/force-push/ shallow clone으로 사라질 수 있다. 유효성은 "존재"가 아니라 HEAD의 조상인지로 판정: git cat-file -e prev^{commit} && git merge-base --is-ancestor prev HEAD.

prev == NULL                  → 전체 스캔 INSERT
prev 가 HEAD의 조상           → git diff prev..HEAD              (빠른 경로)
prev 무효 & commit_time 있음  → base = (≤commit_time 최신 commit); diff base..HEAD
그 외                         → full reconcile (안전망)

올려잡으면 데이터 유실.

→INSERT / DB만 있음→DEPRECATED=1). 22.7만 건이라 감당 가능하고 무조건 정확. 폴백이자 주기 실행 시 드리프트 self-heal. "활성이어야 하는 것"의 진짜 기준은 diff가 아니라 HEAD 트리.


방안 A — 기존 CVE vuln 테이블에 합치기

기존 TB_COMP_LIB_VULN_<eco>_RAW/_V1에 MAL을 그대로 적재.

장점

단점


방안 B — MAL 전용 테이블 (CVE와 분리) ✅ 선호

CVE 매핑과 물리적으로 분리한 전용 테이블. 생태계는 별도 컬럼(REPOSITORY)으로 둬서, 사용자가 처음 원했던 "ecosystem 컬럼(라이브러리=NPM/MAVEN…)" 형태를 그대로 만족. MAL 총량이 크지 않아(현재 ~22.7만, 94% npm) 단일 테이블 세트로 충분.

확정: RAW 원본은 git working copy로 보존(repo 자체가 raw 저장소)하므로 DB에 별도 _RAW/RAW_DATA를 두지 않는다. _RAW/V1 2단 분리 없이 단일 TB_COMP_LIB_MAL + 활성 뷰로 간다. 생태계는 REPOSITORY 컬럼(대문자 정규 코드, §5.5).

CREATE TABLE TB_COMP_LIB_MAL (
  VULN_ID         VARCHAR(20)  NOT NULL,   -- MAL-YYYY-NNNN
  REPOSITORY       VARCHAR(30)  NOT NULL,   -- NPM / PYPI / COMPOSER / CARGO ... (대문자 정규)
  NAME            VARCHAR(330) NOT NULL,   -- 평문 패키지명 (OSV affected.package.name)
  PRODUCT_KEY     VARCHAR(330) NOT NULL,   -- npm=사내 해시키, 그 외=평문
  VULN_RANGE      TEXT         NOT NULL,   -- '|' 구분 토큰(구간/단일버전)의 합집합 (§5)
  DEPRECATED      TINYINT      NOT NULL DEFAULT 0,  -- 0=활성, 1=폐기(소프트삭제)
  SOURCE_URL      VARCHAR(400) NULL,       -- database_specific.source
  RECORD_CREATED  TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP,
  RECORD_UPDATED  TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (VULN_ID, REPOSITORY, PRODUCT_KEY),
  KEY idx_active (DEPRECATED, REPOSITORY)
);

-- 소비자·배포는 활성(미폐기) 행만 노출하는 뷰만 조회
CREATE VIEW VIEW_COMP_LIB_MAL AS
  SELECT VULN_ID, REPOSITORY, NAME, PRODUCT_KEY, VULN_RANGE, SOURCE_URL,
         RECORD_CREATED, RECORD_UPDATED
  FROM TB_COMP_LIB_MAL WHERE DEPRECATED = 0;

(227,060건 실측): advisory당 affected는 항상 1패키지(다중 0건), product 중복 0건. 한 product에 range가 여러 개인 747건은 별도 행이 아니라 한 VULN_RANGE에 합쳐 담는다.

삭제하면 배포(distribution) sync 정합이 깨진다. 삭제 대신 DEPRECATED=1 상태 전이.

추가 테이블 TB_COMP_LIB_MAL_INFO (2026-06-16): advisory 단위 메타 (summary/details/MAL_REFERENCES=osv.references/ALIASES=osv.aliases)를 PK=VULN_ID 별도 테이블에 수집. REFERENCES는 SQL 예약어라 MAL_REFERENCES로 명명. MAL과 동일 라이프사이클(같은 run_ts upsert, 변경 시 양쪽 deprecate→재upsert, full sweep 양쪽) + VIEW_COMP_LIB_MAL_INFO. MAL 행이 생긴 advisory에 대해서만 생성. 백필은 full_reconcile=true.

장점

단점


비교 요약

항목A. CVE 테이블 합침B. MAL 전용 분리 (선호)
CVE와 분리✗ (접두어로만 구분)✅ 물리 분리
기존 스키마 변경enum ALTER 등 필요없음 (신규만)
다운스트림 영향큼 (오인 위험)작음 (선택적 소비)
신규 개발량적음중간 (테이블+파이프라인)
ecosystem 컬럼 모델✗ (생태계별 테이블)
사용자 요구 부합

추천: 방안 B. PRODUCT_KEY/VULN_RANGE 키 규칙은 기존 vuln과 동일하게 맞춰 두면, 나중에 통합 조회가 필요할 때 JOIN/UNION으로 언제든 합칠 수 있다(역은 어렵다).

다음 단계

  1. npm 내부 해시키 생성 로직 위치 확인 (crawler-lib-*).
  2. takedown 영향 정량화: Maven/PyPI MAL 샘플 ↔ product 인벤토리 매칭률 실측.
  3. 본 방안 B 기준으로 구현 스펙 작성 (수집기(git) → RANGE 변환기 → TB_COMP_LIB_MAL 적재 + 증분 DEPRECATED reconcile).