본문으로 건너뛰기
>_ITDITD웹 보안 플랫폼

보안 가이드

osv-scanner 실전 — pnpm, CVE 무시, 오프라인 모드, 자주 만나는 오류

도입 다음 날 생기는 질문들: pnpm-lock.yaml 지원, osv-scanner.toml로 CVE 하나 무시하기, 세 가지 오프라인 플래그와 그로 인한 오류, CI에서의 종료 코드 0/1/127/128, v1에서 v2로 이름이 바뀐 플래그.

게시 2026-09-04 업데이트 2026-10-06 최종 확인 2026-10-06 9분 읽기

대상: 이미 osv-scanner를 설치했고 막히는 부분이 생긴 사람. 설치가 아니라 도입 다음 날의 질문을 다룹니다. 모든 내용은 공식 문서를 따릅니다.

락파일을 지정하기

# a single lockfile
osv-scanner scan source -L pnpm-lock.yaml
 
# walk a directory
osv-scanner scan source -r ./my-project/

파일명으로 형식을 추론할 수 없을 때는 경로 앞에 사용할 파서를 붙입니다.

# parse an oddly named file as requirements.txt
osv-scanner scan source -L 'requirements.txt:/path/to/extra-requirements.txt'
 
# path containing a colon — lead with a bare ':' to keep filename inference
osv-scanner scan source -L ':/path/to/my:projects/package-lock.json'

매니페스트가 아니라 락파일인 이유

매니페스트(package.json 등)는 요청한 범위를 기록할 뿐, 실제로 설치된 것을 기록하지 않습니다. 락파일에는 해석된 정확한 버전이 담겨 있으므로 스캔이 실제 트리에 있는 것과 일치합니다. 범위를 보고 짐작하는 것은 훨씬 약한 근거입니다.

자신의 설정을 확인하는 방법

결과가 이상해 보이면 플래그를 더하기 전에 다음 다섯 가지를 확인하세요. 모두 자신의 프로젝트를 들여다보기만 합니다.

1

버전을 확인한다

osv-scanner --version을 실행하세요. 2025년에 나온 v2는 명령 형태와 여러 플래그 이름을 바꿨기 때문에, v1용으로 쓰인 안내는 그대로 하면 실패하는 경우가 많습니다. 현재 공식 문서는 v2를 기준으로 합니다.
2

무엇을 읽었는지 확인한다

실행 로그에는 읽은 파일과 각 파일에서 찾은 패키지 수가 표시됩니다. 기대한 락파일이 목록에 있는지, 개수가 수상하게 적지 않은지 확인하세요. 전체 목록은 --format json --all-packages로 볼 수 있습니다(--all-packages는 JSON 출력에서만 동작).
3

종료 코드를 확인한다

실행 직후 bash에서는 echo $?, PowerShell에서는 $LASTEXITCODE를 쓰세요. 의미는 아래 표에 있습니다.
4

설정이 실제로 적용됐는지 확인한다

osv-scanner.toml은 그 파일이 있는 디렉터리의 파일에만 적용되고, 하위 디렉터리로 이어지지 않습니다. -r로 스캔하는 모노레포라면 각 락파일 옆에 하나씩 두거나, --config로 파일 하나를 전체에 넘기세요.
5

오프라인 데이터베이스의 위치와 날짜를 확인한다

로컬 데이터베이스는 OSV_SCANNER_LOCAL_DB_CACHE_DIRECTORY가 설정돼 있으면 그곳에서, 아니면 사용자 캐시 디렉터리, 그다음 시스템 임시 디렉터리에서 읽습니다. 그 안의 파일은 osv-scanner/<ecosystem>/all.zip 형태입니다. 수정 날짜가 오래됐다면 판정도 그만큼 오래된 것입니다.

종료 코드와 CI에서의 처리

종료 코드공식 의미CI에서
0패키지를 찾았고, 일치하는 알려진 취약점 없음통과
1패키지를 찾았고, 취약점이 일치함실패(탐지 결과: 고치거나, 기한을 붙여 무시)
127일반 오류실패(스캔 자체가 고장 남)
128패키지를 찾지 못함(읽을 수 있는 것을 하나도 못 찾음)실패(경로를 고치거나 락파일을 추가)

문서는 1126을 결과에 관한 종료 코드로, 129255를 결과와 무관한 오류로 예약해 두고 있습니다. CI 처리를 0은 통과, 1은 탐지 결과, 그 외는 고장 난 스캔으로 세 갈래로 나누면 실패 원인을 훨씬 빨리 찾을 수 있습니다.

특정 취약점 무시하기

스캔하는 디렉터리에 osv-scanner.toml을 둡니다.

[[IgnoredVulns]]
id = "GHSA-xxxx-xxxx-xxxx"
ignoreUntil = 2026-12-31
reason = "Feature is not used and the code path is unreachable; re-evaluate at the December inventory"
  • id(필수): 취약점 식별자
  • ignoreUntil(선택): 이 날짜가 지나면 다시 보고됩니다
  • reason(선택): 그래도 쓰세요. 다음 사람을 위해서이고, 그 사람은 대개 자신입니다

--config=/path/to/config.toml은 디렉터리별 파일을 덮어쓰고 모든 곳에 적용됩니다. 취약점을 무시하면 그 별칭(alias)으로 취급되는 취약점도 함께 무시된다는 점에 주의하세요.

무시는 영구 삭제가 아니라 기한을 붙여서

ignoreUntil이 없는 항목은 영구히 사라지고 아무도 다시 보지 않는 항목이 됩니다. 본 사이트의 규칙은 모든 무시에 기한과 이유를 붙인다입니다. 날짜가 지나면 다시 나타나므로, 미뤄 둔 판단이 조용히 잊히지 않습니다. CI를 조용하게 하려고 넣은 기한 없는 무시야말로 반년 뒤 사고가 되는 것입니다.

오프라인 모드: 세 플래그, 세 가지 역할

혼란이 가장 많은 부분입니다.

플래그하는 일
--offline완전 오프라인. 미리 내려받은 로컬 데이터베이스로 판정하며, 데이터베이스를 업데이트하지 않고, 프로젝트나 의존성 정보를 어디에도 보내지 않습니다
--offline-vulnerabilities취약점 대조만 오프라인입니다. 다른 작업(전이 의존성 해석 등)은 여전히 네트워크를 쓸 수 있습니다
--download-offline-databases로컬 데이터베이스의 다운로드·업데이트를 허용합니다. 오프라인 플래그가 함께 지정됐을 때만 효과가 있습니다

--offline

완전 오프라인. 데이터베이스 업데이트 없음, 외부 전송 없음

--offline-vulnerabilities

대조만 로컬. 다른 작업은 네트워크를 쓸 수 있음

--download-offline-databases(단독으로는 무효)

위의 것 중 하나가 필요 → 단독으로 쓰면 "databases can only be downloaded when running in offline mode"

세 가지 오프라인 플래그. --download-offline-databases는 단독으로는 아무것도 하지 않으며, 그것이 사람들이 검색하는 오류의 원인입니다.

오류와 그 원인

  • databases can only be downloaded when running in offline mode → --download-offline-databases를 단독으로 넘겼습니다. 오프라인 플래그가 함께 필요합니다
  • 로컬 데이터베이스가 아직 없는 상태에서 --offline → 데이터베이스가 없다는 오류가 납니다. 첫 실행에서는 받아 와야 합니다
  • no package sources found → 그 경로에서 락파일이나 매니페스트로 인식된 것이 없습니다. -L로 지정하거나 -r로 순회하세요

추측하지 않고 고르기

  • 네트워크가 차단된 환경에서 실행 → --offline(첫 실행에는 --download-offline-databases도 함께)
  • 취약점 데이터만 로컬에 두면 될 때 → --offline-vulnerabilities
  • 데이터베이스 위치를 고정 → 환경 변수 OSV_SCANNER_LOCAL_DB_CACHE_DIRECTORY
  • 디버깅 → 먼저 플래그 없이 실행한 뒤 좁혀 가기

v1 글의 플래그가 동작하지 않을 때

검색 결과의 상당수는 v1용으로 쓰였습니다. 공식 마이그레이션 가이드에 나온 주요 변경은 다음과 같습니다.

v1v2
--experimental-offline--offline
--experimental-download-offline-databases--download-offline-databases
--experimental-call-analysis--call-analysis
--experimental-licenses 등--licenses로 통합
--docker / -D(컨테이너)osv-scanner scan image <image>
--json--format=json
--skip-git--include-git-root(의미가 반대)

osv-scanner <dir>는 지금도 osv-scanner scan source <dir>의 단축형으로 동작합니다. 스크립트에서는 scan source를 명시하면 다음에 읽는 사람에게 의도가 분명해집니다.

흔한 실수와 고치는 법

흔한 실수

  • CI가 "1 이외의 종료 코드"를 성공으로 처리한다
  • 실패를 없애려고 || true를 붙인다
  • 저장소 루트에 osv-scanner.toml 하나만 두고 하위 디렉터리까지 적용된다고 생각한다
  • package.json만 커밋하고 락파일은 없다
  • --offline에서도 pom.xml의 전이 의존성이 해석된다고 생각한다
  • ignoreUntil 없이 무시를 적는다

고치는 법

  • 0일 때만 통과시켜, 128(아무것도 읽지 못함)이 초록색이 되지 않게 한다
  • 대신 개별 탐지 결과를 기한 있는 무시로 처리한다
  • 디렉터리마다 파일을 두거나, --config로 하나를 전체에 적용한다
  • 락파일을 커밋한다(JavaScript라면 package-lock.json, pnpm-lock.yaml, yarn.lock, bun.lock)
  • 오프라인에서는 전이 의존성 해석이 꺼진다. 네트워크를 쓸 수 있다면 --offline-vulnerabilities를 쓴다
  • 항상 날짜와 이유를 붙이고, 그 날짜가 오면 다시 판단한다

Maven의 pom.xml은 락파일처럼 해석된 모든 버전을 나열하지 않습니다. osv-scanner는 기본적으로 deps.dev 리졸버(--data-source=native이면 Maven Central)로 전이 의존성 그래프를 계산합니다. 이 작업은 네트워크가 필요하며, --offline과 --no-resolve에서는 건너뜁니다. 완전히 차단된 환경에서 실행하면, 결과는 pom.xml에 직접 적힌 의존성만 다룬다고 보세요.

실제 사례: Log4Shell에 답하기 어려웠던 이유

2021년 12월 Log4Shell(CVE-2021-44228)이 드러났을 때, 많은 조직이 영향 여부를 빠르게 답하지 못했습니다. Log4j를 직접 추가한 적이 없었고, 프레임워크나 다른 라이브러리를 통해 들어와 있었기 때문입니다. CISA의 가이드는 조직에 설치된 소프트웨어를 빠짐없이 목록화해 영향받는 소프트웨어 목록과 대조하라고 요청했습니다.

osv-scanner에 적용하면 두 가지를 확인하는 것입니다.

  • 전이 의존성이 실제로 읽히고 있는가: 생태계에 락파일이 있다면 락파일, Maven이라면 해석이 켜진 실행(오프라인도 --no-resolve도 아닌)
  • 간접적으로 들어오는 라이브러리가 목록에 정말 들어 있는가: --format json --all-packages로 출력해 확인

공개 당일 "우리는 영향을 받는가?"에 몇 분 안에 답할 수 있는지는, 미리 그 목록을 제대로 갖춰 두었는지에 달려 있습니다.

주변 도구와의 차이

도구읽는 데이터잘하는 것
osv-scannerOSV.dev여러 생태계에 걸쳐, 락파일 단위, 오프라인 실행 가능
npm audit / pnpm auditnpm 권고 데이터베이스npm 프로젝트에서 바로 사용, 추가 설치 불필요
DependabotGitHub Advisory업데이트 PR을 연다 — 탐지만이 아니라 수정을 제안
Trivy여러 곳(OSV 포함)앱 의존성에 더해 컨테이너 이미지와 OS 패키지

딱 하나만 고르는 것이 실수입니다. 데이터베이스가 다르면 드러나는 것도 다릅니다. 본 사이트는 pnpm audit을 매일 자동 점검으로 돌리고, 락파일을 가로지르는 점검에 osv-scanner를 씁니다.

본 사이트의 견해: 고정한 버전이 낡았다는 것은 스캐너가 알려 주지 않는다

실제 운영에서만 드러나는 문제가 하나 있습니다. overrides로 전이 의존성을 정확한 버전에 고정하면, 그 패키지는 패치 업데이트를 받지 않게 됩니다. 그리고 스캐너는 알려진 취약점이 있을 때만 알려 줍니다. 그래서 "고정해 둔 버전이 이제 오래됐다"는 상태는 아무도 보고하지 않습니다. 본 사이트는 이 문제를 세 번 겪은 뒤, 모든 고정 버전을 같은 메이저 계열의 최신 패치와 비교하는 자체 일일 점검을 추가했습니다. 스캐너는 오늘의 알려진 취약점을 보여 줍니다. 나중에 노출될 가능성이 높은 상태는 다른 장치가 필요합니다.

출처

다음으로 읽기

FAQ

Qosv-scanner는 pnpm-lock.yaml을 지원하나요?
A

지원합니다. npm, yarn, pnpm의 락파일 모두 지원됩니다. `osv-scanner scan source -L pnpm-lock.yaml`로 직접 지정하거나, `-r`로 디렉터리를 순회하세요.

QCVE 하나만 무시하려면 어떻게 하나요?
A

스캔하는 디렉터리에 `osv-scanner.toml`을 두고 `id`를 적은 `[[IgnoredVulns]]` 항목을 추가하세요. `ignoreUntil`(기한)과 `reason`도 지정할 수 있어서, 예외가 영원히 사라지는 대신 언제·왜 무시했는지 기록됩니다. `--config=/path/to/config.toml`을 넘기면 디렉터리별 파일을 덮어쓰고 전체에 적용됩니다.

Q'databases can only be downloaded when running in offline mode' 오류는 왜 나오나요?
A

`--download-offline-databases`는 오프라인 플래그가 함께 지정됐을 때만 효과가 있고, 단독으로는 쓸 수 없기 때문입니다. 실행 중에 로컬 데이터베이스를 받아 오고 싶다면 오프라인 플래그와 함께 지정하세요.

Qnpm audit과 Dependabot이 이미 있는데 osv-scanner가 필요한가요?
A

읽는 데이터베이스와 다루는 범위가 다릅니다. npm audit은 npm 권고 데이터베이스를 쓰며 npm 전용이고, Dependabot은 주로 GitHub에서 업데이트를 제안합니다. osv-scanner는 여러 생태계(npm, PyPI, Go, Maven 등)에 걸쳐 OSV.dev를 읽고, 락파일 단위로 동작하며, 완전히 오프라인으로도 실행할 수 있습니다. 한쪽이 놓친 것을 다른 쪽이 잡는 일은 흔하므로, 하나를 고르기보다 서로 다른 역할로 다루세요.

QCI에서 osv-scanner의 종료 코드는 어떻게 다뤄야 하나요?
A

공식 문서에 따르면 0은 패키지를 찾았고 알려진 취약점과 일치한 것이 없음, 1은 취약점을 찾음, 127은 일반 오류, 128은 패키지를 하나도 찾지 못함을 뜻합니다. 0만 성공으로 다루세요. '1 이외는 통과' 같은 규칙은 아무것도 스캔하지 않은 128을 초록색 체크로 바꿔 버립니다.

Q예전 글에 나오는 --experimental-offline이나 --docker가 왜 안 되나요?
A

v2에서 이름이 바뀌거나 옮겨졌기 때문입니다. 공식 마이그레이션 가이드에 따르면 --experimental-offline은 --offline으로, --experimental-download-offline-databases는 --download-offline-databases로, --docker는 `osv-scanner scan image` 명령으로, --json은 `--format=json`으로 바뀌었습니다. 먼저 `osv-scanner --version`으로 메이저 버전을 확인하세요.