SBOM은 당신이 무엇을 설치했는지는 증명하지만, 설치했어야 하는 것은 증명하지 못합니다.
요약
SBOM과 CVE 스캔은 설치된 패키지를 기록할 뿐, AI 에이전트가 제안한 패키지의 신뢰성이나 환각 여부를 검증하지 못합니다. 이를 해결하기 위해 설치 전 단계에서 보증된 베이스라인과 대조하여 허용 여부를 결정하는 '공급망 게이트' 방식의 보안 접근법을 제안합니다.
핵심 포인트
- SBOM은 사후 기록일 뿐, 에이전트의 잘못된 패키지 제안을 막지 못함
- AI 에이전트의 패키지 환각 및 타이포스쿼팅 위험성 존재
- 설치 전 단계에서 보증된 스냅샷과 대조하는 'Default-Deny' 방식 필요
- 단순 CVE 스캔으로는 신규 등록된 악성 패키지를 탐지하기 어려움
설치 전 공급망 게이트(pre-install supply-chain gate)는 npm install이 실행되기 전, 출처(provenance)를 기준으로 AI 에이전트가 제안하는 각 패키지에 대해 ALLOW(허용) 또는 DENY(거부)를 반환합니다. 여기서 출처란 이름이 보증된 스냅샷(vouched snapshot)이나 인기 있는 베이스라인(popular baseline)에 포함되어 있는지, 그리고 .npmrc 레지스트리가 신뢰할 수 있는지를 의미합니다. 종속성 해결(resolve) 후에 생성된 SBOM은 그 질문에 답할 수 없습니다. 이 포스트의 공격 매니페스트(attack manifest)에서 supply_chain_gate.py는 2개의 DENY를 반환하고 종료 코드 1(exits 1)로 종료됩니다.
AI 공개 (AI disclosure): 저는 AI 어시스턴트와 함께
supply_chain_gate.py를 작성했으며, 게시하기 전에 오프라인에서 직접 실행했습니다. 아래 출력 블록의 모든 숫자는 Python 3.13.5, 표준 라이브러리만 사용하고 네트워크 연결이 없는 실제 로컬 실행 환경에서 복사한 것입니다. 저는 종료 코드(0 / 1 / 2)를 확인했고, STDOUT을 두 번 해싱하여 바이트 단위로 결정론적(deterministic)임을 확인했으며, 모든 줄을 직접 편집했습니다. 제가 인용하는 외부 수치(USENIX 2025 패키지 환각 연구)는 연구자들의 수치이며 제 것이 아닙니다. 저는 출처를 링크하고 그들이 어떻게 측정했는지 명시합니다. 의도적으로 그들의 수치와 제 실행 결과의 수치를 별도의 단락에 유지하고 있습니다.
요약하자면 (In short):
- SBOM과
npm install이후 실행되는 CVE 스캔은 무엇이 해결(resolved)되었는지, 그리고 알려진 CVE가 있는지 기록합니다. 하지만 둘 다 당신의 에이전트가 애초에 그 이름을 제안했어야 했는지 여부는 말해주지 못합니다. - 코딩 에이전트(coding agent)는 이름이 실제인지, 환각(hallucinated)인지, 혹은 실제 이름에서 한 글자만 틀린 것인지와 관계없이 동일한 평탄한 신뢰도(flat confidence)로 의존성(dependency)을 추천합니다. 그 신뢰도는 바로 사후 해결(post-resolve) 스캔이 꿰뚫어 볼 수 없는 부분입니다. 즉, 어제 등록된 이름은 아직 CVE가 없으므로, 알려진 CVE 스캔(known-CVE scan)은 이를 깨끗한 상태로 분류합니다.
supply_chain_gate.py는 매니페스트(에이전트가 제안한 패키지, 당신이 보증한 스냅샷, 그리고.npmrc)를 읽고, 설치 전에 번들로 포함된 인기 베이스라인(popular baseline)과 대조하여 패키지당 허용(ALLOW) 또는 거부(DENY)를 반환합니다.- 논점을 뒷받침하는 결과:
express(정확히 일치)를 허용(ALLOW)하는 동일한 277개 이름의 베이스라인이 형제 매니페스트에 있는expresss는 거부(DENY)합니다. 한 글자 차이로 판결이 뒤집힙니다. 이를 뒤집는 것은 알려진 악성 이름에 대한 정적 차단 목록(static blocklist)이 아니라, 보증된 베이스라인에 대한 기본 거부(default-deny) 방식입니다. 편집 거리(edit-distance) 체크는 단지 거부(DENY)에 레이블(TYPOSQUAT:express)을 붙여 사람이 그것이 어떤 실제 이름을 모방하고 있는지 알 수 있게 할 뿐입니다. - 표준 라이브러리만 사용합니다 (
json,re,difflib,sys,hashlib). 네트워크, 해결(resolve), 서브프로세스(subprocess), 설치(install), 패키지 내용 읽기를 수행하지 않습니다. 실행은 바이트 단위로 결정론적(deterministic)입니다. 도구와 모든 피스처(fixtures)는 이 포스트에 포함되어 있습니다.
격차가 발생하는 지점
SBOM은 영수증입니다. 당신이 무엇을 설치했는지 알려줍니다. 하지만 당신의 선택이 옳았는지는 알려주지 않습니다.
에이전트 스택(agent stacks)에서 제가 계속 목격하는 실패 사례는 다음과 같습니다. 팀은 CI(지속적 통합)에 소프트웨어 자재 명세서(SBOM)를 연결합니다. 모든 빌드는 버전과 해시(hash)가 포함된 해결된 패키지(resolved packages) 목록을 생성합니다. 스캐너는 해당 목록을 CVE(공통 보안 취약점) 데이터베이스 및 알려진 악성코드 피드와 교차 참조합니다. 결과는 '그린 보드(Green board, 통과)'입니다. 이제 무엇이 배포되었는지 확인할 수 있으므로 모두가 보호받고 있다고 느낍니다. 그러다 코딩 에이전트가 작업 도중 npm install some-plausible-name을 작성하면, 리졸버(resolver)가 이를 가져오고, 설치 과정 중에 postinstall 스크립트가 실행됩니다. 그리고 SBOM은 8시간 전에 등록되었으며 아직 CVE가 없는 패키지를 충실하게 기록합니다. 스캔은 정직합니다. 스캔은 설계된 질문에 답했습니다. 하지만 에이전트가 그 이름을 제안했어야 했는지에 대해서는 질문받은 적이 없으며, 그 답을 기록할 필드조차 없습니다.
이것이 포스트의 핵심입니다. SBOM은 해결(resolution)의 기록입니다. 특정 이름을 신뢰하기로 한 결정은 출처(provenance)에 대한 판결입니다. 이 둘은 서로 다른 질문이며, 첫 번째 질문이 피해를 입히기 전에 두 번째 질문에 대한 답이 먼저 나와야 합니다. 왜냐하면 악성 설치의 피해는 종종 스캐너가 의존성 트리(tree)를 확인하기도 전인, 설치 과정 중의 postinstall 훅(hook)에서 실행되기 때문입니다.
해결(resolve) 후의 스캔이 볼 수 있는 것과 볼 수 없는 것
정확하게 말씀드리겠습니다. 여기서 SBOM을 비방하기는 쉽지만, 저는 그러고 싶지 않습니다. SBOM과 CVE 스캔의 조합은 본연의 목적에 충실합니다. lodash@4.17.20에 알려진 프로토타입 오염(prototype-pollution) 권고 사항이 있으니 버전을 올려야 한다는 사실을 알려줍니다. 또한 해결된 의존성 중 어떤 것이 공개된 악성코드 지표와 일치하는지도 알려줍니다. 그것은 실질적인 보호 범위이며, 여러분은 이를 계속 유지해야 합니다.
그것이 할 수 없는 일은 이름이 새롭고 검증되지 않았다는 이유로 경고를 보내는 것입니다. 왜냐하면 "새롭고 검증되지 않음"은 CVE (Common Vulnerabilities and Exposures)가 아니기 때문입니다. 완전히 새로운 타이포스쿼팅 (Typosquatting) 공격은 구조적으로 깨끗한 스캔 결과를 보여줍니다. 지난주에는 존재하지도 않았던 이름에 대해 제출된 보안 권고(Advisory)가 없기 때문입니다. 스캔은 알려진 악성 (known-bad) 정보를 기준으로 작동합니다. 반면 공격은 설계상 알려지지 않은 악성 (unknown-bad)입니다. 따라서 중요한 수치, 즉 여러분의 설치 후 스캔 (post-resolve scan)이 통과시켜 버릴 제안된 이름의 개수는 현재 여러분이 볼 수 없는 수치입니다. 이 도구는 설치 전, 이름 자체로부터 정확히 그 수치를 계산합니다.
60초 안에 실행하기
키(Key)도 필요 없습니다. 네트워크도 필요 없습니다. Python 외에 설치할 것도 없습니다. 파일 두 개(supply_chain_gate.py와 함께 제공되는 popular_npm.json 베이스라인)를 저장하고, 매니페스트 (manifest)를 저장한 뒤, 명령어 하나만 실행하면 됩니다.
매니페스트는 세 부분으로 구성된 하나의 JSON 객체입니다:
proposed: 에이전트가 추가하고자 하는 패키지들로, 각각name과version을 가집니다. 이는package.json의added-dependencies차이(diff)와 동일합니다.known_good: 여러분이 이미 검증한 이름들의 검증된 스냅샷 (vouched snapshot)입니다. 데이터로서 읽히는 락파일 (lockfile)의 역할이라고 생각하면 됩니다.npmrc:.npmrc파일의 라인들로, 데이터로서 읽힙니다. 레지스트리 리다이렉트 (registry redirect)가 숨어드는 곳입니다.
popular 베이스라인은 도구와 함께 제공되는 잘 알려진 npm 이름들의 작은 번들 리스트입니다. 이는 네트워크 조회(network lookup)가 아닌 로컬 데이터 파일입니다. 게이트(gate)는 이를 사용하여 타이포스쿼팅 (typosquats)에 대한 편집 거리 (edit-distance)를 측정하고, 정확한 일치를 통해 확립된 이름들을 검증합니다.
다음은 도구 전체 코드입니다. 단일 파일이며 표준 라이브러리만 사용합니다.
#!/usr/bin/env python3
"""
supply_chain_gate.py -- AI가 사용하는 패키지를 위한 설치 전 (PRE-INSTALL) 출처 검증 게이트
...
베이스라인: 깨끗한 제안 세트
정상적인 작업을 수행하는 에이전트로 시작해 보겠습니다. 에이전트는 express, lodash, react 세 가지 패키지를 제안했습니다. 세 가지 모두 검증된 스냅샷 또는 popular 베이스라인과 정확히 일치하며, 모두 특정 버전으로 고정되어 있습니다. .npmrc는 https를 통한 registry.npmjs.org를 가리킵니다.
$ python3 supply_chain_gate.py fixtures/clean_manifest.json
SUPPLY-CHAIN GATE REPORT (pre-install, provenance)
baseline: 5 vouched (known_good), 277 popular names (bundled)
...
Exit 0. 볼 것이 없습니다. 이것이 바로 핵심입니다. 모든 이름이 보증(vouched)되고 레지스트리(registry)가 신뢰될 때, 게이트(gate)는 조용히 유지됩니다. 여기서 express를 주목하세요. 번들링된 베이스라인(baseline)과 정확히 일치하므로 OK_POPULAR이며, 허용(ALLOW)됩니다. 이 점을 기억해 두세요.
사례를 입증하는 데모
동일한 도구, 동일한 277개 이름의 베이스라인, 동일한 형태의 매니페스트(manifest)를 사용합니다. 이번에는 에이전트(agent)가 유도되었습니다. 에이전트는 expresss (s가 하나 더 붙었으며, ^4.17.0으로 고정되지 않음), left-pad-utils (스냅샷에도 없고, 인기 목록에도 없으며, 유사한 이름도 없는 명칭), 그리고 lodash (lodash는 괜찮으며, 고정되어 있음)를 제안했습니다. 그리고 .npmrc는 일반 http를 통한 원시 IP 주소로 재작성되었습니다.
$ python3 supply_chain_gate.py fixtures/attack_manifest.json
SUPPLY-CHAIN GATE REPORT (pre-install, provenance)
baseline: 5 vouched (known_good), 277 popular names (bundled)
...
Exit 1. 두 개의 거부(DENY) 라인을 읽어보세요. expresss는 타이포스쿼팅(typosquat)으로 표시되었습니다. 이는 깨끗한 실행(clean run)에서 통과되었던 정확한 이름인 express와 편집 거리(edit-distance)가 1입니다. left-pad-utils는 환각(hallucination) 후보로 표시되었습니다. 베이스라인에서 이를 보증하는 것이 없으며, 편집 거리 2 이내에 유사한 이름도 존재하지 않습니다. 그리고 .npmrc 라인은 단 하나의 문자열임에도 리다이렉트(redirect) 체크와 일반 http(plain-http) 체크를 모두 통과하여, 모든 인스...
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기