2026년, npm 대신 pnpm 11을 선택해야 하는 이유

|Platform Decision|11분 읽기

npm을 아직 쓰고 계신가요?

최근 회사 프로젝트들을 정리하다가 문득 깨달은 게 있습니다. 아직도 많은 팀이 npm을 기본 패키지 관리자로 쓰고 있더라고요. 사실 이해됩니다. npm은 Node.js에 기본으로 들어있고, 튜토리얼마다 npm install을 치라고 하고, 별 문제 없이 잘 돌아가니까요.

하지만 지난 4월 pnpm 11이 나온 이후로는 상황이 좀 달라졌습니다. 이제는 단순히 "pnpm이 더 빨라서" 수준이 아니라 보안 측면에서도 무시하기 어려운 차이가 생겼거든요.

npm이 갖고 있는 근본적인 문제

패키지 관리자를 바꾸는 일은 우선순위가 낮아 보입니다. "당장 급한 건 아니니까" 하면서 스프린트에서 밀려나죠. 하지만 공급망 공격이 점점 정교해지는 2026년에는 다른 접근이 필요합니다.

일반적인 공격 시나리오를 보면 이렇습니다:

  1. 인기 패키지의 관리자 토큰이 유출됨
  2. 공격자가 악성 코드가 포함된 패치 버전을 배포
  3. CI/CD 파이프라인이 ^1.2.0 같은 semver 범위로 자동 업데이트
  4. postinstall 스크립트가 실행되면서 환경변수와 비밀 정보 탈취

최근에는 Mini Shai-Hulud 같은 공격도 나타났는데, npm, PyPI, Packagist를 동시에 감염시키면서 Bun 런타임까지 다운로드해서 난독화된 자격증명 탈취 프로그램을 실행하더라고요.

npm은 이런 공격에 대해 기본적인 보호 장치가 없습니다. 패키지가 올라오면 바로 설치할 수 있고, 어디서든 하위 의존성을 가져올 수 있고, 모든 postinstall 스크립트가 기본으로 실행됩니다.

pnpm 11에서 달라진 보안 기능들

24시간 대기 정책 (minimumReleaseAge)

pnpm 11에서 가장 중요한 변화입니다. 기본적으로 24시간 이내에 배포된 패키지는 설치되지 않습니다.

# pnpm-workspace.yaml
pnpm:
  minimumReleaseAge: 1440  # 분 단위 = 1일

왜 이게 중요한가요? 대부분의 공급망 공격은 속도에 의존하기 때문입니다. 악성 버전을 배포하고 레지스트리에서 삭제되기 전까지 몇 시간밖에 안 되거든요. 24시간의 쿨다운 기간이 있으면 Socket.dev나 Snyk 같은 보안 도구들이 악성 패키지를 미리 탐지할 수 있습니다.

급하게 특정 패키지가 필요하면 예외 처리도 가능합니다:

pnpm:
  minimumReleaseAge: 1440
  minimumReleaseAgeExclude:
    - "@types/*"
    - "my-trusted-package"

외부 의존성 차단 (blockExoticSubdeps)

npm 레지스트리의 패키지들은 하위 의존성을 GitHub 저장소나 tarball URL에서 가져올 수 있습니다. 이런 "특이한" 소스들은 표준 레지스트리 감사를 우회하는 경로가 되죠.

pnpm 11에서는 blockExoticSubdeps: true가 기본값입니다. 전이 의존성은 반드시 구성된 레지스트리에서만 가져올 수 있고, 하위 의존성이 외부 URL에서 뭔가를 가져오려고 하면 설치가 중단됩니다.

빌드 스크립트 제어 (allowBuilds)

postinstall 같은 라이프사이클 스크립트는 악성코드가 실행되는 주요 경로입니다. pnpm 11에서는 어떤 패키지가 빌드 스크립트를 실행할 수 있는지 명시적으로 정의할 수 있습니다.

pnpm:
  allowBuilds:
    "electron": true
    "esbuild": true
    "core-js": false

기본적으로 모든 스크립트는 차단되고, 신뢰하는 패키지만 허용 목록에 추가하는 방식입니다. 더 이상 "패키지에 악성코드가 없기를" 바라는 게 아니라, 내 컴퓨터에서 코드를 실행할 수 있는 패키지를 적극적으로 결정하게 되죠.

신뢰 정책 (trustPolicy)

pnpm:
  trustPolicy: no-downgrade

이전 버전에 비해 신뢰도가 떨어진 패키지(예: 서명이 없어진 최신 릴리스)의 설치를 거부합니다. 관리자 계정이 해킹당한 경우, 공격자들은 보통 기존과 같은 서명 절차를 거치지 않고 새 버전을 배포하거든요. 이런 이상 징후를 자동으로 감지해줍니다.

보안 외에도 개선된 점들

네이티브 레지스트리 명령어

이전까지는 pnpm publishpnpm login 같은 명령어들이 내부적으로 npm CLI를 호출했는데, pnpm 11에서는 모든 걸 네이티브로 처리합니다. npm 의존성이 필요 없어졌죠.

내장 SBOM 생성

pnpm sbom

CycloneDX 1.7이나 SPDX 2.3 JSON 형식으로 소프트웨어 자재 명세서(SBOM)를 생성할 수 있습니다. 규제가 까다로운 산업군이나 벤더 보안 검토가 필요한 팀에게는 꽤 유용한 기능입니다.

SQLite 기반 저장소

기존에는 패키지마다 JSON 파일 하나씩 만들어서 인덱스를 관리했는데, Store v11에서는 단일 SQLite 데이터베이스로 바뀌었습니다. 시스템 호출 횟수가 줄어들면서 설치 속도가 눈에 띄게 빨라졌어요. 캐시와 락 파일이 있는 상태에서는 약 2.3초 만에 설치가 완료됩니다.

npm에서 pnpm으로 마이그레이션하기

실제 마이그레이션은 생각보다 단순합니다.

1. pnpm 설치

npm install -g pnpm
# 또는 독립 설치
curl -fsSL https://get.pnpm.io/install.sh | sh -

2. 기존 프로젝트 가져오기

pnpm import

package-lock.jsonpnpm-lock.yaml로 변환됩니다. package.json은 그대로 유지되고요.

3. 의존성 설치

pnpm install

4. 보안 설정 구성

# pnpm-workspace.yaml
pnpm:
  minimumReleaseAge: 1440
  blockExoticSubdeps: true
  trustPolicy: no-downgrade
  allowBuilds:
    "electron": true
    "esbuild": true
    # 빌드 스크립트가 필요한 패키지들 추가

주의사항

pnpm은 기본적으로 더 엄격한 모듈 해석을 사용합니다. package.json에 선언하지 않은 패키지에 접근하는 숨겨진 의존성이 있다면 에러가 날 수 있어요. 이건 사실 좋은 기능인데, 숨겨진 의존성을 명시적으로 드러내주거든요. 코드베이스에 몇 가지 작은 수정이 필요할 수는 있습니다.

간단한 비교표

기능 npm pnpm 11
디스크 사용량 프로젝트마다 복제 하드링크 공유 저장소
설치 속도 기준선 더 빠름 (SQLite 저장소)
24시간 대기 정책 없음 기본 활성화
외부 의존성 차단 없음 기본 활성화
빌드 스크립트 제어 기본 모두 실행 명시적 허용 목록
신뢰 정책 없음 no-downgrade 옵션
SBOM 생성 없음 내장

앞으로의 방향: pnpm 12

pnpm 12에서는 Pacquet이라는 Rust 기반 설치 엔진이 도입될 예정입니다. 초기 벤치마크를 보면 웜 설치는 2.3초에서 1초 미만으로, 콜드 설치는 4.7초에서 약 3.1초로 단축된다고 하네요. npm과의 성능 격차는 더욱 벌어질 것 같습니다.

마치며

개인 프로젝트나 CI/CD가 없는 소규모 앱이라면 당장 급할 건 없습니다. 하지만 프로덕션 인프라를 관리하거나 자동화된 배포 파이프라인을 운영한다면, pnpm 11의 보안 기능들은 선택이 아니라 필수에 가깝다고 생각해요.

마이그레이션 비용은 낮고, 유지보수 부담도 거의 없으면서, 보안 수준은 확실히 올라갑니다. 몇 년 전에 이런 기능들이 있었다면 여러 보안 사고를 예방할 수 있었을 텐데 하는 아쉬움이 드는 건 저뿐일까요.

#pnpm#npm#패키지관리자#보안#Node.js