1P by GN⁺ 4시간전 | ★ favorite | 댓글 1개
  • 2026-05-11 19:20~19:26 UTC에 공격자가 42개 @tanstack/ npm 패키지에 걸쳐 악성 버전 84개를 게시함
  • 공격 체인은 pull_request_target “Pwn Request”, GitHub Actions 캐시 오염, runner 메모리의 OIDC 토큰 추출을 결합함
  • npm 토큰과 publish 워크플로는 탈취·손상되지 않았고, 악성코드가 OIDC trusted publisher 권한으로 registry에 직접 POST함
  • 영향 버전 설치 시 AWS, GCP, Kubernetes, Vault, GitHub, npm, SSH 자격 증명이 노출됐을 수 있어 교체가 필요함
  • 모든 영향 버전은 deprecated 처리됐고 npm security와 tarball 제거를 진행했으며, 추적 이슈와 GitHub Security Advisory가 공개됨

사건 개요

  • 2026-05-11 19:20~19:26 UTC 사이 공격자가 42개 @tanstack/* npm 패키지에 걸쳐 악성 버전 84개를 게시함
  • 공격 체인은 pull_request_target “Pwn Request” 패턴, fork↔base 신뢰 경계를 넘는 GitHub Actions 캐시 오염, GitHub Actions runner 프로세스 메모리에서의 OIDC 토큰 추출을 결합함
  • npm 토큰은 탈취되지 않았고, npm publish 워크플로 자체도 손상되지 않은 것으로 확인됨
  • 악성 버전은 외부 연구자 ashishkurmistepsecurity에서 공개적으로 20분 안에 탐지함
  • 모든 영향 버전은 deprecated 처리됐고, npm security와 함께 레지스트리에서 tarball 제거를 진행함
  • 2026-05-11에 영향 버전을 설치한 사용자는 설치 호스트에서 접근 가능한 AWS, GCP, Kubernetes, Vault, GitHub, npm, SSH 자격 증명을 교체해야 함
  • 추적 이슈는 TanStack/router#7383, GitHub Security Advisory는 GHSA-g7cv-rxg3-hmpx

영향 범위

  • 영향받은 패키지

    • 영향 범위는 42개 패키지와 84개 버전이며, 패키지당 2개 버전이 약 6분 간격으로 게시됨
    • 전체 목록은 추적 이슈에 포함됨
    • 확인된 비영향 제품군은 @tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual*, @tanstack/store, @tanstack/start 메타 패키지임
    • @tanstack/start-*는 확인된 비영향 목록에 포함되지 않음
  • 악성코드 동작

    • 개발자 또는 CI 환경이 영향 버전에 대해 npm install, pnpm install, yarn install을 실행하면 npm이 악성 optionalDependencies 항목을 해석하고 fork network의 orphan payload commit을 가져옴
    • 이후 prepare 라이프사이클 스크립트가 실행되며, 영향 tarball 안에 숨겨진 약 2.3MB 난독화 router_init.js가 동작함
    • 악성 스크립트는 AWS IMDS/Secrets Manager, GCP metadata, Kubernetes service-account token, Vault token, ~/.npmrc, GitHub token, gh CLI, .git-credentials, SSH private key 등 일반적인 위치에서 자격 증명을 수집함
    • 탈취 데이터는 Session/Oxen messenger file-upload network를 통해 유출되며, 대상은 filev2.getsession.org, seed{1,2,3}.getsession.org
    • 해당 네트워크는 종단 간 암호화되고 공격자 제어 C2가 없으므로, 네트워크 완화책은 IP/도메인 차단뿐임
    • 자기 전파 로직은 registry.npmjs.org/-/v1/search?text=maintainer:<user>로 피해자가 관리하는 다른 패키지를 열거한 뒤 같은 주입 방식으로 다시 게시함
    • payload가 npm install 라이프사이클 일부로 실행되므로, 2026-05-11에 영향 버전을 설치한 호스트는 잠재적으로 손상된 것으로 취급해야 함

타임라인

  • 공격 전: 캐시 오염 단계

    • 2026-05-10 17:16 UTC에 공격자가 TanStack/router fork인 github.com/zblgg/configuration을 만들고, fork 목록 검색을 피하려고 이름을 바꿈
    • 2026-05-10 23:29 UTC에 조작된 신원 claude <claude@users.noreply.github.com>으로 악성 커밋 65bf499d16a5e8d25ba95d69ec9790a6dd4a1f14가 fork에 작성됨
    • 해당 커밋은 약 30,000줄 번들 JS payload인 packages/history/vite_setup.mjs를 추가했고, push 이벤트의 CI를 억제하려고 커밋 메시지에 [skip ci]를 붙임
    • 2026-05-11 약 10:49 UTC에 zblgg가 TanStack/router main을 대상으로 “WIP: simplify history build” 제목의 PR #7378을 염
    • bundle-size.ymllabeler.yml은 모두 pull_request_target으로 PR에 대해 자동 실행됐고, pull_request_target은 첫 기여자 승인 게이트를 우회하므로 별도 승인이 필요 없었음
    • pull_request를 쓰는 pr.yml은 승인 대기 상태로 차단됐고 실행되지 않음
    • 2026-05-11 11:01~11:11 UTC에 zblgg가 PR head에 여러 차례 force-push해 추가 pull_request_target 실행을 유발함
    • 2026-05-11 11:11 UTC에 악성 커밋 65bf499d가 PR head에 올라갔고, bundle-size.ymlbenchmark-pr job이 refs/pull/7378/merge를 체크아웃한 뒤 pnpm installpnpm nx run @benchmarks/bundle-size:build를 실행하면서 vite_setup.mjs가 동작함
    • 2026-05-11 11:29 UTC에 Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11라는 1.1GB GitHub Actions 캐시가 TanStack/router에 저장됨
    • 이 캐시는 refs/heads/main scope에 저장됐고, 다음 main push에서 release.yml이 조회할 키와 맞도록 구성됨
    • 2026-05-11 11:31 UTC에 공격자가 PR을 당시 main HEAD인 b1c061af로 되돌려 visible PR을 0-file no-op처럼 만들었고, 같은 분에 PR을 닫고 branch를 삭제했지만 오염된 캐시는 남음
  • 발화: 게시 단계

    • 2026-05-11 19:15 UTC에 Manuel이 PR #7369를 merge하면서 main push가 발생했고, release.yml workflow run 25613093674가 19:15:44에 시작된 뒤 실패함
    • 2026-05-11 19:20:39 UTC에 npm registry가 @tanstack/history@1.161.9와 41개 sibling package의 publish를 수신함
    • 전체적으로 42개 패키지에 걸쳐 약 84개 버전이 게시됐지만, 이 정확한 초에 보이는 것은 약 절반이며 나머지는 두 번째 run에서 게시됨
    • publish 인증은 TanStack/router release.yml@refs/heads/main에 대한 OIDC trusted-publisher binding으로 이뤄졌지만, 테스트 실패로 건너뛴 workflow의 Publish Packages step에서 발생한 것은 아님
    • 실제 게시자는 테스트/정리 단계에서 실행된 malware였고, id-token: write 권한으로 OIDC 토큰을 mint한 뒤 registry.npmjs.org에 직접 POST함
    • 2026-05-11 19:20:47 UTC에 run 25613093674는 failure 상태로 완료됨
    • 2026-05-11 19:16 UTC에 Manuel이 PR #7382를 merge하면서 두 번째 main push가 발생했고, 19:16:22에 workflow run 25691781302가 시작됨
    • 두 번째 run도 같은 오염 캐시를 restore했고, 2026-05-11 19:26:14 UTC에 @tanstack/history@1.161.12 등 패키지당 두 번째 버전 세트가 같은 OIDC 메커니즘으로 게시됨
    • 2026-05-11 19:26:20 UTC에 run 25691781302도 failure 상태로 완료됨
  • 탐지와 대응

    • 2026-05-11 약 19:50 UTC에 외부 연구자 carlini가 악성 optionalDependencies fingerprint와 패키지 목록을 포함한 이슈 #7383을 염
    • 초기 목록은 42개 중 14개였고, 연구자는 npm security에도 직접 알림
    • 2026-05-11 약 20:00 UTC에 Manuel이 #7383에서 사고를 확인하고 대응을 시작함
    • 2026-05-11 약 20:10 UTC에 Manuel이 사용자 머신 손상 가능성에 대비해 다른 팀원의 GitHub push 권한을 제거함
    • 2026-05-11 약 20:30 UTC에 Tanner가 전체 IOC 목록과 registry-side tarball 제거 요청을 security@npmjs.com으로 보냈고, npm을 통해 정식 malware report를 제출함
    • 2026-05-11 약 21:00 UTC에 295개 @tanstack/* 패키지 전체 스캔으로 범위가 42개 패키지, 84개 버전으로 확인됨
    • Tanner가 84개 영향 패키지 전체에 대한 npm deprecation을 시작했고, @tan_stack과 maintainer들이 Twitter/X, LinkedIn, Bluesky에서 공개 알림을 진행함
    • 2026-05-11 21:30 UTC에 bundle-size.ymlpull_request_target 캐시 오염 벡터와 zblgg/configuration fork가 식별됨
    • 모든 TanStack/* GitHub repository의 캐시 항목이 API로 제거됨
    • hardening PR이 merge되어 bundle-size.yml이 재구성되고, repository_owner guard가 추가됐으며, third-party action ref가 SHA로 고정됨
    • 공식 GitHub Security Advisory가 게시됐고 CVE가 요청됨

근본 원인

  • 세 취약점의 결합

    • 공격에는 세 가지 취약점이 모두 필요했고, 어느 하나만으로는 충분하지 않았음
    • fork PR 코드가 base repository cache로 넘어가고, base repository cache가 release workflow runtime으로 넘어가며, release workflow runtime이 npm registry 쓰기 권한으로 이어지는 식으로 각 취약점이 서로의 신뢰 경계를 연결함
  • pull_request_target “Pwn Request” 패턴

    • bundle-size.yml은 fork PR에 대해 pull_request_target으로 실행됐고, 그 trigger context 안에서 fork의 PR merge ref를 checkout한 뒤 build를 실행함
    • 핵심 구조는 다음과 같음
    on:
      pull_request_target:
        paths: ['packages/**', 'benchmarks/**']
    
    jobs:
      benchmark-pr:
        steps:
          - uses: actions/checkout@v6.0.2
            with:
              ref: refs/pull/${{ github.event.pull_request.number }}/merge # fork's merged code
    
          - uses: TanStack/config/.github/setup@main # transitively calls actions/cache@v5
    
          - run: pnpm nx run @benchmarks/bundle-size:build # executes fork-controlled code
    
    • workflow 작성자는 comment-pr job과 benchmark-pr job을 분리해 신뢰 경계를 나누려 했고, YAML comment에는 benchmark-pr를 “untrusted with read-only permissions”로 유지하려는 의도가 적혀 있었음
    • 그러나 actions/cache@v5의 post-job save는 permissions:로 막히지 않으며, cache write는 workflow GITHUB_TOKEN이 아니라 runner 내부 token을 사용함
    • 따라서 permissions: contents: read 설정은 cache mutation을 막지 못함
    • cache scope는 repository 단위이고, base repository cache scope를 사용하는 pull_request_target run과 main push가 공유함
    • base repository cache scope에서 실행되는 PR은 나중에 main의 production workflow가 restore할 cache entry를 오염시킬 수 있음
  • GitHub Actions 캐시 오염

    • 악성 vite_setup.mjs는 정상 release.yml workflow가 계산하고 조회할 pnpm-store key에 맞춰 데이터를 쓰도록 설계됨
    • 대상 key는 Linux-pnpm-store-${hashFiles('**/pnpm-lock.yaml')} 형식임
    • benchmark-pr job 종료 시 actions/cache@v5 post-step이 오염된 pnpm store를 정확히 그 key로 저장함
    • 이후 main push에서 release.yml이 실행되자 Setup Tools step이 오염된 entry를 설계대로 restore함
    • 이 공격 유형은 2024년 Adnan Khan이 문서화한 GitHub Actions cache poisoning 계열이며, TanStack에만 국한된 버그가 아니라 의식적인 완화가 필요한 GitHub Actions 설계 이슈임
  • runner 메모리에서 OIDC 토큰 추출

    • release.yml은 npm OIDC trusted publishing에 필요해서 정당하게 id-token: write를 선언함
    • 오염된 pnpm store가 runner에 restore되면 공격자 제어 binary가 디스크에 존재하게 되고, build step에서 호출됨
    • 해당 binary는 /proc/*/cmdline로 GitHub Actions Runner.Worker 프로세스를 찾고, /proc/<pid>/maps/proc/<pid>/mem을 읽어 worker 메모리를 dump함
    • 이후 runner가 id-token: write 설정에서 lazy mint한 OIDC 토큰을 메모리에서 추출함
    • 추출한 token으로 registry.npmjs.org에 직접 POST 요청을 인증해, workflow의 Publish Packages step을 완전히 우회함
    • 이 메모리 추출 방식은 2025년 3월 tj-actions/changed-files compromise에 쓰인 방식과 같고, attribution comment가 포함된 동일 Python script가 사용됨
    • 공격자는 새로운 기법을 발명한 것이 아니라 공개 연구를 재조합함
  • 각 요소가 단독으로 충분하지 않은 이유

    • pull_request_target 자체는 label이나 comment 같은 신뢰된 작업에는 사용할 수 있음
    • 이미 손상된 dependency 내부에서의 cache poisoning만으로는 별도의 publish vehicle이 필요함
    • OIDC token extraction만으로는 runner에서의 기존 code execution이 필요함

탐지와 IOC

  • 탐지 경로

    • 탐지는 내부가 아니라 외부에서 이뤄짐
    • carlini가 publish 후 약 20분 만에 이슈 #7383을 열어 전체 기술 분석을 제공함
    • Tanner는 war room을 시작한 직후 Socket.dev에서 상황을 확인하는 전화를 받음
  • downstream maintainer와 보안 도구용 fingerprint

    • @tanstack/* 패키지 manifest에서 다음 optionalDependencies 항목이 핵심 IOC임
    "optionalDependencies": {
      "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
    }
    

교훈

  • 잘된 점

    • 외부 연구자들이 사고 후 약 20분 안에 탐지하고 전체 기술 세부사항과 함께 보고함
    • maintainer team이 여러 time zone에 걸쳐 즉시 조율함
    • 탐지 커뮤니티가 몇 시간 안에 명확한 공개 IOC 패턴을 확보함
  • 개선이 필요했던 점

    • 내부 alerting이 없었고, compromise 사실을 제3자로부터 알게 됨
    • 자체 publish monitoring이 필요하며, 이런 문제를 빠르게 탐지할 수 있는 생태계 보안 연구 기업들과 더 긴밀히 협력하고 feedback loop를 좁힐 계획임
    • pull_request_target workflow는 오래전부터 위험한 패턴으로 알려져 있었지만 audit되지 않았음
    • third-party action의 floating ref인 @v6.0.2, @main은 이번 사건과 별개로 상시 supply-chain risk를 만듦
    • npm의 “dependent가 있으면 unpublish 불가” 정책 때문에 거의 모든 영향 패키지에서 unpublish가 불가능했음
    • registry-side tarball 제거를 npm security에 의존해야 했고, 이로 인해 악성 tarball이 설치 가능한 상태로 남는 시간이 몇 시간 추가됨
    • npm scope의 7명 maintainer 목록은 동일 blast radius에 대해 7개의 별도 credential-theft target을 만든다는 의미가 됨
    • OIDC trusted-publisher binding에는 publish별 review가 없고, 한 번 설정되면 workflow 안의 어떤 code path라도 publish 가능한 token을 mint할 수 있음
    • 필요한 대안은 수동 review가 있는 단기 classic token으로 이동하거나, 예상치 못한 workflow step에서의 publish를 탐지하는 provenance-source-verification을 추가하는 것임
  • 운이 좋았던 점

    • 공격자가 테스트를 깨뜨리는 payload를 선택해 정상 publish step이 skip됐고, 더 깨끗해 보이는 tarball이 생성되지 않았음
    • 이 때문에 공격이 충분히 요란하게 드러나 빠르게 탐지됨
    • 더 조심스러운 공격자가 테스트를 깨뜨리지 않았다면 몇 시간 더 조용히 publish할 수 있었음
    • 공격자는 attribution comment가 포함된 공개 memory-dump script를 재사용했고, 새로운 코드를 작성하지 않아 IOC matching이 더 빨라짐

남은 질문

  • bundle-size.ymlSetup Tools step이 실제로 actions/cache@v5를 호출했는지 확인해야 함
  • PR #7378에 대한 pull_request_target run 중 하나의 post-job log를 읽어 검증해야 하며, 예시 run id는 25666610798
  • force-push로 사라지기 전 최초 PR head commit에 무엇이 있었는지 확인해야 하며, GitHub reflog에 남아 있을 수 있음
  • 악성 commit이 fork의 git object store에 들어간 방식이 직접 git push였는지, audit-log entry를 남길 GitHub web UI 생성이었는지 확인해야 함
  • voicproducoes가 실제 계정인지 sock puppet인지 활동 이력과 대조해야 함
  • 6개의 중복 linux-npm-store-* entry로 보이는 npm cache도 오염됐는지, 실제 사용됐는지 확인해야 함
  • 공격에 Nx Cloud가 필요했는지, GitHub Actions cache만으로도 작동했을지 확인해야 함
  • TanStack/router fork network 안에서 orphan payload commit을 포함한 다른 fork를 식별할 수 있는지 확인해야 함
  • 다른 fork가 해당 commit을 hosting하고 있다면 github:tanstack/router#79ac49ee... 접근성이 유지돼 cleanup이 더 어려워짐
  • router, query, table, form, virtual 등 다른 TanStack repo가 같은 bundle-size.yml 스타일 패턴을 사용하는지 audit이 필요함
  • publish window 동안 영향 버전을 실제로 다운로드한 사용자 수를 npm support에서 받아야 함
  • 7명 maintainer의 머신이 별도로 손상됐는지 확인해야 함
  • 악성 publish에는 maintainer npm token이 사용되지 않았지만, maintainer machine은 self-propagation logic의 2차 target일 수 있음

참고 자료

Hacker News 의견들
  • 토큰을 폐기할 때 조심해야 함. 페이로드가 dead-man's switch~/.local/bin/gh-token-monitor.sh에 설치하고, Linux에서는 systemd 사용자 서비스로, macOS에서는 LaunchAgent com.user.gh-token-monitor로 등록하는 것처럼 보임
    훔친 토큰으로 60초마다 api.github.com/user를 폴링하고, 토큰이 폐기되어 HTTP 40x가 나오면 rm -rf ~/를 실행함
    https://github.com/TanStack/router/issues/7383#issuecomment-...

    • 현실적으로 악성코드를 설치했다면 어차피 컴퓨터를 완전히 초기화해야 함
    • 놀라움. 상호확증파괴 같은 상황임
      앞으로 5년간 소프트웨어 세계는 정말 거칠어질 것 같고, 에어갭 시스템이 크게 중요해질 듯함
    • 원래 항상 백업을 설정해뒀어야 하지만, 이 사건 때문에 사람들이 백업을 갖추게 된다면 그나마 다행임
  • @mistralai/mistralai npm 패키지도 이 의 일부로 손상됨
    https://github.com/mistralai/client-ts/issues/217
    지금은 npm 레지스트리에서 내려간 상태임

  • 불행하지만, 이건 Trusted Publishing만으로는 CI에서 안전하게 배포하기에 충분하지 않다는 증거로 보임. CI 파이프라인 내부의 공격자나 훔친 저장소 관리자 권한이 있으면 쉽게 배포할 수 있음
    새 정보는 아니고, Trusted Publishing이 이를 보장하도록 설계된 것도 아니지만, 로컬 배포와 2단계 인증에서 Trusted Publishing으로 옮기면 CI 침해를 통한 이런 공격 경로가 생김. 로컬에서 작업할 때 npm publish를 막아주던 두 번째 요소가 사라지는 셈임
    현재 전개상 공격자는 CI/CD 파이프라인을 장악했고, npm publish에 두 번째 요소가 없었기 때문에 OIDC 토큰을 훔쳐 배포를 완료한 것처럼 보임. 흥미롭지만 별개로, 배포 작업 자체는 실패했는데 악성 커밋 안의 페이로드가 워크플로의 OIDC 토큰으로 스스로 배포할 수 있었던 듯함
    원하는 건 장기 토큰 없는 Trusted Publisher 모델을 유지하되, GitHub 바깥의 두 번째 요소가 남아 있는 CI 배포임. 즉 누군가 npm 쪽에서 2단계 인증으로 아티팩트를 실제 공개 상태로 승격해야 하는 단계적 배포가 필요함
    배포가 GitHub 신뢰 모델 안에서만 가능하다면 저장소 관리자 토큰을 털거나 파이프라인에 악성 코드를 넣은 사람은 누구나 배포를 쉽게 완료할 수 있음. GitHub 컨텍스트 밖의 진짜 두 번째 요소가 있으면 저장소를 망치거나 악성 코드를 심을 수는 있어도, 레지스트리용 두 번째 요소 없이는 배포하지 못함

    • 준인기 패키지 하나를 갖고 있는데, 여전히 로컬 배포와 2단계 인증을 쓰고 있음. Trusted Publishing은 너무 복잡해 보이고 계속 해킹당하는 것처럼 보여서, 우리가 안전하게 운용하기엔 너무 복잡한 게 아닌가 싶고 다시 설계부터 해야 할지도 모름
    • 여전히 Trusted Publishing은 큰 개선이라고 생각하지만, 릴리스를 진짜 공개 상태로 표시할 때 두 번째 요소를 요구하는 아이디어는 좋음. 그러면 이런 CI 웜을 실행하기가 매우 어려워질 것임
    • YubiKey 같은 걸로 터치 서명을 하고 싶음. 클라우드가 대신 자격 증명을 관리하도록 믿는다는 발상 자체가 실수처럼 보임
    • astral 블로그에서 최근 Trusted Publishing을 쓰면서도 릴리스 게이트를 어떻게 두는지, 즉 릴리스 워크플로에 수동 승인을 넣는 방식을 보여줬음. 안타깝게도 NPM/PyPI/Rubygems의 Trusted Publishing 문서는 이 가능성을 언급조차 하지 않고, 기본값으로 제공하지도 않음
    • 이런 종류의 공급망 공격에 Trusted Publishing이 어떤 차이를 만든다고 사람들이 말하는 이유가 항상 이해되지 않았음
  • 사후 분석: https://tanstack.com/blog/npm-supply-chain-compromise-postmo...

    • TanStack의 사후 분석은 고맙지만, npm 생태계 전체 관점의 보안 이슈는 아직 진행 중인 우려가 맞지 않나 싶음
      TanStack 패키지를 가져오거나 포함했을 수 있는 하위 패키지들을 안전하다고 봐도 된다는 증거가 있는지 궁금함
  • postinstall 스크립트는 치명적임. 모두 pnpm을 써야 함
    FORK에 푸시된 “고아” 커밋이 npm 클라이언트에서 이런 일을 유발할 수 있다는 게 말이 안 됨. GitHub에도 책임이 크다고 봄. 악의적인 포크의 커밋이 GitHub의 공유 객체 저장소를 통해 정상 저장소와 구분되지 않는 URI로 접근 가능하다는 건 완전히 미친 구조임

    • 업데이트된 의존성으로 앱을 실행하면 어차피 그 코드가 실행됨. root냐 non-root냐도 중요하지 않고, 중요한 것들은 애플리케이션을 실행하는 사용자 권한으로 접근 가능함
    • 이게 어떻게 GitHub의 P0 장애가 아닌지 모르겠음. 설명할 수 있는 사람이 있나?
      처음 읽었을 때는 “fork”라는 말을 잘못 써서 사실 공식 저장소의 브랜치를 뜻하는 줄 알았음. 그게 진짜일 리 없다고 생각했는데, 세상에
  • https://tanstack.com/blog/npm-supply-chain-compromise-postmo...
    TanStack에서 이 사건에 대한 사후 분석을 방금 공개했음

  • npm 환경을 안전하게 설정하라는 알림임
    https://gajus.com/blog/3-pnpm-settings-to-protect-yourself-f...
    설정 몇 개만으로 큰 문제를 줄일 수 있음

    • npm v11 이상에서는 allow-git=none도 있음: https://github.blog/changelog/2026-02-18-npm-bulk-trusted-pu...
    • 이 글은 npm의 최소 릴리스 나이에 대해 틀린 것 아닌가 싶음. 1) 설정 이름은 min-release-age임. 2) 무슨 이유인지 분 단위가 아니라 일 단위로 만들었음: https://docs.npmjs.com/cli/v11/using-npm/config#min-release-...
      의존성 관리자 공간이 완전히 불필요하게 파편화됐다고 봄
    • 최소 나이를 7일로 설정하면 npm 공급망 취약점을 “절대” 겪지 않는다는 주장은 과함
    • 모든 의존성을 반드시 고정해야 함
      패키지 버전 의존성이 ^1.0.0처럼 되어 있거나 심지어 "*"라면 더 읽지 말고 즉시 안전한 버전으로 고정해야 함
  • Claude로 급하게 만들어서 확산을 줄이는 데 도움을 주려 했음. 당연히 직접 검증해야 하지만, 언급된 손상 패키지가 머신에 있는지 스캔해줌: https://github.com/PaulSinghDev/tanstack-shai-hulud-fix

  • 이제는 모두가 각 프로젝트를 개별 VM에서 실행해야 하는 단계에 온 것 같음
    최근의 로컬 권한 상승 취약점을 보면 Docker만으로는 절대 충분하지 않음. 애초에 컨테이너는 주된 보안 경계로 설계된 것도 아님

    • Devcontainers가 이런 “격리된 개발 환경” 개념의 가장 잘 알려진 형태지만, 완전한 VM은 아니고 이번 일에서 완전히 보호해주지도 못함. GitHub 자격 증명이 컨테이너 안으로 자동으로 들어오기 때문임
      컨테이너 안에서 접근해야 하는 다른 클라우드 서비스가 있다면, 이 자격 증명 탈취기가 그것도 가져갈 것임. 그래도 피해 반경은 줄여주니 최소한 개선은 됨
    • QubesOS가 올바른 방향을 잡았음. 루트에 여러 VM을 두고 겹겹의 보안 계층을 원하게 됨
    • 컨테이너를 꼭 쓰겠다면 컨테이너마다 VM을 하나씩 두는 방법도 있음. 임의의 Kubernetes 서비스가 아니라 전부 VM에서 돌린 덕분에 최근 몇 주는 꽤 편안했음
    • 다행히 C와 C++처럼 더 안전한 언어 생태계를 쓰는 프로젝트는 이런 문제에서 벗어나 있음 :-)
  • 와, 또 다른 거대 패키지임. Axios와 LiteLLM이 손상된 뒤 올렸던 공익 알림을 다시 올림. 수명주기 스크립트 관련 내용도 적용됨
    npm/bun/pnpm/uv는 이제 모두 패키지의 최소 릴리스 나이 설정을 지원함. ~/.npmrcignore-scripts=true도 넣어뒀고, 분석상 이것만으로도 취약점을 완화할 수 있었음. bun과 pnpm은 기본적으로 수명주기 스크립트를 실행하지 않음
    전역 설정으로 최소 릴리스 나이를 7일로 설정하는 방법은 다음과 같음
    ~/.config/uv/uv.toml
    exclude-newer = "7 days"
    ~/.npmrc
    min-release-age=7 # days
    ignore-scripts=true
    ~/Library/Preferences/pnpm/rc
    minimum-release-age=10080 # minutes
    ~/.bunfig.toml
    [install]
    minimumReleaseAge = 604800 # seconds
    전역 설정을 덮어써야 한다면 CLI 플래그를 쓰면 됨
    npm install --min-release-age 0
    pnpm add --minimum-release-age 0
    uv add --exclude-newer "0 days"
    bun add --minimum-release-age 0
    한 가지 더 덧붙이면, 의존성 대기 시간을 대규모로 도입하면 취약점 발견이 늦어지거나, 의존성 대기 시간이 일종의 무임승차라는 우려가 있는 듯한데 동의하지 않음. 의존성 대기 시간으로 교환하는 것은 시간 선호이고, 항상 나보다 시간 선호가 높은 사람들은 존재함
    0: https://news.ycombinator.com/item?id=47582220
    1: https://news.ycombinator.com/item?id=47513932

    • 동의함. 지난 두 번의 파도가 오기 전인 3월에 이 설정들을 켜둬서 다행임. 추가로 저장소에 lockfile을 커밋해두고 새 의존성을 추가할 때 주의해야 함
      예기치 않은 변경을 피하려면 pnpm install --frozen-lockfile을 쓰면 됨. min-release-age를 설정하지 않았다면 간접 의존성을 통해서도 영향을 받은 패키지를 끌어올 수 있다는 점을 기억해야 함. 가능하면 패키지 관리자 버전도 고정하는 게 좋음