Bitwarden CLI npm 패키지, GitHub Actions CI/CD 파이프라인 공격으로 악성코드 삽입됨
Bitwarden CLI compromised in ongoing Checkmarx supply chain campaign
TL;DR Highlight
Bitwarden CLI의 npm 패키지(@bitwarden/cli 2026.4.0)가 GitHub Actions CI/CD 파이프라인 침해를 통해 자격증명 탈취 악성코드에 감염됐다. 1천만 명 이상이 사용하는 오픈소스 패스워드 매니저 CLI가 공급망 공격에 당했다는 점에서 npm 보안 전반에 경각심을 주는 사건이다.
Who Should Read
CI/CD 파이프라인에서 npm 패키지를 설치하거나 Bitwarden CLI를 사용하는 개발자 및 DevOps 엔지니어. 특히 GitHub Actions 워크플로우에서 자동으로 의존성을 설치하는 환경을 운영 중인 팀.
Core Mechanics
- 공격 대상이 된 버전은 @bitwarden/cli 2026.4.0이며, 악성 코드는 패키지 내 bw1.js 파일에 삽입됐다. 공격자는 Bitwarden의 GitHub Actions CI/CD 파이프라인을 침해해 빌드 결과물에 페이로드를 심는 방식을 사용했다.
- 이 사건은 Checkmarx 공급망 캠페인(특정 공격 그룹이 npm 생태계를 노리는 연속 공격)의 일부로, 이전에 분석된 mcpAddon.js와 동일한 C2(명령·제어 서버) 엔드포인트(audit.checkmarx[.]cx/v1/telemetry)와 페이로드 구조를 공유한다.
- 악성 페이로드는 GitHub Actions Runner의 메모리를 스크래핑해 GitHub 토큰을 훔치고, ~/.aws/ 파일과 환경변수에서 AWS 자격증명을, azd·gcloud·~/.npmrc에서 Azure/GCP/npm 토큰을, 그리고 Claude/MCP 설정 파일까지 탈취한다.
- 탈취한 npm 토큰으로 피해자가 쓰기 권한을 가진 다른 npm 패키지를 찾아 preinstall 훅에 악성 코드를 주입해 재배포하는 공급망 전파 방식을 사용한다. GitHub에도 Dune 소설 테마 이름({단어}-{단어}-{3자리숫자}) 형태의 공개 저장소를 만들어 암호화된 결과를 커밋한다.
- 러시아어 로케일 킬스위치가 있어, 시스템 locale이 'ru'로 시작하면 조용히 종료된다. Intl.DateTimeFormat().resolvedOptions().locale과 LC_ALL, LC_MESSAGES, LANGUAGE, LANG 환경변수를 확인한다.
- 악성 페이로드는 npm install 시점의 preinstall 훅에서 실행되기 때문에, 설치 후 코드를 검사하는 기존 보안 관행이 무력화된다. 설치 자체가 공격이기 때문에, CI/CD에서 자동 설치가 반복되는 환경은 단시간 노출만으로도 위험하다.
- Bitwarden CLI 자체는 자동 업데이트를 하지 않으므로 피해는 제한적이었고, 영향을 받은 다운로드 수는 약 334건으로 확인됐다. 브라우저 확장, MCP 서버, 기타 공식 배포판은 현재까지 영향 없음.
- ~/.bashrc와 ~/.zshrc에 페이로드를 주입해 셸 재시작 후에도 지속성을 유지하며, Dune 소설의 'Shai-Hulud', 'Butlerian Jihad' 등 이념적 브랜딩이 악성코드 내에 직접 포함된 것이 이전 Checkmarx 캠페인과 다른 점이다.
Evidence
- npm 패키지 설치 시 최소 릴리즈 대기 기간을 설정하면 이런 공격을 방어할 수 있다는 실용적 조언이 공유됐다. npm 11.10+에서 .npmrc에 min-release-age=7(일)을 설정하면 이번 악성 패키지(~19시간 만에 발견·deprecated)뿐 아니라 axios, ua-parser-js 등 단시간에 제거된 이전 사례들도 막을 수 있었을 것이라는 의견이다.
- preinstall 훅이 실행 시점이라는 점이 핵심 문제라는 지적이 있었다. '설치 후 검사'라는 기존 보안 관행이 완전히 무너지며, AI 코딩 어시스턴트나 에이전트가 자동으로 패키지를 설치하는 환경에서는 단 한 번의 설치만으로도 공격이 성립한다는 점에서 더 위험하다는 분석이 나왔다.
- 의존성 고정(pin)의 중요성에 대한 토론이 있었다. lockfile이 있어도 ^(캐럿) 버전 범위를 쓰면 lockfile 업데이트 시 의도치 않은 새 버전이 들어올 수 있으므로, 비즈니스에 치명적인 도구는 정확한 버전을 고정해야 한다는 의견이 나왔다.
- 이 공격이 Claude/MCP 설정 파일까지 탈취 대상으로 삼는다는 점에서, 셸 프로파일 변조를 통해 다음 코딩 어시스턴트가 읽는 컨텍스트를 오염시키는 새로운 공격 벡터가 등장할 수 있다는 우려가 제기됐다.
- 러시아 로케일 킬스위치에 대해 '대담하면서도 비겁하다'는 반응이 있었다. 자국민은 보호하면서 나머지를 공격하는 패턴이 국가 연계 또는 특정 지역 기반 위협 행위자임을 강하게 시사한다는 의견이다.
How to Apply
- npm/pnpm/bun/uv를 사용하는 프로젝트라면 패키지 매니저 설정에 최소 릴리즈 대기 기간을 추가하면 된다. ~/.npmrc에 min-release-age=7, pnpm rc에 minimum-release-age=10080(분), ~/.bunfig.toml에 minimumReleaseAge = 604800(초)을 설정하면 신규 배포 직후 악성 패키지가 자동 설치되는 것을 방지할 수 있다.
- CI/CD 파이프라인에서 npm 패키지를 설치할 때 package.json의 버전 범위를 ^ 없이 정확한 버전으로 고정하고, lockfile을 커밋에 포함시켜야 한다. 특히 시크릿이나 자격증명을 다루는 도구(Bitwarden CLI 등)는 반드시 버전 핀닝을 적용하는 것이 좋다.
- Bitwarden CLI를 현재 사용 중이라면 영향 받은 버전(2026.4.0)을 사용했는지 CI 로그를 확인하고, 해당 워크플로우에서 노출됐을 수 있는 모든 시크릿(GitHub 토큰, AWS/GCP/Azure 자격증명, npm 토큰, SSH 키 등)을 즉시 교체해야 한다. Bitwarden 공식 커뮤니티에서 공개한 피해 시간대를 참고해 노출 여부를 판단하면 된다.
- GitHub Actions를 운영 중이라면 워크플로우에서 사용하는 서드파티 Action의 버전을 SHA 해시로 고정하고, 불필요한 시크릿 접근 권한을 제거해 침해 시 피해 범위를 최소화하는 것이 좋다.
Code Example
snippet
# ~/.npmrc (npm 11.10+ 필요)
min-release-age=7 # 단위: 일
# ~/Library/Preferences/pnpm/rc
minimum-release-age=10080 # 단위: 분
# ~/.bunfig.toml
[install]
minimumReleaseAge = 604800 # 단위: 초
# ~/.config/uv/uv.toml (Python uv 패키지 매니저)
exclude-newer = "7 days"Terminology
공급망 공격(Supply Chain Attack)내가 직접 만든 코드가 아닌, 내가 사용하는 라이브러리나 빌드 도구를 공격해 내 소프트웨어에 악성코드를 심는 방식. 마치 식재료 공급업체를 해킹해 식당 음식을 오염시키는 것과 같다.
C2(Command & Control)악성코드가 공격자로부터 명령을 받고 탈취한 데이터를 보내는 서버. 악성 프로그램의 본부 역할을 한다.
preinstall 훅npm install 실행 시 패키지 설치 직전에 자동으로 실행되는 스크립트. package.json에 설정하며, 공격자가 이 시점에 악성코드를 실행하면 사용자가 막을 방법이 없다.
버전 핀닝(Version Pinning)패키지 버전을 '1.x.x'처럼 범위로 지정하지 않고 '1.2.3'처럼 정확하게 고정하는 것. 범위로 지정하면 나중에 악성 업데이트가 자동으로 설치될 수 있다.
CI/CD 파이프라인코드를 자동으로 빌드·테스트·배포하는 자동화 과정. GitHub Actions가 대표적이며, 이 파이프라인이 침해되면 배포되는 소프트웨어 자체가 오염될 수 있다.
킬스위치(Kill Switch)특정 조건에서 악성코드가 스스로 실행을 멈추는 장치. 이 사례에서는 러시아 로케일이 감지되면 아무것도 하지 않고 종료된다.