CI/CD 자동화할수록 공급망이 뚫린다

CI/CD 자동화할수록 공급망이 뚫린다

TanStack npm 공격이 증명한 것—파이프라인 자동화의 속도는 공격 표면도 함께 넓힌다

CI/CD 보안 공급망 공격 GitHub Actions npm 보안 OIDC 토큰 pull_request_target 캐시 포이즈닝 TanStack
광고

2026년 5월 11일, 19시 20분부터 19시 26분까지 단 6분. TanStack의 42개 npm 패키지에 84개의 악성 버전이 올라오는 데 걸린 시간이다. React 생태계에서 가장 널리 쓰이는 라이브러리 중 하나가 공격받았고, 공격자는 npm 토큰을 단 하나도 훔치지 않았다. dev.to에 공개된 TanStack 포스트모템을 분석하면, 이 공격이 단순한 해킹 사고가 아니라 CI/CD 자동화 파이프라인 자체를 무기로 바꾼 사례임을 알 수 있다.

공격의 구조는 세 단계로 이루어진 체인이었다. 첫째, GitHub Actions의 pull_request_target 트리거를 악용한 'Pwn Request' 패턴. 이 이벤트는 외부 PR에도 베이스 저장소의 시크릿을 노출하도록 설계되어 있어, 공격자가 악성 코드가 담긴 PR을 열기만 해도 해당 코드가 권한 있는 러너 위에서 실행된다. TanStack의 bundle-size.ymllabeler.yml 워크플로우가 정확히 이 패턴에 노출되어 있었다.

둘째, GitHub Actions 캐시 포이즈닝. 공격자는 bundle-size 워크플로우가 실행되는 동안 1.1GB짜리 악성 캐시 엔트리를 refs/heads/main 스코프로 기록했다. 이후 PR을 빈 diff로 force-push하고 닫았다. 저장소에서 공격의 시각적 흔적은 사라졌지만, 캐시는 남았다. 약 8시간 뒤, 정상적인 PR 머지가 release.yml을 트리거했고, 이 워크플로우는 오염된 캐시를 복원했다.

셋째, OIDC 토큰 탈취. TanStack은 npm Trusted Publisher 방식을 사용해 NPM_TOKEN을 저장소에 보관하지 않았다. 배포 시점에 러너가 GitHub로부터 OIDC 토큰을 직접 발급받는 구조다. 공격자가 심어둔 악성 코드는 release.yml의 teardown 단계에서 이 토큰을 메모리에서 추출했고, registry.npmjs.org에 직접 HTTP 요청을 날려 84개 버전을 게시했다. npm은 워크플로우가 정식 서명한 요청으로 판단하고 그대로 수락했다.

이 공격에서 팀 리드로서 가장 불편한 지점은 따로 있다. 공격자의 ID가 claude <claude@users.noreply.github.com>이었고, 커밋 메시지 앞에 [skip ci]를 붙여 CI 트리거를 의도적으로 억제했다. AI 도구가 생성한 커밋 식별자와 구분하기 어려운 이름을 위장에 사용했다는 점은, AI-First 워크플로우에서 커밋 출처 검증이 얼마나 중요한지를 역설한다.

GitLab이 최근 발표한 구조조정 계획은 이 맥락에서 읽어야 한다. GitLab은 AI 에이전트가 계획·코딩·리뷰·배포·복구를 담당하는 '에이전트형 시대'를 선언하며, 에이전트가 병렬로 merge request를 열고 24시간 파이프라인을 트리거하는 인프라로의 전환을 예고했다. R&D를 약 60개 소규모 팀으로 재편하고, 내부 검토와 인수인계를 AI 에이전트로 자동화하겠다는 방향이다. 방향성 자체는 맞다. 문제는 에이전트가 트리거하는 CI/CD 이벤트가 늘어날수록, TanStack 사례에서 본 공격 표면도 함께 넓어진다는 점이다.

실제로 Google Threat Intelligence Group이 같은 시기 공개한 보고서에서도 같은 신호가 잡힌다. 북한 APT45는 수천 개의 프롬프트를 자동으로 반복 제출해 취약점을 분석하고 익스플로잇 코드를 검증하는 방식을 채택했다. AI가 공격자의 취약점 탐색 속도를 높이는 동안, 방어 측의 CI/CD 파이프라인은 자동화라는 이름 아래 검증 레이어를 걷어내고 있다. 이 두 벡터가 만나는 지점이 바로 공급망 공격의 새 진입로다.

내일 당장 팀에 적용할 수 있는 체크리스트를 정리한다.

GitHub Actions 워크플로우 점검 - pull_request_target을 사용하는 모든 워크플로우를 목록화하고, 외부 PR 코드를 checkout해 실행하는 단계가 있는지 확인한다. 있다면 pull_request 이벤트로 교체하거나, 외부 코드 실행 단계를 완전히 분리한다. - 워크플로우별 permissions를 최소 권한으로 명시한다. id-token: write는 실제 배포 워크플로우에만 선언해야 한다.

캐시 보안 - 릴리즈 워크플로우가 포크나 외부 PR 워크플로우가 쓴 캐시를 복원할 수 있는지 확인한다. 복원 가능하다면 해당 캐시를 신뢰하지 않는 것이 기본 원칙이다. - 프로덕션 배포 파이프라인은 캐시 복원 없이 클린 빌드를 강제하거나, 캐시 키에 배포 전용 시크릿 해시를 포함시켜 외부 워크플로우가 동일한 키를 쓸 수 없게 막는다.

OIDC Trusted Publisher 설정 - npm Trusted Publisher 바인딩을 특정 워크플로우 파일과 브랜치 ref에 정확히 고정한다. 범위가 넓을수록 공격 벡터가 넓어진다. - 배포 워크플로우의 teardown 단계에서 외부 코드가 실행될 수 없는지 확인한다. teardown은 메인 단계가 실패해도 실행된다는 점을 잊지 말아야 한다.

공급망 모니터링 - StepSecurity의 Harden-Runner, Socket.dev 같은 도구를 CI 파이프라인에 통합해 런타임에 비정상 네트워크 요청을 탐지한다. - 팀의 npm 패키지 의존성에 @tanstack/*이 포함되어 있고 5월 11일 npm install을 실행한 이력이 있다면, AWS·GCP·GitHub·npm·SSH 자격증명 로테이션을 즉시 진행해야 한다.

GitLab의 비전처럼 에이전트가 파이프라인을 자율적으로 운용하는 세상이 온다면, 지금 당장 해결해야 할 선행 과제가 있다. 에이전트가 트리거하는 이벤트와 사람이 트리거하는 이벤트를 파이프라인 레벨에서 구분하고, 각각에 다른 권한과 검증 게이트를 적용하는 구조다. 자동화의 속도를 올리면서 보안 레이어를 함께 설계하지 않으면, 다음 TanStack 사례의 피해자는 당신의 팀이 될 수 있다.

출처

더 많은 AI 트렌드를 Seedora 앱에서 확인하세요