ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [npm][보안] stage reject 뒤 dist-tag rollback과 live tag recovery를 어떤 audit field로 따로 남기나
    기타개발지식/풀스택개발 2026. 8. 18. 09:12

    IT 리서치 노트

    [npm][보안] stage reject 뒤 dist-tag rollback과 live tag recovery를 어떤 audit field로 따로 남기나

    npm staged publishing을 운영하다 보면 reject 메모와 live registry 복구 메모가 섞이기 쉽다. 2026년 8월 17일 기준 npm 공식 문서를 다시 보면 trusted publisher는 stage-only permission으로 CI publish를 review 전 단계로 밀 수 있고, staged package tag는 immutable이며, dist-tag는 이미 공개된 install 포인터를 관리하는 별도 축이다. 이 글은 stage reject 뒤 dist-tag rollback과 live tag recovery를 어떤 audit field로 따로 남겨야 release incident 복기가 쉬워지는지 정리한다.

    1. 개요

    결론부터 말하면 npm release incident 메모는 stage reject 기록과 live dist-tag recovery 기록을 반드시 분리해야 한다. stage reject는 승인 전 artifact 폐기 기록이고, dist-tag rollback은 이미 공개된 install 포인터를 복구한 기록이다.

    둘을 같은 runbook으로 적으면 누가 artifact를 reject 했는지와 누가 latest를 되돌렸는지가 섞인다. stage-id, reviewer, 2FA는 stage 쪽에 두고, package@version과 tag pointer와 registry 조회 결과는 live recovery 쪽에 따로 두는 편이 안전하다.

    2. 어디서 실제로 막히는가

    실무에서 흔한 실패는 네 가지다. 첫째, staged package를 reject 한 뒤 same version을 다른 tag로 다시 올릴 때 stage-id를 남기지 않는다. 둘째, dist-tag rollback을 했는데 어떤 maintainer가 latest를 돌렸는지 기록하지 않는다. 셋째, trusted publisher가 stage-only permission인지 allow-publish도 열려 있었는지 구분하지 않는다. 넷째, stage reject와 registry live recovery를 같은 타임라인 하나로만 남겨 승인 전 사고와 공개 후 사고를 구분하지 못한다.

    npm trusted publishers 문서는 stage-only permissions로 최대 보안 구성을 만들 수 있다고 설명한다. npm-stage 문서는 staged package의 tag가 immutable이고, 다른 tag가 필요하면 reject 뒤 재-stage 해야 한다고 적는다. dist-tags 문서는 npm publish가 기본적으로 latest를 붙인다고 설명한다. 이 셋을 같이 읽으면 stage reject는 artifact review 흐름이고, dist-tag rollback은 live registry pointer 관리 흐름이라는 점이 분명해진다.

    그래서 동일한 'release 되돌리기'처럼 보여도 책임과 증거가 다르다. stage reject에서는 stage-id, reviewer, 2FA, 새 stage 여부가 중요하고, dist-tag recovery에서는 package@version, old tag, new tag, registry 조회 결과, 실행자가 중요하다. 이 차이를 놓치면 회고 때도 어느 지점에서 잘못됐는지 정확히 설명할 수 없다.

    • 증상: reject는 했는데 live latest도 바뀌어 있어 원인 구분이 어렵다.
    • 실패: stage-id와 dist-tag pointer를 같은 표에 뭉뚱그린다.
    • 위험: trusted publisher permission이 stage-only였는지 publish까지 열렸는지 기록하지 않는다.
    • 재발: 같은 version을 다시 stage 했는지, live tag를 누가 복구했는지 남지 않는다.
    증상 먼저 볼 곳 판단 기준
    같은 version을 다른 tag로 다시 써야 함 stage-id와 reject 기록 immutable tag 때문에 reject 후 재-stage가 필요하다
    이미 latest가 잘못 감 dist-tag 목록과 operator live registry pointer 복구를 따로 기록한다
    CI publish 경로가 헷갈림 trust permission allow-stage-publish인지 allow-publish인지 본다

    3. 실무에서 적용하는 순서

    release incident runbook은 여섯 단계가 실용적이다. 먼저 trusted publisher permission이 stage-only인지 확인한다. 다음으로 현재 stage-id와 reviewer와 2FA 여부를 저장한다. 세 번째로 reject가 필요한지, approve 대신 re-stage가 필요한지 결정한다. 네 번째로 live registry가 이미 잘못 노출됐다면 dist-tag 목록을 조회한다. 다섯 번째로 필요한 tag rollback 또는 recovery를 실행한다. 마지막으로 stage 영역 기록과 live tag 기록을 별도 audit block으로 저장한다.

    1. trusted publisher permission을 먼저 확인한다.
    2. stage-id, reviewer, 2FA 사실을 저장한다.
    3. reject 후 재-stage가 필요한지 결정한다.
    4. npm dist-tag 목록을 조회하고 현재 pointer를 기록한다.
    5. 필요한 rollback 또는 recovery를 실행한다.
    6. stage audit과 live tag audit을 별도 블록으로 저장한다.

    이 절차에서는 조회하고, 저장하고, 비교하고, 실행하고, 다시 조회하는 순서를 지키는 편이 좋다. stage reject 블록에는 stage-id, reject 시각, reviewer, 2FA, 새 stage 여부를 넣고, live recovery 블록에는 package@version, old tag, new tag, dist-tag 목록 결과, operator를 넣는다. 버튼을 누르고, 명령을 실행하고, 결과를 복사하고, registry 화면을 확인하고, 다시 메모를 업데이트하는 흐름을 분리해야 release 회고가 정확해진다.

    • stage reject는 artifact 승인 전 흐름으로 저장한다.
    • dist-tag rollback은 live registry 포인터 복구로 저장한다.
    • same version 재-stage 여부와 새 tag를 따로 남긴다.
    • registry 조회 결과를 마지막에 다시 저장한다.
    permission=allow-stage-publish
    stage_id=stg_01JXYZ
    reject_required=true
    restage_tag=next
    dist_tag_before=latest:2.4.0
    dist_tag_after=latest:2.3.7
    verification=npm dist-tag ls scope/pkg

    이렇게 남겨 두면 stage review 사고와 live install 사고가 같은 release 메모에 섞이지 않는다. 팀이 어떤 단계에서 멈췄고 어떤 단계에서 복구했는지 분명해진다.

    4. 공식 문서와 예시 화면으로 확인하기

    첫 공식 화면은 trusted publisher 최대 보안 설정이다. npm은 stage-only permission을 두면 자동화 publish를 review 단계 뒤로 미룰 수 있다고 설명한다.

    trusted publisher를 stage-only permission으로 두면 CI publish와 live release가 분리된다.
    trusted publisher를 stage-only permission으로 두면 CI publish와 live release가 분리된다.

    이 전제를 먼저 이해해야 stage reject와 live tag recovery가 왜 같은 작업이 아닌지 분명해진다. stage 쪽은 승인 전 artifact 흐름이고, dist-tag recovery는 이미 live registry를 다루는 흐름이다.

    두 번째 자료는 npm stage 문서의 tag behavior다. staged package의 tag는 immutable이고, 다른 tag로 바꾸려면 먼저 reject 후 다시 stage 해야 한다고 적혀 있다.

    staged package의 tag는 바꿀 수 없으며, 다른 tag가 필요하면 reject 뒤 다시 stage 해야 한다.
    staged package의 tag는 바꿀 수 없으며, 다른 tag가 필요하면 reject 뒤 다시 stage 해야 한다.

    이 때문에 stage reject audit은 stage-id와 reviewer와 2FA 사실을 중심으로 남겨야 한다. live dist-tag rollback 메모와 섞으면 어떤 tag가 아직 registry에 반영됐는지 판단이 불분명해진다.

    세 번째 공식 화면은 dist-tag 기본 동작이다. npm publish는 기본적으로 latest를 붙이고, 다른 tag는 별도로 관리한다고 문서가 설명한다.

    dist-tag는 live registry 배포 경로를 관리하는 별도 축이며 latest가 기본값이다.
    dist-tag는 live registry 배포 경로를 관리하는 별도 축이며 latest가 기본값이다.

    따라서 live tag recovery는 stage reject와 다르게 이미 공개된 install 경로를 다루는 메모다. 누가 latest를 돌렸는지, 어떤 version을 다시 가리켰는지 별도 audit field가 필요하다.

    운영 메모에서는 staged reject와 live dist-tag recovery를 다른 표로 유지하는 편이 좋다. stage-id와 tag pointer는 같은 release 흐름처럼 보여도 실제로 책임자와 확인 위치가 다르다.

    stage reject audit field와 live dist-tag recovery audit field를 분리한 표다.
    stage reject audit field와 live dist-tag recovery audit field를 분리한 표다.

    이미 trusted publisher scope와 trust permission 글, org 2FA 정책과 rollout record 글, stage reject와 org member removal 분기 글을 읽었다면 이번 표는 rollback 기록만 따로 정리해 준다.

    마지막 자료는 rollback memo 예시다. stage-id와 dist-tag pointer를 별도 블록으로 나눠 적으면 누가 무엇을 reject 했고 무엇을 live로 되돌렸는지 헷갈리지 않는다.

    stage reject와 dist-tag recovery를 분리해 남기는 최소 메모 예시다.
    stage reject와 dist-tag recovery를 분리해 남기는 최소 메모 예시다.

    이 정도만 남겨도 approve 전 artifact 사고와 이미 배포된 latest 복구를 같은 runbook으로 섞지 않게 된다. 조회하고, 저장하고, dist-tag를 다시 확인하고, registry 결과를 복기하는 순서가 중요하다.

    여기에 approve 또는 reject를 남길 때 reviewer 2FA 흔적과 stage-id를 어떤 칸에 고정할지까지 정리하려면 새 staged artifact rollback 감사 필드 글을 같이 보면 artifact 사건과 live registry 사건을 더 짧게 나눌 수 있다.

    5. 주의사항과 리스크

    첫 번째 리스크는 stage reject로 이미 live latest도 해결됐다고 오해하는 것이다. 두 번째 리스크는 dist-tag rollback을 했지만 어떤 stage artifact를 reject 했는지 남기지 않는 것이다. 세 번째 리스크는 trusted publisher permission이 publish까지 열려 있었는지 확인하지 않아 자동화 경로를 잘못 판단하는 것이다.

    release 기록에는 명령과 결과와 확인 위치를 분리해 두는 편이 좋다. npm stage view 결과는 stage 영역에, npm dist-tag ls 결과는 live 영역에, npm trust permission은 자동화 경계에 두면 incident 메모가 훨씬 읽기 쉬워진다.

    • stage reject와 live rollback은 별도 audit field로 유지한다.
    • tag pointer 변경은 operator와 결과를 함께 남긴다.
    • trust permission을 적지 않으면 자동화 경로 판단이 틀어진다.

    6. 결론

    npm release incident에서는 reject 기록과 live tag recovery 기록을 분리해야 한다. stage-id와 reviewer와 2FA는 artifact 쪽에, package@version과 tag pointer는 live registry 쪽에 두는 편이 가장 안전하다.

    • stage reject와 dist-tag rollback을 같은 표로 쓰지 않는다.
    • same version 재-stage 여부를 따로 남긴다.
    • live tag recovery는 registry 조회 결과까지 저장한다.

    7. 참고 링크

    1. https://docs.npmjs.com/trusted-publishers/
    2. https://docs.npmjs.com/cli/v11/commands/npm-stage/
    3. https://docs.npmjs.com/cli/v11/commands/npm-trust/
    4. https://docs.npmjs.com/staged-publishing/
    5. https://docs.npmjs.com/adding-dist-tags-to-packages/
    반응형