FEInterview Prep

Velog

NPM 보안 모범 사례

npm 공급망 공격은 늘었고, 도구는 늘 그 자리에 있었다. 핵심은 `.npmrc` 한 파일에 다섯 줄 만 정확히 적는 것이다.

2025-10-31·8분 읽기
빌드/도구Node.js아키텍처
원문 보기 ↗

핵심 요약

이 글은 bodadotsh/npm-security-best-practices 를 기반으로 14개 항목을 정리하지만, 실무 핵심은 개발자용 6개 항목이다. .npmrc 한 파일에 다음을 적어두는 것이 출발점이다.

ignore-scripts=true
provenance=true
save-exact=true
save-prefix=''

주요 관문 6개를 표로:

#관문명령/설정효과
1버전 고정npm install --save-exact / save-exact=truesemver 범위로 인한 자동 업그레이드 차단
2잠금 파일npm ci, --frozen-lockfile환경 간 동일성 보장
3라이프사이클 차단ignore-scripts=truepostinstall 류 악성 스크립트 무력화
4최소 릴리스 기간pnpm: minimumReleaseAge, yarn: npmMinimalAgeGate갓 배포된 악성 버전 격리
5권한 모델node --permission임의 코드 실행 시 자원 접근 제한
6외부 의존 줄이기내장 fetch, node:test, node --watch공격 표면 자체 축소

메인테이너 측은 2FA(WebAuthn 권장), 세분화된 토큰, OIDC 신뢰 게시, files 화이트리스트 가 핵심.

npm 보안은 '스캐너 도입' 이 아니라 '설치 시점의 관문 5개' 로 본다. 버전 고정 → 잠금 파일 → 라이프사이클 차단 → 최소 릴리스 기간 → 외부 의존 줄이기. 이 다섯 관문이 공격의 평균 노출 시간(수 시간) 보다 길게 작용하면 대부분의 공급망 공격은 무력화된다.

npm 생태계의 공격 빈도 는 면접 단골이다.

  • event-stream (2018), ua-parser-js (2021), axios (2026/3) — 모두 메인테이너 토큰 탈취
  • Shai-Hulud 같은 worm 은 postinstall 스크립트로 자격 증명 탈취
  • left-pad 사고처럼 제거 만으로도 빌드가 멎음

좋은 답변은 도구 이름(npm audit, snyk)을 나열하는 게 아니라, "왜 이 다섯 관문이 그 순서인가" 를 설명하는 것이다. 이 글은 그 다섯을 한 묶음으로 정리해 둔 가장 컴팩트한 자료다.

학습 포인트

면접 답변으로 연결할 학습 포인트입니다.

다섯 관문의 *순서* 가 곧 비용 곡선

관문은 도입 비용이 다르다 — 무서운 게 아니라 순서대로 켜는 게 정답이다.

0원 비용:  save-exact + lockfile     → 즉시
낮은 비용: ignore-scripts             → 빌드 깨질 때만 화이트리스트
중간 비용: minimumReleaseAge          → 의존 최신화 워크플로 필요
높은 비용: node --permission          → 코드 변경 동반

첫 두 줄은 .npmrc 만 고치면 끝나고, 효과는 공격의 90% 가 들어오는 자동 업그레이드 경로를 막는다. 가장 큰 ROI 다.

.npmrcsave-exactlockfilerollout
자주 하는 오해

"보안 == 비싼 도구" 라고 착각하는 것. 실제로는 0원 짜리 줄 두 개가 가장 큰 효과를 낸다.

라이프사이클 스크립트 차단의 비대칭 효과

Shai-Hulud worm 같은 공격은 postinstall 한 줄로 자격 증명을 빼간다. 차단 비용 vs 효과의 비대칭이 매우 크다.

npm config set ignore-scripts true --global
yarn config set enableScripts false
# bun, deno, pnpm: 기본적으로 비활성화

CI 에는 더 강하게 — npm ci --omit=dev --ignore-scripts. 빌드가 정말로 postinstall 을 필요로 하는 소수 패키지 만 화이트리스트(예: pnpm onlyBuiltDependencies)로 풀면 된다. 효과/비용 비가 가장 좋은 방어다.

postinstallpreinstallignore-scriptsallowlist
자주 하는 오해

"라이프사이클 끄면 빌드 다 깨짐" 이라고 미리 포기하는 것. 실제로는 0~3개 패키지만 화이트리스트하면 충분한 경우가 대부분이다.

최소 릴리스 기간 — 시간으로 거른다

공격자는 토큰 탈취 후 수 시간 내 발각 전에 끝내야 한다(axios 4h, ua-parser-js 4h). "새 버전을 N일 격리" 는 이 노출 창을 그대로 이긴다.

# pnpm — 분 단위 (7일)
minimumReleaseAge: 10080

# yarn 4 — duration string
npmMinimalAgeGate: "7d"

# Renovate / Dependabot — 보안 패치는 우회
cooldown:
  default-days: 7
  semver-patch-days: 3

주의: 잠금 파일을 완전 고정 하는 팀에는 효과가 작다 — 관문은 업데이트 시점 에만 의미가 있다.

minimumReleaseAgeexposure windowRenovateDependabot
자주 하는 오해

스캐너 도입을 우선시하는 것. 시간 격리는 스캐너로 못 잡는 제로데이 까지 시간으로 막는 패시브 방어다.

읽는 순서

  1. 1이론

    GitHub bodadotsh/npm-security-best-practices README 를 정독하고, 14개 항목 중 우리 팀이 이미 켠 것/아직 안 켠 것 표로 만드세요.

  2. 2구현

    프로젝트에 .npmrc 4줄(save-exact, ignore-scripts, save-prefix='', provenance) 을 추가하고 npm ci --ignore-scripts 로 CI 가 그대로 도는지 검증. 깨지는 패키지를 화이트리스트 로 푸세요.

  3. 3실무

    Renovate 또는 Dependabot 에 cooldown 7일을 적용하되 보안 업데이트는 0일 우회. PR 한두 개로 실제 동작 확인.

  4. 4설명

    팀에 5분 발표 — '5개 관문(고정/잠금/스크립트/시간/권한) + 운영 비용'. 각 관문이 막는 대표 공격 사례 를 한 줄씩.

면접 연결 질문

easy공급망 공격을 가장 적은 비용으로 줄이는 *.npmrc 한 줄* 을 고르고 그 이유를 설명해보세요.
힌트

[감점 답변] 'audit 돌린다'. [좋은 답변] ignore-scripts=true. 비용 0, 효과 비대칭 — postinstall 류가 가장 흔한 자격 증명 탈취 경로이고, 빌드가 진짜 postinstall 을 필요로 하는 패키지는 손에 꼽는다. 화이트리스트로 풀면 끝.

medium잠금 파일(`package-lock.json`)이 있는데도 보안에 추가 관문을 두는 이유를 설명해보세요.
힌트

[감점 답변] '잠금이 있으면 충분하다'. [좋은 답변] 잠금은 이전에 본 버전 만 보호한다. 새 버전을 추가/업그레이드하는 순간 (npm install / Renovate PR) 다시 0일 패키지가 들어온다. 따라서 (1) save-exact 로 자동 업그레이드 차단, (2) minimumReleaseAge 로 신상 격리 가 추가로 필요하다. 잠금은 재현성 이지 방어 가 아니다.

hard내부 패키지가 많은 모노레포에서 `minimumReleaseAge` 를 도입할 때 충돌하는 운영 이슈와 해결책을 답해보세요.
힌트

[감점 답변] '되는 데까지 켠다'. [좋은 답변] 내부 패키지는 하루 안에 여러 번 게시 되므로 7일 게이트가 항상 막는다. 해결: scope allowlist (@org/* 는 minimumReleaseAge 0), 또는 사설 레지스트리에서 업스트림만 게이트 적용. CVE 패치는 cooldown 우회(Renovate osvVulnerabilityAlerts: true) — 보안 패치는 0일이어야 한다.

자기 점검

본인 레포의 `.npmrc` (또는 yarnrc/bunfig)를 보고 *지금 누락된* 보안 관문 두 가지를 고르고 도입 순서를 정해보세요.
save-exactignore-scriptslockfileminimumReleaseAge
자주 하는 오해

"전부 한 번에 켜자" — 실제로는 비용 낮은 것부터 1주 단위로 도입해야 빌드 회귀를 잡을 수 있다.

메인테이너로서 토큰을 보호하는 *2단 방어* 를 설명해보세요.
WebAuthntrusted publishingOIDCgranular token
자주 하는 오해

TOTP 만 켜면 안전하다는 가정. 실제로는 WebAuthn(보안 키) + OIDC 신뢰 게시 가 토큰 자체를 없앤다.

외부 의존을 *줄이기* 위한 후보 라이브러리 3개를 골라 내장 대체로 매핑해보세요.
fetchnode:testnode --watchnode --env-file
자주 하는 오해

유틸 라이브러리가 의존 트리를 폭발시키지 않는다는 인식. 작은 라이브러리도 transitive 가 길면 공격 표면을 키운다.