탈취한 GitHub 토큰으로 공개 저장소 8천여 곳에 정보 탈취 코드 심어
SafeDep은 공개 저장소의 기본 브랜치 검색에서 2026년 11월 10~12일 악성 커밋을 받은 저장소 8,093곳을 집계했습니다. 같은 보고서의 목록에는 8,092행이 표시됩니다. 공격자는 GitHub Actions 워크플로나 npm 설치 훅으로 비밀값을 빼내고 AWS·Azure에 지속성 있는 접근 경로를 추가한 뒤, 대부분의 악성 커밋을 되돌렸습니다.

SafeDep 블로그에 Kunal Singh가 게시한 분석은 Aikido Security가 먼저 보고한 ‘wfx’ 캠페인을 다룹니다. SafeDep은 2026년 11월 10일 12:46 UTC부터 11월 12일 04:36 UTC까지 악성 커밋을 받은 저장소가 8,093곳이라고 집계했습니다. 검색 대상은 공개 저장소의 기본 브랜치로, 비공개 저장소와 다른 브랜치는 포함하지 않았습니다. 같은 페이지의 저장소 목록에는 8,092행이 표시됩니다. SafeDep은 검색 당시에도 캠페인이 진행 중이라고 밝혔습니다.
공격자는 탈취한 GitHub 토큰으로 저장소에 글을 쓸 권한이 있는지 먼저 시험했습니다. wfx-docs 브랜치에서 docs: dependency audit notes라는 제목의 풀 리퀘스트를 열고 작은 메모 파일을 추가해 토큰의 쓰기 권한을 확인했습니다. 이후 저장소에 악성 코드를 심었습니다. 빈 저장소에는 Actions 워크플로 파일을 새로 추가했고, 기존 저장소에서는 워크플로 파일에 코드를 끼워 넣었습니다. 세 번째 방식은 package.json에 npm postinstall 훅을 넣어 패키지 설치 때 스크립트를 실행하는 것이었습니다. 세 방식 모두 CI에서 작동하지만, npm 훅은 개발자의 컴퓨터에서 해당 패키지를 설치할 때도 실행될 수 있습니다.
Actions 워크플로 방식은 저장소에 코드가 푸시될 때마다 실행되도록 구성됐습니다. 첫 단계에서 GitHub Actions 실행 토큰을 작업 환경에 복사하고, 다음 단계에서 저장소의 비밀값과 변수를 JSON 형태로 페이로드에 전달했습니다. 페이로드는 실행 환경의 프로세스 메모리와 환경변수에서 API 키를 찾고, SSH 키와 클라우드 명령줄 도구 설정, Kubernetes 설정 파일, 저장소 안의 .env 같은 파일도 수집하도록 작성됐습니다. GitHub Actions가 외부 서비스에 인증할 때 사용하는 OIDC 토큰도 요청해 AWS·Azure·GCP에 접근을 시도했습니다. 저장소가 클라우드와 연동되지 않은 경우에도 토큰을 요청했으며, Vault와 Kubernetes API에도 이를 다시 제출했습니다. 수집한 정보는 외부 서버로 HTTP 요청을 보내거나 Telegram을 통해 전송하도록 구성됐습니다.
공격자는 대부분의 저장소에서 코드 실행 뒤 악성 커밋을 되돌렸습니다. SafeDep이 소개한 한 사례에서는 악성 코드가 들어간 커밋과 이를 제거한 커밋이 약 4분 간격으로 올라왔습니다. 커밋을 되돌려도 이미 실행된 워크플로가 외부로 보낸 비밀값은 회수되지 않습니다. 분석된 페이로드에는 AWS 접근 키를 만들고 역할의 신뢰 정책을 바꾸는 기능이 있었습니다. 신뢰 정책은 어떤 주체가 해당 역할을 맡을 수 있는지 정하는 설정입니다. Azure에서는 앱 등록에 wfx-ci-*라는 이름의 페더레이션 자격 증명을 추가했습니다. 이 설정이 남으면 앱의 클라이언트 비밀값을 교체한 뒤에도 해당 저장소의 메인 브랜치 워크플로가 앱에 인증할 수 있습니다.
패키지 위험은 설치 방식과 실행 조건에 따라 달랐습니다. SafeDep은 Packagist 패키지 digitalelvis/neuronai-studio v3.14.3, Go 모듈 gocurl의 특정 pseudo-version, redock v1.0.278·v1.0.280·v1.0.282를 지목했습니다. neuronai-studio는 Composer로 설치할 때 npm 훅이 실행되지 않습니다. 반면 해당 패키지 디렉터리에서 npm install이나 npm ci를 실행하면 훅이 작동합니다.
Go 모듈 두 사례에서 페이로드는 Go 소스 코드가 아니라 GitHub Actions 워크플로 파일에 들어갔습니다. go get, go build, go mod download는 Go 모듈을 내려받거나 빌드하는 명령으로, Actions 워크플로를 실행하지 않습니다. gocurl 워크플로는 버전 태그가 올라올 때 작동하도록 설정됐지만 공격자 푸시에는 태그가 없었습니다. redock 워크플로는 beta 브랜치 푸시가 조건이었는데 공격자는 master에 커밋했습니다. SafeDep이 조사한 당시 두 저장소 모두 해당 조건으로 워크플로가 실행된 기록은 없었습니다.
SafeDep은 영향을 받은 저장소의 커밋 작성자 계정에서 토큰·OAuth 앱 승인·SSH 키·세션을 폐기하고 2단계 인증을 설정하며, 저장소가 읽을 수 있었던 비밀값과 변수를 교체하라고 권고했습니다. 클라우드 흔적으로는 AWS CloudTrail에서 wfx-* 세션이 CreateAccessKey나 UpdateAssumeRolePolicy를 실행했는지, Azure Entra ID 앱 등록에 출처를 알 수 없는 wfx-ci-* 페더레이션 자격 증명이 있는지를 들었습니다. GCP 작업 부하 ID 풀의 GenerateAccessToken·SignJwt 호출과 GitHub OIDC 발급자를 통한 Vault JWT 로그인도 확인 항목으로 제시했습니다.