# labrador-scrapers
AI Summary
Purpose:
- 데이터 수집 ETL 컴포넌트(스크래퍼) 모노레포.
etl_components/<name>/마다 컨테이너 1개.
Key points:
- 신규 컴포넌트는
etl_component_template/template/또는 기존etl_components/osv_vuln을 본떠 만든다. - 프레임워크:
labrador_sqlmodel(BaseScraper/TransactionManager) + 모델은BaseRecord. spm_scraper(Swift Package Index/SPI)는 GitHub 전용(universe=PackageList 100% GitHub, validate.swift가 코드로 강제) + 버전=GitHub 태그 HTML 스크랩. 누락도구~/labrador/tool/spm_missing_products. 아래 컴포넌트 섹션 참조.- 라이브러리 크롤러도 이 모노레포에 다수 상주:
npm_crawler/pypi_crawler(레거시ai/labradorlabs/포팅, work-list LIST 테이블 방식 =crawler-lib-*계보) +conan_crawler/vcpkg_crawler/lib_hunter_crawler(labrador_sqlmodelBaseScraper, RAW→VERSION→PRODUCT 3단 SQLModel 파이프라인). 아래 "컴포넌트: 라이브러리 크롤러" 섹션 참조. - 커밋은 반드시
<TYPE>/...(예:DAT-3242/...) feature 브랜치에서 — main 직접 커밋은 훅이 차단. - 스케줄링은 이 repo가 아니라
~/labrador/platform/labrador-data-platform(Airflow DAG,dags/<crawler>_crawler.py) — 새 크롤러 운영/주기/모드(증분·풀·resync)는 거기서. (메모리 library-crawler-scheduling 참조.) - pre-commit 훅이 black(79)/isort/pylint(W0611,W0612,W0613)/mypy/docstring을 강제.
- The 2026-08-20 RHEL VEX v4 work is a redesign, not a patch to the current
parser. It measures all 63,152 documents and specifies three tables (CPE dictionary, vulnerability facts, and document state), channel/module-aware matching, and document-scoped recomputation with row-diff application.
- RHEL VEX v4 scraper implemented 2026-08-27 on branch
dat-3533/rhel_vex_v4 (commit 519024e): etl_components/os_pkg_vuln/rhel_vex_crawler/ — models per the final multi-OS schema (CHANNEL_ID replaces CPE, streams folded, MODULE ''-default in PK, IS_DELETED soft delete, SOURCE_ID/SOURCE_REF, URL), relationships-based parser (rhel_vex_parsing.py, pure/tested), archive+changes.csv+deletions.csv raw crawler with gap-free inclusive watermarks, document-scoped diff loader. Golden set (8-CVE sheet) zero real mismatches. Trial load, DAG (DAT-3557), and cutover remain. NOTE: fact table name equals live v3 TB_OS_PKG_VULN_RHEL — trial must target a test schema.
- v3 rhel_crawler REMOVED 2026-08-27 (commit
232218a, main). - The 2026-08-20 crawler-source pre-review distinguishes technically reachable
URLs from approved sources. High-risk sources route to written agreement, official interfaces, or replacement data; medium-risk sources require runtime rate-limit, API-key, User-Agent, and robots controls. This is neither completed legal advice nor proof that every affected production source was already removed.
Relevant when:
- 새 수집기/스크래퍼 컴포넌트 추가, 기존 컴포넌트 수정, 커밋/훅 문제 디버깅.
Do not read full document unless:
- 이 repo에서 코드 작업하거나 훅/배포 문제를 겪을 때.
Linked documents:
ai/workspace/repos.mdai/repo-notes/labrador-data-platform.mdai/decisions/2026-06-15-malicious-package-openssf-collection.mdai/worklog/2026/2026-W25.mdai/worklog/2026/2026-W26.md(spm_scraper 누락 도구 + 버전 백필)ai/repo-notes/crawler-lib-swift.md(별개 = CocoaPods 크롤러; SPM 아님, 혼동 주의)ai/wiki/projects/rhel-vex-vuln-collection.mdai/wiki/projects/crawler-source-governance.mdai/sources/confluence/2026-08-20-crawler-source-legal-review.md
Repo Info
- Repo ID:
labrador-scrapers - Main branch:
main - Local path:
~/labrador/platform/labrador-scrapers - Remote:
bitbucket.org:labradorlabs/labrador-scrapers.git
Architecture Notes
etl_components/<name>/: 컴포넌트별 디렉토리. 표준 파일 세트 —
main.py(엔트리), consts.py, base.py(Database dataclass), db_utils.py (LabradorDB: selects/insert/update, ON DUPLICATE KEY UPDATE, 100개 청크), utils.py(CommonUtil snake/camel·JSON), exceptions.py (LabradorScraperCriticalError), <name>_models.py, Dockerfile, install_deps.sh, pip.conf, version.txt.
- 스크래퍼 모델:
class X(BaseScraper)—__init__(super().__init__(Model)),
initialize()(DB open·테이블 보장·상태 로드), scrape_data() -> List[BaseRecord] (self.done=True 될 때까지 반복 호출). 직접 DB 쓰기는 self.db = LabradorDB()로 하고 []를 반환해도 됨(osv_raw_crawler/openssf_mal 방식).
- main.py CLI:
--scraper <name> --target-db <url> --source-db <url> --iter-num
--scraper-args "k=v,k2=v2" --incremental/--no-incremental --log-to-console --log-level. find_scraper가 이름→클래스 매핑.
- 상태/증분:
TB_CRAWLER_STATUS(모델CrawlerStatus, PK=TYPE)에RAW_DATAJSON으로
마지막 동기화 상태 저장. 컴포넌트별 TYPE 사용(예: OSV_RAW, OPENSSF_MAL).
- Dockerfile:
python:3.12-slim-bullseye,labrador-sqlmodel==1.4.2rc1설치,
install_deps.sh로 추가 의존성, COPY *.py ., ENTRYPOINT python3 main.py. PVC는 /resources에 마운트(RESOURCES_DIR).
컴포넌트: openssf_mal (OpenSSF 악성 패키지)
- 위치
etl_components/openssf_mal/. OpenSSFossf/malicious-packages(OSV JSON,
MAL-YYYY-NNNN)를 git clone/pull로 PVC에 받아 TB_COMP_LIB_MAL에 적재.
- 단일 테이블
TB_COMP_LIB_MALPK(VULN_ID, REPOSITORY, PRODUCT_KEY),
VULN_RANGE는 | 구분 토큰 합집합, DEPRECATED(0/1) 소프트삭제 + VIEW_COMP_LIB_MAL.
REPOSITORY정규화: `npm→NPM, PyPI→PYPI, RubyGems→RUBYGEMS, NuGet→NUGET, Go→GOLANG,
crates.io→CARGO, Maven→MAVEN, Packagist→COMPOSER. vscode/git`은 스킵.
- npm
PRODUCT_KEY= AES-CBC:base64(AES-CBC(name)), key=IV=b'NPMNPMNPMNPMNPMN'
(원본 npm_crawler/ai/labradorlabs/util/aesCbc.py). 그 외 생태계는 평문명.
install_deps.sh에git(데비안) 과pycryptodome추가 필요.- 추가 테이블
TB_COMP_LIB_MAL_INFO(PK=VULN_ID): advisory 단위 메타
SUMMARY/DETAILS/MAL_REFERENCES(=osv.references, REFERENCES는 예약어)/ALIASES. MAL과 동일 라이프사이클(같은 run_ts upsert, 변경 시 MAL/MAL_INFO 양쪽 deprecate→재upsert, full sweep 양쪽) + VIEW_COMP_LIB_MAL_INFO. MAL 행 생긴 advisory만 생성. 같은 스크래퍼가 두 테이블 함께 적재. 신규 테이블 백필 = --scraper-args "full_reconcile=true".
- 설계:
ai/decisions/2026-06-15-malicious-package-openssf-collection.md,
정본 human/reports/2026-malicious-package-collection.md.
컴포넌트: spm_scraper (Swift Package Index / SPI)
- 위치
etl_components/spm_scraper/.spm_build_processor.py(SpmBuildProcessor).
DB: gatheringdb TB_COMP_LIB_PRODUCT_SPI / TB_COMP_LIB_VERSION_SPI (LANGUAGE='swift', PACKAGE_MANAGER='SPM', PRODUCT_KEY=owner/repo).
- 수집 흐름: universe = SPI sitemap
swiftpackageindex.com/sitemap.xml→ URL 끝 2세그먼트
= PRODUCT_KEY. 버전 = GitHub tags HTML 스크랩(github.com/{owner}/{repo}/tags?after=<last_tag> 커서, div.Box-body.p-0[data-view-component] … h2.f4.d-inline>a). 태그별 git clone+checkout 으로 라이선스(licensee) 추출. 태그 0개 repo는 product 통째로 skip(미적재).
- 누락 도구:
~/labrador/tool/spm_missing_products(미커밋,*_missing_products패턴).
sitemap이 로컬 Cloudflare 403이라 동일 데이터 SwiftPackageIndex/PackageList/packages.json (GitHub raw)을 universe로 사용. product 누락(차집합) + version 누락(repo별 GitHub 태그 vs DB). 백필 충실판 collect_spm_missing.py(크롤러 product_process 재사용, 런타임 필요) / 간이판 collect_spm_missing_simple.py(누락 태그를 version row 직접 적재, RELEASE_DATE는 GitHub /commits/{tag}, SORT_ORDER semver 재계산, LATEST_VERSION 보정). 상세 = W26 worklog. grace window(2026-07-06, extract_spm_missing_products.py --grace-days 기본 3): PackageList 최근 N일 추가분은 SPI 인덱싱→sitemap→크롤러 정상 지연(0~2일) 통과 중일 수 있어 누락 대신 spm_missing_grace.txt 로 분리(GitHub API 로 N일 전 시점 packages.json 과 비교). 근거: 7/4~7/5 추가 6건이 "누락"으로 떴으나 전부 지연이었고(태그 보유, collect_spm_new_products.py 로 즉시 백필 완료 — product 6/version 12), 7/1~7/3 추가 18건은 전부 정상 수집돼 있었음. 상세 = W28 worklog.
한계/후속 1 — GitHub 전용 스코프 (GitLab/Bitbucket 사각지대)
- SPI는 GitHub 호스팅 repo만 인덱싱한다. 근거: ① PackageList
packages.json**11,140개
전부 github.com, non-GitHub 0개(2026-06-22 실측). ② PackageList 검증 validate.swift가 제출 URL의 owner/repo를 추출해 https://api.github.com/repos/{owner}/{repo} · raw.githubusercontent.com으로만 메타/기본브랜치/트리를 조회 → 비-GitHub URL은 검증 통과 불가 (코드 레벨 강제). ③ 크롤러도 spm_build_processor.py:21 git_url="https://github.com/" 하드코딩 (태그 페치·URL 컬럼 전부 github 고정; 1235줄 gitlab 분기는 SCM 링크 파싱 fallback일 뿐 실제 태그 경로 아님). 참고: 현재 공개 FAQ/Add-a-Package 마크다운엔 "GitHub만"이라는 명시 문장은 없음**(일반 git 요구사항으로만 기술) — 강제는 문서가 아니라 validate.swift가 함.
- 함의: SPM 매니저 자체는 모든 git 호스트(GitHub/GitLab/Bitbucket/사내) 지원하므로
GitLab/Bitbucket Swift 패키지는 생태계엔 존재하지만 SPI 색인 밖 → 우리 DB universe 밖 → 누락으로도 안 잡힘(분모 밖). 즉 누락 체크 결론은 항상 "GitHub 호스팅 + SPI 색인 한정" 스코프. SPI 미러로서는 올바른 범위지만 GitLab/Bitbucket은 알려진 사각지대.
- 잡으려면 ① SPI 아닌 별도 수집 목록(SPI가 그것들을 빼므로) + ② 크롤러를 호스트별 태그 API로
일반화: GitLab GET /api/v4/projects/{id}/repository/tags, Bitbucket GET /2.0/repositories/{ws}/{repo}/refs/tags. (크롤러 본체 변경 → 현재 보류.)
한계/후속 2 — GitHub tags HTML 스크랩 → GitHub API 전환 권장
- 버전 소스가 GitHub tags HTML이라 (a) GitHub HTML 구조 변경, (b) **동시성↑ 시 throttle로
태그 페이지 일부만 반환 → 가짜누락(false-positive) 에 취약. 실측(2026-06-22, 16-worker): 1차 누락 9건 중 5건이 직렬 재검증 시 0 누락(throttle 잔상). 진짜 누락은 4건뿐. → 누락 체크는 확정 전 직렬/저동시성 재검증 단계 필수**(도구에 반영).
- 개선안: HTML 스크랩 대신 GitHub API(
GET /repos/{o}/{r}/tags100/page, 또는 git refs)
사용 → 파싱 깨짐·throttle 가짜누락 제거 + 태그 날짜까지 한 번에(간이 백필이 이미 /commits/{tag}로 RELEASE_DATE 정확히 가져온 것처럼). 비용: rate limit — 비인증 60/hr은 11k repo에 부족, 토큰 인증 5,000/hr이면 가능(또는 GraphQL 배치). 크롤러 본체 변경 사안.
누락 포인트 상세 (코드 라인, 2026-06-22 정독)
전부 spm_build_processor.py. 가장 위험 = A1·A2·B1 연쇄(태그 HTML 커서 긁기의 조용한 절단 → 버전 0 → product까지 skip → 복구 큐 없음 → 다음 성공 풀런까지 미복구).
- A1[치명]
_get_extract_version_listL1110-1118 (async L155-162): 페이지네이션 중
한 페이지라도 비200이면 그때까지 모은 것만 반환 = 조용한 절단. (16-worker 가짜누락 원인.)
- A2[치명] L1102/L148:
?after=커서(직전 마지막 태그명) **URL 미인코딩 + 페이지 상한
없음** → 특수문자 태그명에서 커서 정지 → 무한재귀/뒤버전 누락. (우리 복제본은 quote+max_pages=200.)
- A3[높음] L1123-1132: 단일 CSS 셀렉터 의존 → GitHub 마크업 변경 시 전 repo 버전 0건.
- B1[치명]
product_processL400-406: 버전목록 비면 product upsert 자체 skip.
A1/A2로 "fetch 실패=0"과 "진짜 태그0"을 구분 못해 일시오류 한 번에 product 통째 누락. 게다가 product 생성 전 L402, version 처리 시 L437 태그를 2번 fetch → 1차 성공(product 생성)/ 2차 실패(버전0) 시 버전0 고아 product 발생 가능.
- B2[높음]
getNewProductsL317-327: sitemap 실패 시 [] 반환 → 그날 product 패스 무동작(조용). - C1[높음] product(product_process)/version(version_process_async=기존 product만 순회) 2패스
의존 + pending/retry 마킹 없음(go 크롤러와 대비) → 한번 놓치면 복구 안 됨.
- C3[품질] RELEASE_DATE = sitemap lastmod 한 값(L1030/562/765) → 한 패키지 모든 버전 동일 날짜
(실제 태그 날짜 아님; 기존 행 전부 동일일자였던 이유).
- D1
getNewProducts캐시 dead code(결과를self.new_products에 미할당) → 매 패스 sitemap 재요청.
PK 제약 = 버전없는 패키지 처리 + 실태 (2026-06-22 분류)
TB_COMP_LIB_VERSION_SPIPK=(LANGUAGE,PACKAGE_MANAGER,PRODUCT_KEY,VERSION), VERSION NOT NULL
→ 버전(=git 태그) 없는 패키지는 version 행 불가. PRODUCT_SPI는 LATEST_VERSION nullable이라 product만은 가능. 크롤러는 태그0이면 product까지 skip(B1)으로 처리 → 진짜 태그없는 SPI 패키지는 영구 사각지대 (product 누락에 잡히나 백필 불가=정상).
- 고아 product(version 0개) 404개 분류(GitHub 태그 재조회, 0/에러는 직렬 재검증): 태그있음 3,
진짜 태그없음 401, 에러 0. → 고아 대부분은 버그가 아니라 진짜 태그없음. 태그있는 3개 (heirloomlogic/Persnicket 13, hjuraev/nats-swift 1, workingDog/OSRMSwift 1)는 recover_orphans.py로 15 version 복구(product 행에서 메타 상속). 남은 401은 복구 불가=정상.
- missing product 218 분류: 태그있음 192(=진짜 product 누락, 수집됐어야 함) / 진짜 태그없음 26
(영구 사각지대=정상). 192는 product+version 둘 다 신규 생성 필요 → SPI 상세페이지(DetailParser)가 로컬 Cloudflare 403이라 크롤러 런타임 필요(미해결).
컴포넌트: 라이브러리 크롤러 (npm / pypi / conan / vcpkg / hunter)
crawler-lib-* 외에, 라이브러리(SBOM/패키지) 크롤러 일부가 이 모노레포의 etl_components/ 안에 컴포넌트로 들어와 있다. 두 계열로 나뉜다 (2026-06-23 실측).
계열 A — 레거시 ai/labradorlabs/ 포팅 (work-list LIST 방식)
crawler-lib-*와 같은 코드 계보. 컴포넌트 안에 자체 ai/labradorlabs/... 소스 트리 + info_data dict(테이블 라우팅) + LIST work-list 테이블(PROCESSED flag)을 그대로 들고 있다. main.py의 find_scraper만 BaseScraper 프레임워크에 맞춤.
npm_crawler—LANGUAGE='javascript',REPOSITORY='NPM'.
- 메인 로직 ai/labradorlabs/npmCrawler.py(info_data); 진입 NpmPackageCrawler.py (NpmCrawling/NpmCrawlingIncremental), NpmPackageUpdateCrawler.py, crawling_by_name.py. - 테이블: VERSION=gatheringdb.TB_COMP_LIB_VERSION_JAVASCRIPT, PRODUCT=TB_COMP_LIB_PRODUCT, LIST=gatheringdb.TB_COMP_LIB_JAVASCRIPT_LIST, LICENSE=gatheringdb.TB_COMP_LIB_LICENSE_JAVASCRIPT. - scraper 이름: npm_package_update, npm_crawling. - 라이선스 폴백에 GitHub API(api.github.com/repos/{owner}/{repo}/tags) 사용. - 소스 URL(config.ini [NPM_CRAWLER]): 업데이트 피드 = replicate.npmjs.com/registry/_changes(CouchDB _changes), 메타 = registry.npmjs.com/{product}(전체 doc). - 피드/버전 수집 모델(누락 진단 핵심, 2026-07-09 검증): _changes 는 coalesced → 패키지는 since 순방향에서 문서 최신 seq 에 1회만 등장(버전당 X, 이전 버전 seq 는 이후 업데이트로 덮여 사라짐). 하지만 npm_package_update 는 패키지명만 work-list (processed=0, 재등장 시 ON DUPLICATE KEY UPDATE PROCESSED=VALUES(PROCESSED) 로 0 리셋)에 넣고, npm_crawling 이 registry.npmjs.com/{pkg} 전체 doc 을 받아 api_versions − db_versions 로 없는 버전 전부 삽입(npmCrawler.py:904-948). → 피드 gap ≠ 버전 누락(패키지가 피드에 1회만 떠도 그 시점에 전체 버전 백필). - 진짜 누락 조건: fetch 한 doc 이 부분 200(feed-ahead-of-meta: 피드가 메타 CDN 보다 앞섬) + 이후 재-publish 없음 → 재크롤 안 됨 → 영구 누락. 404 는 retry-ladder (PROCESSED 30~34, npmCrawler.py:29~)가 커버하지만 부분 200 은 미커버. 진단: LIST TIMESTAMP(INSERT 1회, upsert 미변경)=최초 등록 / LAST_UPDATED=마지막 크롤, version 행들의 동일 LAST_UPDATED=단일 크롤 백필. 사례 @zylem/behaviors = 실누락 아님(6월 seq 미수신 but 7/7 1회로 3/3). 상세 ai/worklog/2026/2026-W28.md.
pypi_crawler—ai/labradorlabs/pypiCrawler.py. 메타 소스 = PyPI JSON API
https://pypi.org/pypi/{name}/json. - 테이블: VERSION=gatheringdb.TB_COMP_LIB_VERSION_PYTHON, PRODUCT=labradordb.TB_COMP_LIB_PRODUCT, LIST=gatheringdb.TB_COMP_LIB_PYTHON_LIST, LICENSE=gatheringdb.TB_COMP_LIB_LICENSE_PYTHON. (npm은 PRODUCT가 unqualified, pypi는 labradordb. 명시 — 컴포넌트마다 default DB 가정이 달라 주의.) - scraper 이름: pypi_package_update, pypi_package_list_full_scan, pypi_crawling, pypi_crawling_incremental, pypi_crawling_resync. - --workers 동시 워커 옵션 있음(pypi_crawling* 대상).
계열 B — labrador_sqlmodel BaseScraper, RAW→VERSION→PRODUCT 3단
git repo를 PVC(/resources)에 clone해 파일/레시피를 파싱하는 3단 스크래퍼. 각 단계가 별도 scraper로 등록되어(find_scraper) 순서대로 호출된다: RAW(원본 추출) → VERSION(버전 생성) → PRODUCT(최신버전→product). SQLModel BaseRecord 모델 + 생태계별 전용 테이블(공유 TB_COMP_LIB_PRODUCT 아님).
conan_crawler— C/C++ (Conan). 소스 =github.com/conan-io/conan-center-index
clone → /resources/conan-center-index. - 단계/scraper: conan_raw(ConanCrawler/ConanIncrementalCrawler) → conan_version(ConanVersionCreator) → conan_product(ConanProductCreator). - 테이블: TB_COMP_LIB_CONAN_RAW(CONAN_DATA_YML/RECIPE_FOLDER/LIBRARY_FOLDER) → TB_COMP_LIB_VERSION_CONAN(UNIQUE (CONAN_RAW_ID, VERSION)) → TB_COMP_LIB_PRODUCT_CONAN.
vcpkg_crawler— C/CPP (vcpkg). 소스 =github.com/microsoft/vcpkgclone →
/resources/vcpkg. LANGUAGE='c_cpp'(version은 'C/CPP'), REPOSITORY/REPOSITORY_NAME='VCPKG'. - 단계/scraper: vcpkg_raw(VCPKGRAWCrawler) → vcpkg_version(VCPKGVersionCrawler/VCPKGVersionIncrementalCrawler) → vcpkg_product(VCPKGProductCrawler). - 테이블: TB_COMP_LIB_VCPKG_RAW → TB_COMP_LIB_VERSION_VCPKG → TB_COMP_LIB_PRODUCT_VCPKG. tests/ 디렉토리 보유(단일 product 버전수 검증 등). - version 수집 = registry 기반(2026-07-07, 0cfa080): vcpkg_registry.py가 versions/<x>-/<port>.json을 열거(oldest-first, #N dedup) 후 각 entry의 git-tree에서 git show로 vcpkg.json 또는 CONTROL(전용 파서) 읽음 → CONTROL 시절(≤2021-08) 버전 포함. registry 파일 없는 포트만 기존 vcpkg.json git-log 폴백. shallow clone이면 unshallow 가드. 백필 풀 런 2026-07-07 labCrawl1 수동 실행(갭 1,324포트/9,190버전). W28 참조.
lib_hunter_crawler— C/C++ (Hunter cmake 패키지 매니저). 소스 =
github.com/cpp-pm/hunter clone → /resources/hunter, cmake/projects/<lib>/ 의 hunter*.cmake 파일을 파싱(FileSystemIterationBasedScraper). - crawler: HunterRawCrawler, HunterVersionCrawler, HunterProductCrawler, HunterArchiveRawCrawler + hunter_version_sorter. - 테이블: TB_COMP_LIB_RAW_HUNTER → TB_COMP_LIB_VERSION_HUNTER → TB_COMP_LIB_PRODUCT_HUNTER (+ archive raw 단계).
주의
- 계열 B 3개(conan/vcpkg/hunter)는 전부 C/C++ 생태계이며 같은 3단 패턴이라
한 곳을 고치면 나머지 점검 권장.
- 누락 탐지+백필 도구
~/labrador/tool/cpp_missing_products(2026-06-23 신설, 셋 통합
--eco). universe = upstream git repo 파싱(크롤러와 동일): conan=conandata.yml sources, hunter=hunter_add_version VERSION, vcpkg=ports/*/vcpkg.json의 git 히스토리(레지스트리 versions/는 CONTROL시절·port-version-suffix 때문에 8566 과다보고 → 금지, git-log가 정답). detect→extract_missing.py, backfill→backfill.py(기본 dry-run, --commit). 자세히는 W26. - ⚠️ W28 재평가(2026-07-07): 위 "과다보고 8566"에는 CONTROL 시절 실제 버전(진짜 누락)이 섞여 있음. vcpkg 크롤러는 vcpkg.json git log만 walk 하므로 포트별 매니페스트 전환(2020-06~2021-08) 이전 버전은 구조적으로 수집 불가 — 예: zeromq는 upstream distinct 36 중 4만 수집(32 결손, vcpkg.json 생성 2021-07-26). 탐지 도구도 같은 universe라 이 결손이 안 보임. 보완 방향 = registry versions/<x>-/<port>.json distinct base version + git show <git-tree>(CONTROL 파서 필요), #N suffix만 dedup. W28 참조.
- 모노레포
etl_components/에는 이 5개 + spm/openssf_mal 외에도 vuln/CVE/CPE/EPSS/KEV
등 다수 컴포넌트가 있다(ls etl_components/). 본 섹션은 라이브러리 패키지 크롤러만.
- conan 테이블 스키마 정렬(2026-06-24 cutover 완료): conan VERSION/PRODUCT를 vcpkg 포맷으로 통일함 —
복합 PK (LANGUAGE,REPOSITORY,PRODUCT_KEY[,VERSION]), LANGUAGE='c_cpp'/REPOSITORY='Conan', CONAN_RAW_ID/auto ID 제거, version에 RAW_UPDATED 추가, product 타임스탬프 CREATED/LAST_UPDATED. DB rename 완료: 라이브 TB_COMP_LIB_VERSION_CONAN(5,676)·TB_COMP_LIB_PRODUCT_CONAN(1,929)이 신규 스키마, 옛 스키마는 *_CONAN_OLD 백업. 코드는 dat-3257/lib_crawler(5547872 스키마정렬 + 3c32f21 게이트 LAST_UPDATED). ‼️ 단, 운영 이미지가 옛 코드면 다음 크롤러 run이 깨짐 → dat-3257 머지·이미지 배포 선행 필수(미배포 시 13:00 UTC 전 DAG 정지/롤백). 옛 잔재 정리 완료(2026-06-24): 고아 TB_COMP_LIB_CONAN_VERSION(2024-10 동결) → ..._DEPRECATED 리네임(모니터 후 DROP 예정), 죽은 conan_to_comp_lib_product_op 삭제(data-platform 1f65331, 미푸시). 상세·롤백 SQL = W26 worklog.
- [버그, 진단 2026-07-01] conan product가 공용
TB_COMP_LIB_PRODUCT로 미전파 → 다운스트림 "누락":
conan 크롤러는 product를 TB_COMP_LIB_PRODUCT_CONAN 전용 테이블에만 쓴다. 공용 gatheringdb.TB_COMP_LIB_PRODUCT(REPOSITORY='Conan')로의 복사는 data-platform op conan_to_comp_lib_product_op가 했는데, 그 op가 2024-07-08경 멈춤(공용 conan MAX(CREATED)=2024-07-08) 후 2026-06-24 삭제됨. 결과: 2024-07-08 이후 추가된 conan 전부 공용 미반영 = _CONAN 1,931 vs 공용 1,634(2026-07-01), gap ~297. vuln processor·누락체크 등 공용 테이블을 읽는 소비자에겐 "누락"으로 보임(크롤러 데이터 자체는 정상). 2026-07-01 사용자 25건은 _CONAN→공용 INSERT…SELECT로 백필(공용 1,659). 근본 해결: 전파 op 복원/대체 또는 크롤러가 공용에도 upsert(+잔여 272 일괄 백필). ⚠️ vcpkg(_VCPKG)/hunter(_HUNTER)도 동일 구조 → 공용 전파 여부 점검 필요. (go/spm vuln processor 이슈와 동류 = 생태계 전용테이블 vs 공용테이블 불일치.)
Common Commands
# 로컬 실행
python3 main.py --scraper <name> --source-db <url> --target-db <url> \
--log-to-console --log-level INFO
# openssf_mal 강제 full reconcile
python3 main.py --scraper openssf_mal --source-db <url> --target-db <url> \
--scraper-args "full_reconcile=true"
# 커밋 전 로컬 훅 검사(도구 필요): black/isort/pylint/mypy
black --check --line-length 79 --target-version py312 --skip-string-normalization *.py
isort --check-only -m 3 *.py
pylint -d all -e W0611,W0612,W0613 *.pyKnown Pitfalls
- main 직접 커밋 불가:
.githooks/prepare-commit-msg가 브랜치명에서
(FWK|DAT)-[0-9]+ 티켓을 추출(tr로 대문자화 — bash 3.2 OK). main에선 추출 실패 → exit 1. 반드시 DAT-xxxx/... feature 브랜치에서 커밋 후 main으로 FF 머지.
- pre-commit docstring 강제: 모든 함수/메서드(클래스 내
__init__제외)에 docstring
필수. 누락 시 커밋 거부.
- 로컬에 black/mypy 없으면 훅이 "command not found"로 실패 → 커밋 거부. venv에 설치 후
PATH="<venv>/bin:$PATH" git commit으로 훅이 도구를 찾게 한다.
- K8s PVC git "dubious ownership"(exit 128): PVC repo 소유자 uid ≠ 컨테이너 uid면
git fetch/rev-parse가 거부됨. 해결: safe.directory를 글로벌 config로 등록해야 하고 (-c/CLI는 git이 보안상 무시), 베이스가 bullseye=git 2.30.x라 GIT_CONFIG_GLOBAL (2.32+)·safe.directory=*(2.35.2+)가 안 먹힘. → HOME의 .gitconfig에 * + 명시 repo 경로를 둘 다 등록(명시 경로는 전 버전 지원). openssf_mal repo_sync.py 참고.
- subprocess로 git 호출 시 실패하면 stderr를 예외 메시지에 포함시켜야 원인이 보인다.
setup_logger_config로그 중복/레벨 리셋(2026-07-09):labrador_sqlmodel/core/logging.py
:73-80 이 호출 시점에 loggerDict 의 모든 기존 named 로거에 handler 부착 + propagate=True + setLevel(level) 을 강제. 부작용 2가지 — ① 중복 로깅: setup 이전에 생성된 named 로거(모듈 최상단 import 로 getLogger(__name__) 실행)는 자기 handler + root 전파로 2회 출력. ② 레벨 억제 무효화: import 시점에 낮춰둔 레벨 (예: sqlalchemy.engine=WARNING)이 run --log-level 로 되돌려짐. 회피: 노이즈 로거는 메서드/__init__ 내부 지연 import(setup 이후 생성)로 두거나 setup 호출 직후 레벨 재설정. 사례: hunter LicenseeUtil ×2(2af380b), npm SQLAlchemy echo 가 ERROR 로 오탐 (echo 억제가 setup 에 덮임 — 데이터에 "ERROR" 든 패키지에서 모니터 오탐). 상세 W28 worklog.
컴포넌트: comp_lib_vuln_processor (취약점 → product 매핑)
- 위치
etl_components/comp_lib_vuln_processor/. TB_VULN_COMP_LIB ↔TB_COMP_LIB_PRODUCT
매핑/업데이트/삭제. repository별 언어 = base_mapper.REPOSITORY_TO_LANGUAGE (maven/npm/composer/pypi/rubygems/golang/cocoapods/spm/nuget/hunter/vcpkg/conan).
- product 테이블 선택 로직(4곳 동일 2분기):
conan/vcpkg/hunter → gatheringdb.TB_COMP_LIB_PRODUCT,
그 외 전부 → labradordb.TB_COMP_LIB_PRODUCT. 위치: base_mapper._setup_table_names(:94), db._get_comp_lib_table_name(:258), comp_lib_vuln_deletor(:95), comp_lib_vuln_data_integrator(:84). UPSERT = INSERT ... (LANGUAGE,REPOSITORY,PRODUCT_KEY,VULN_RANGE) ON DUPLICATE KEY UPDATE VULN_RANGE (db.UPSERT_COMP_LIB_PRODUCT_QUERY).
[go 수정완료 05a76da(main), spm 잔여 | 2026-07-01] go(및 spm) product를 잘못된 labradordb 구 테이블에 씀
수정(2026-07-01, main
05a76da):db.py에 단일 리졸버resolve_comp_lib_product_table(repository)신설, 4곳(db._get_comp_lib_table_name/base_mapper._setup_table_names/comp_lib_vuln_deletor/comp_lib_vuln_data_integrator)이 전부 이걸 경유. golang →gatheringdb.TB_COMP_LIB_PRODUCT_GOLANG(REPOSITORY/PRODUCT_KEY/VULN 컬럼 보유 → upsert/join 불변). conan/vcpkg/hunter·나머지 불변. spm 잔여:TB_COMP_LIB_PRODUCT_SPI가 REPOSITORY 대신 PACKAGE_MANAGER 컬럼이라 REPOSITORY 기반 upsert/join과 불호환 → 별도 컬럼매핑 필요(이번 커밋 제외, labradordb 유지). py_compile OK, 테스트 없음· 로컬 deps 미설치라 런타임/DB 동작은 이미지에서 확인 필요. 아래는 원 진단.
- 문제: go 크롤러는 product를
gatheringdb.TB_COMP_LIB_PRODUCT_GOLANG(별도 테이블명, as_cs)로
이관했는데, vuln processor의 2분기 로직은 go(repository='golang')를 "그 외"로 보고 labradordb.TB_COMP_LIB_PRODUCT(마이그레이션 전 동결 사본, 2026-05-28 CREATED 멈춤)에 UPSERT. → ① gatheringdb의 신규 go product는 취약점(VULN_RANGE) enrichment를 못 받음, ② 옛 labradordb go 행만 재기록돼 LAST_UPDATED(on update CURRENT_TIMESTAMP)만 bump (실측 product 50건/30d). SPM도 동일 위험(gatheringdb.TB_COMP_LIB_PRODUCT_SPI인데 현재 labradordb로 감).
- 참고:
labradordb.TB_COMP_LIB_VERSION_GOLANG갱신(1,025/30d)은 scrapers 참조 0건 → go 크롤러측 별건
(no-flag/resync 추정), vuln processor와 무관.
- 타깃 준비됨:
gatheringdb.TB_COMP_LIB_PRODUCT_GOLANG= PK(LANGUAGE,REPOSITORY,PRODUCT_KEY)+VULN_INFO+VULN_RANGE
보유 → DDL 불필요. (as_cs 케이스구분 = go 정체성 일치.)
- 수정안(DRY): repository→FQ product 테이블 단일 리졸버(
resolve_comp_lib_product_table)로 4곳 분기
통일. 매핑: conan/vcpkg/hunter→gatheringdb.TB_COMP_LIB_PRODUCT, golang→gatheringdb.TB_COMP_LIB_PRODUCT_GOLANG, spm→gatheringdb.TB_COMP_LIB_PRODUCT_SPI(검증 후), 그 외→labradordb.TB_COMP_LIB_PRODUCT(현행). UPSERT/JOIN 불변, 테이블명만 리졸버 경유. 옵션: 기존 labradordb go VULN_RANGE→PRODUCT_GOLANG 일회성 백필. 단위테스트로 매핑 검증. 상세 W27 worklog(2026-07-01).
Current Work
openssf_mal컴포넌트 main 반영(f3e8a4e구현 →94609ddsafe.directory 수정).
실행 검증은 이미지 재빌드 후 진행 중(PVC clone 소유권 통과 확인, 이후 적재 검증 예정).