OpenSSF malicious-packages를 독립 RAW 자산으로 수집하는 파이프라인 — 새 팀원 온보딩 상세
ossf/malicious-packages — 악성 패키지 advisory를 OSV JSON(MAL-YYYY-NNNN)으로 게시하는 공개 저장소.git pull diff + DEPRECATED 소프트삭제 + 활성 뷰.악성 패키지 정보는 스캐너/배포로 흘러가는 1차 보안 데이터다. 그런데 악성 패키지는 레지스트리에서 빠르게 takedown되고, advisory도 사후 정정된다. 그래서 (1) 원본을 무손실로 먼저 확보하고(독립 수집), (2) 정정·삭제는 물리 삭제가 아니라 상태 전이로 다뤄 배포 동기화가 깨지지 않게 하는 것이 핵심이다.
① affected vs database_specific. affected는 정규화된 결론(매핑에 사용), database_specific.malicious-packages-origins는 출처·감사 추적이다. 여러 피드가 같은 패키지를 신고해도 affected는 1개로 머지되고 근거는 origins에 N개로 남는다.
② ranges vs versions. 범위 표현과 명시적 버전 목록이 둘 다 쓰인다.
| 형태 | 건수 | 비율 |
|---|---|---|
ranges만 (대개 introduced:0=전 버전) | 203,356 | 89.6% |
versions만 (특정 버전) | 16,704 | 7.4% |
| 둘 다 | 7,000 | 3.1% |
③ 상한 있는 케이스는 희소하지만 중요. fixed 33건, last_affected 128건. "정상 패키지가 특정 버전만 탈취됐다 수정된" 고가치 케이스(예: pyphetools [0.9.120,0.9.121))다.
versions는 불연속이 많다(예: easyascii 2.4.1, 2.4.5, 3.2.1…). 절대 min~max 단일 구간으로 합치지 말 것 — 정상 버전을 악성으로 오탐하게 된다.git clone / 이후 git pull. repo 자체가 raw 저장소(osv/malicious/<eco>/<pkg>/MAL-*.json).data/manifest.json에 head_commit·생태계별 카운트 기록.PRODUCT_KEY 산출(npm=사내 해시, 그 외 평문) → 적재.현재 PoC: ~/labrador/tool/openssf-malicious/collect.py (raw 확보까지, DB 미연동).
RANGE 변환 규칙 — VULN_RANGE는 | 구분 토큰(구간/단일버전)의 합집합.
| 입력 | 변환 |
|---|---|
introduced:0 | (,) |
introduced:X | [X,) |
introduced:A + fixed:B | [A,B) |
introduced:A + last_affected:B | [A,B] |
versions:[…] (불연속) | v1|v2|v3 (합치기 금지) |
| range 여러 개 (한 product, 747건) | union 단순화 ((,) 있으면 (,)) |
ecosystem 정규화 — OSV→내부 대문자 코드: npm→NPM, PyPI→PYPI, Packagist→COMPOSER, crates.io→CARGO, Go→GOLANG, Maven→MAVEN 등. 케이싱 불일치(pypi/PYPI, Cargo/CARGO)는 조인 시 UPPER()로 흡수. vscode/git은 매핑 없음 → 스킵+로그.
advisory는 사후 수정된다. 예: MAL-xxxx-xx가 처음엔 패키지 A를 악성으로 지목했다가, 재분석 후 패키지 B로 정정될 수 있다.
git diff로 직전 head_commit 대비 변경된 MAL 파일만 추출.VULN_ID 기존 행과 비교: 새 행 → INSERT(DEPRECATED=0) / 사라진 행(A) → DEPRECATED=1.VULN_ID 행 전부 DEPRECATED=1.head_commit 저장 → 멱등(같은 commit 재처리 시 무변화).DEPRECATED=1로 상태만 전이한다.기준점(prev) 복구. prev는 최초 NULL이거나 upstream history rewrite/shallow로 사라질 수 있다. 유효성은 "존재"가 아니라 HEAD의 조상인지(git merge-base --is-ancestor)로 판정한다. 무효 시 stats의 commit_time 이하(≤)의 가장 최근 commit을 base로 잡는다(반드시 내림 — 올려잡으면 데이터 유실). 둘 다 실패하면 full reconcile.
DEPRECATED=0)을 통째로 대조(repo만 있음→INSERT / DB만 있음→DEPRECATED=1). 22.7만 건이라 감당 가능, 주기 실행 시 드리프트 self-heal. 상태는 TB_CRAWLER_STATUS TYPE='OPENSSF_MAL'에 {commit_id, commit_time}(committer date, TZ ISO8601).DEPRECATED 컬럼 + 활성 뷰DEPRECATED TINYINT — 0 = 활성, 1 = 폐기. 소비자/배포는 원본 테이블이 아니라 활성 뷰만 조회한다.
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;
폐기 이력은 원본에 남아 감사·복원이 가능하고, 다운스트림은 "현재 유효한 악성 매핑"만 단순 조회한다.
PRODUCT_KEY 해시 위치base64(AES-CBC(pkg_name)), key=IV=b'NPMNPMNPMNPMNPMN', ai/labradorlabs/util/aesCbc.py (deterministic).DEPRECATED 상태 전이를 어떻게 소비할지 연동 방식.근거: ~/labrador/tool/openssf-malicious · 설계 결정 ai/decisions/2026-06-15-malicious-package-openssf-collection.md · 정본 human/reports/2026-malicious-package-collection.md