ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [npm][보안] staged artifact rollback에서 reviewer 2FA 흔적과 stage ID를 어떤 감사 필드로 고정하나
    기타개발지식/풀스택개발 2026. 8. 21. 20:17

    IT 리서치 노트

    [npm][보안] staged artifact rollback에서 reviewer 2FA 흔적과 stage ID를 어떤 감사 필드로 고정하나

    npm staged publishing을 운영하다 보면 rollback 메모가 package version과 dist-tag 쪽으로만 쏠리고, 정작 reviewer 2FA 흔적과 stage ID는 뒤늦게 찾게 되는 경우가 많다. 하지만 2026년 8월 21일 기준 npm 공식 문서를 다시 보면 staged artifact lifecycle은 stage-id 중심으로 움직이고, stage publish는 2FA를 나중 승인 단계로 미루며, bypass 2FA token도 staged approval의 2FA를 우회하지 못한다. 이 글은 staged artifact rollback에서 reviewer 2FA 흔적과 stage ID를 어떤 감사 필드로 고정해야 incident 복기가 짧아지는지 정리한다.

    1. 개요

    결론부터 말하면 npm staged artifact rollback 메모의 첫 칸은 package version이 아니라 stage-id여야 하고, 두 번째 칸은 reviewer + 2FA 확인 시각이어야 한다. stage publish는 CI가 2FA 없이 올릴 수 있지만 approve·reject 판단은 proof-of-presence가 필요한 별도 단계이기 때문이다.

    이 둘을 고정하지 않으면 reject, approve 보류, re-stage, dist-tag rollback이 모두 "release 되돌리기"처럼 보여도 artifact 축과 live registry 축을 다시 분리할 수 없다. stage-id는 승인 전 artifact를 식별하고, reviewer 2FA 흔적은 사람이 그 artifact에 개입했다는 보안 증거를 남긴다.

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

    실무에서 흔한 실패는 rollback을 version 메모 하나로 닫는 것이다. 예를 들어 @scope/pkg@2.4.1을 reject했는데 어떤 stage-id였는지 안 적으면, 나중에 같은 version을 다시 stage한 사건과 원래 reject 사건이 섞인다. 또 CI가 stage publish를 했다는 사실만 적고 reviewer가 언제 2FA로 approve 또는 reject했는지 안 남기면 proof-of-presence가 있었는지 회고가 불가능해진다.

    npm stage 문서는 view·approve·reject가 모두 stage-id를 기준으로 동작한다고 적고, stage publish는 2FA prompt를 요구하지 않아 proof-of-presence를 나중으로 미룬다고 설명한다. creating and publishing public packages 문서는 bypass 2FA가 켜진 granular access token도 staged package approval의 2FA를 우회하지 못한다고 못 박는다. trusted publishers 문서는 OIDC로 할 수 있는 것은 publish 또는 stage publish까지이며, stage view·approve·reject는 interactive auth가 필요하다고 적는다. 이 네 문장을 합치면 artifact lifecycle의 감사 중심은 version이 아니라 stage-id와 reviewer action이다.

    문제가 더 커지는 지점은 dist-tag rollback과 섞일 때다. live registry에서 latest를 되돌리는 일은 이미 공개된 install path를 바꾸는 사건이고, staged artifact reject는 승인 전 artifact를 폐기하는 사건이다. version만 적고 넘어가면 이 두 사건이 같은 시간축에 겹쳐져 누가 무엇을 승인했고 무엇을 공개 경로에서 되돌렸는지 분리되지 않는다.

    • 증상: 같은 version이 두 번 stage됐는데 어느 artifact를 reject했는지 다시 못 찾는다.
    • 실패: reviewer와 2FA 흔적을 stage 메모에 안 남긴다.
    • 막힘: approve 보류와 dist-tag rollback을 같은 줄에 적는다.
    • 누락: trusted publisher action scope와 re-stage 여부를 함께 기록하지 않는다.
    보이는 장면 섞이면 안 되는 필드 먼저 적을 값
    artifact를 reject했다 package version만 적고 종료 stage-id, reviewer, 2FA 확인 시각
    같은 version을 다시 stage했다 old/new stage-id 혼합 new stage-id, target tag, reason
    latest도 잘못 노출됐다 artifact 사건과 live tag 사건 혼합 dist-tag 기록, operator, registry 조회 결과

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

    가장 짧은 방법은 rollback 메모를 두 블록으로 자르는 것이다. 첫째 블록은 staged artifact block이다. 여기에 stage-id, package@version, reviewer, 2FA 확인 시각, 결정, re-stage 필요 여부를 넣는다. 둘째 블록은 live registry block이다. dist-tag rollback이 실제로 있었을 때만 package@version, old tag, new tag, operator, registry 재조회 결과를 넣는다.

    1. stage view 결과에서 stage-id를 먼저 고정한다.
    2. approve 또는 reject 판단을 한 reviewer와 2FA 흔적을 적는다.
    3. reject 사유와 re-stage 필요 여부를 artifact block에 적는다.
    4. live dist-tag 변경이 있었을 때만 별도 registry block을 연다.
    5. 새 artifact를 stage했다면 new stage-id를 새 줄로 만든다.
    • stage-id를 조회하고 reviewer를 기록하고 2FA 시각을 저장한다.
    • reject 이유를 분리하고 re-stage 여부를 확인하고 target tag를 비교한다.
    • registry 상태를 재조회하고 operator를 남기고 live 사건을 분리한다.
    • 새 artifact를 다시 stage하고 새 stage-id를 적고 승인 경로를 추적한다.
    • approve 또는 reject를 명시하고 proof-of-presence를 검증하고 audit line을 유지한다.
    • tag 변경을 점검하고 tarball 결과를 확인하고 incident 복구 메모를 저장한다.

    실제 대응에서는 stage view 조회 결과, approve 또는 reject 응답, registry 조회 결과, 남겨 둘 로그 파일, 바꾼 설정, 검토한 권한 범위, 다시 실행한 명령까지 한 묶음으로 저장하는 편이 좋다. 이 기록이 있어야 같은 incident를 다시 확인할 때 artifact 축과 live 축을 빠르게 분리할 수 있다.

    이 방식의 장점은 staged artifact와 live package distribution을 서로 다른 속도로 복구할 수 있다는 점이다. 어떤 incident는 stage reject로 끝나고 live registry에는 아무 변화가 없을 수 있다. 반대로 어떤 incident는 artifact reject와 동시에 latest 복구까지 해야 한다. 두 사건을 같은 줄에 넣으면 package version 하나만 남고, 사람 승인 흔적과 stage lineage가 지워진다.

    또 reviewer 2FA 흔적은 단순 참고용이 아니다. npm이 staged approval에서 proof-of-presence를 요구하도록 설계한 이상, 누가 어떤 경로로 approve·reject했는지를 남기는 것이 공급망 운영 기록의 핵심이다. token bypass 2FA가 approve를 우회하지 못한다는 문장도 이 설계를 뒷받침한다. stage-id와 reviewer 2FA를 먼저 적고, 그 다음에야 dist-tag나 trust permission을 붙이는 순서가 맞다.

    감사 필드 예시
    artifact_block.stage_id=stg_01JXYZ
    artifact_block.review_actor=maintainer_a
    artifact_block.review_2fa=confirmed
    artifact_block.decision=reject
    artifact_block.restage=true
    registry_block.changed=false
    registry_block.old_tag=
    registry_block.new_tag=
    registry_block.operator=

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

    첫 공식 자료는 npm stage subcommands 구간이다. npm은 view, approve, reject가 모두 stage-id를 기준으로 움직인다고 명확히 적어 둔다.

    staged artifact 운영의 핵심 식별자는 package version만이 아니라 stage-id라고 npm 문서가 설명한다.
    staged artifact 운영의 핵심 식별자는 package version만이 아니라 stage-id라고 npm 문서가 설명한다.

    즉 rollback 메모를 package@version 하나로 끝내면 승인 전 artifact 사건을 다시 못 따라간다. reviewer 2FA 흔적도 결국 특정 stage-id에 연결돼야 audit trail이 닫힌다.

    두 번째 공식 자료는 token behavior 설명이다. npm은 stage publish 자체는 2FA prompt를 요구하지 않고, proof-of-presence를 나중 승인 단계로 미룬다고 설명한다.

    stage publish는 2FA를 나중 승인 단계로 미루므로 reviewer와 2FA 흔적이 별도 감사 필드가 된다.
    stage publish는 2FA를 나중 승인 단계로 미루므로 reviewer와 2FA 흔적이 별도 감사 필드가 된다.

    이 전제가 있으면 CI가 artifact를 올린 시각과 사람이 approve·reject 판단을 한 시각을 같은 줄에 뭉개면 안 된다는 결론이 나온다. stage-id는 artifact 축이고 reviewer 2FA는 proof-of-presence 축이다.

    세 번째 공식 자료는 staged approval의 2FA 한계다. npm은 bypass 2FA가 켜진 granular access token이라도 staged package approval의 2FA를 우회하지 못한다고 못 박는다.

    bypass 2FA token이 있어도 staged approval의 reviewer 2FA는 남기게 되어 있으므로 approve actor 흔적이 중요하다.
    bypass 2FA token이 있어도 staged approval의 reviewer 2FA는 남기게 되어 있으므로 approve actor 흔적이 중요하다.

    이 문장 때문에 rollback 메모에서 reviewer 2FA 흔적을 optional 필드처럼 다루면 안 된다. live publish와 달리 staged artifact lifecycle은 사람 승인 사실 자체가 보안 증거가 된다.

    실무에서는 stage-id와 reviewer 2FA 흔적을 어떤 칸에 고정할지 먼저 정하는 편이 좋다. stage reject, approve 보류, re-stage, live rollback은 모두 release incident처럼 보이지만 남겨야 할 값이 다르다.

    stage-id와 reviewer 2FA 흔적을 중심으로 staged artifact rollback 감사 필드를 분리한 표다.
    stage-id와 reviewer 2FA 흔적을 중심으로 staged artifact rollback 감사 필드를 분리한 표다.

    이미 stage reject와 org member removal 분기 글과 dist-tag recovery 글로 바깥 축을 다뤘다면, 이번 표는 staged artifact rollback 안쪽 audit 필드만 더 좁게 정리한 판이다.

    마지막 자료는 rollback memo 예시다. stage-id와 reviewer 2FA 흔적을 별도 블록으로 고정해 두면 CI staging과 사람 승인 판단을 한눈에 분리할 수 있다.

    reviewer 2FA 흔적과 stage-id를 함께 남기는 staged artifact rollback 메모 예시다.
    reviewer 2FA 흔적과 stage-id를 함께 남기는 staged artifact rollback 메모 예시다.

    이 정도만 있어도 release incident 회고에서 '누가 reject 했는지', '2FA proof-of-presence가 있었는지', '같은 version을 다시 stage 했는지'를 package version 하나보다 훨씬 정확히 복원할 수 있다.

    5. 주의사항과 리스크

    첫 번째 리스크는 reject 사건을 version 메모만 남기고 끝내는 것이다. 두 번째는 reviewer 2FA를 구두 확인에만 두고 시간과 경로를 안 적는 것이다. 세 번째는 dist-tag recovery가 일어나지 않았는데도 rollback 메모에 미리 넣어 artifact 사건과 섞는 것이다.

    운영 전에 확인할 때는 최소한 stage-id, review actor, 2FA 확인 시각, decision, re-stage 여부 다섯 값을 artifact block 기본값으로 고정해 두는 편이 좋다. 그래야 rollback 메모가 사람 기억이 아니라 stage lineage 기준으로 남는다.

    • stage-id는 package version보다 먼저 적는다.
    • reviewer 2FA 흔적은 선택값이 아니라 proof-of-presence 증거다.
    • artifact 사건과 live registry 사건은 별도 블록으로 둔다.

    6. 결론

    npm staged artifact rollback을 짧게 복기하려면 package version보다 stage-id와 reviewer 2FA 흔적을 먼저 고정하는 편이 맞다. stage-id는 승인 전 artifact lineage를, reviewer 2FA는 사람 개입 증거를 남긴다. 그다음에만 dist-tag 같은 live registry 사건을 별도 블록으로 붙이면 release incident가 훨씬 읽기 쉬워진다.

    여기서 한 걸음 더 나가 approve actor 시점과 reviewer 2FA 시점을 실제 사건 시간축으로 나눠 적고 싶다면 같은 날 발행한 approve actor와 reviewer 2FA 시점을 어떤 incident timeline으로 분리하나를 바로 이어서 보면 된다. 그 글은 artifact staged_at, review decision_at, live visible_at, rollback action_at을 따로 적는 후속편이다.

    • artifact block의 기본 필드는 stage-id와 reviewer 2FA다.
    • re-stage가 생기면 new stage-id를 새 줄로 만든다.
    • live rollback은 별도 registry block으로 분리한다.

    7. 참고 링크

    1. https://docs.npmjs.com/cli/v11/commands/npm-stage/
    2. https://docs.npmjs.com/trusted-publishers/
    3. https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages/
    4. https://docs.npmjs.com/creating-and-viewing-access-tokens/
Designed by Tistory.