한줄 요약
npm 공급망 공격은 외주 개발 환경의 CI/CD와 클라이언트 자격증명을 동시에 위협한다.
본문
공급망 공격(Supply Chain Attack)은 개발팀이 직접 작성하지 않은 외부 패키지를 통해 악성 코드가 빌드 환경 전체로 확산되는 보안 위협이다. 최근 들어 npm 생태계를 겨냥한 이런 유형의 공격이 점점 정교해지고 있으며, 특히 여러 클라이언트 프로젝트를 동시에 운영하는 IT 에이전시나 외주 개발사에게는 단순한 '남의 이야기'가 아니다.
왜 에이전시 환경이 특히 위험한가?
일반 기업 내부 개발팀과 달리, IT 에이전시는 구조적으로 공격 표면이 넓다.
하나의 CI/CD 파이프라인에서 복수의 클라이언트 프로젝트가 빌드된다. 개발자 한 명이 여러 프로젝트의 AWS 키, GitHub 토큰, npm 퍼블리시 권한을 동시에 보유하는 경우도 흔하다. 악성 패키지가 설치되는 순간, 그 환경에 저장된 모든 자격증명이 수집 대상이 된다.
최근 확인된 공격 방식을 보면 범위가 상당히 넓다. GitHub 토큰, AWS/Azure/GCP 클라우드 자격증명, SSH 키, .npmrc 파일, 환경변수, 그리고 각종 외부 서비스의 API 설정 파일까지 수집 대상에 포함된다. 수집된 정보는 암호화된 형태로 외부 서버로 전송되고, 탈취한 npm 토큰을 이용해 다른 패키지에 악성 코드를 재삽입하는 방식으로 감염이 확산된다.
에이전시 입장에서 이 시나리오가 무서운 이유는 하나다. 클라이언트 A의 프로젝트에서 발생한 감염이 클라이언트 B의 자격증명 탈취로 이어질 수 있다.
악성 패키지는 어떻게 탐지를 피하는가?
공격이 정교해진 데는 기술적인 이유가 있다.
난독화된 코드와 정상 패키지를 구분하기 어렵게 만드는 구조, 악성 동작을 지연 실행하는 타이밍 설계, 그리고 특정 로케일 환경에서는 아예 실행되지 않는 킬 스위치 로직까지 포함된다. 이는 단순한 바이러스 백신 스캔으로는 잡아내기 어렵다는 뜻이다.
더 주목할 점은 지속성 확보 방식이다. 셸 설정 파일(~/.bashrc, ~/.zshrc 등)에 페이로드를 삽입해 개발자가 터미널을 열 때마다 악성 코드가 실행되도록 한다. 개발 머신 자체가 지속적인 감염 상태에 놓이는 것이다. 에이전시 환경에서 개발자 로컬 머신이 공유 리소스에 접근하는 구조라면, 이 지속성은 심각한 문제가 된다.
또한 GitHub Actions 워크플로우에 무단으로 악성 스텝을 주입해 리포지토리 시크릿을 추가 수집하는 방식도 확인된다. CI 로그를 주기적으로 검토하지 않는 팀이라면 이 과정이 수 주간 발각되지 않을 수 있다.
에이전시가 즉시 실행해야 할 대응 체크리스트
보안 사고를 인지했을 때 '어디서부터 시작할지 모르겠다'는 상황을 막기 위해, 우선순위 순으로 정리한다.
1단계: 자격증명 전면 교체 의심스러운 패키지가 설치된 환경에서 사용된 모든 자격증명을 즉시 교체한다. GitHub 토큰, npm 액세스 토큰, 클라우드 IAM 키, SSH 키, CI/CD 시크릿 전부가 대상이다. 클라이언트 프로젝트에 사용된 자격증명도 포함해야 한다.
2단계: CI/CD 로그 전수 검토
최근 빌드 로그에서 비정상적인 외부 네트워크 연결, 예상치 못한 파일 접근, 알 수 없는 워크플로우 실행 이력을 확인한다. GitHub Actions 환경이라면 .github/workflows/ 디렉토리에 무단으로 추가된 파일이 없는지 살펴본다.
3단계: npm 퍼블리시 이력 감사
에이전시가 관리하는 npm 패키지가 있다면, 최근 버전 배포 이력과 preinstall 훅 변경 여부를 확인한다. 팀원이 인지하지 못한 버전이 배포되어 있다면 즉시 unpublish 후 조사에 들어간다.
4단계: 개발자 로컬 머신 점검
~/.bashrc, ~/.zshrc, ~/.profile 등 셸 설정 파일에 알 수 없는 코드가 추가되어 있는지 확인한다. 또한 임시 디렉토리(/tmp/)에 정체를 알 수 없는 락 파일이나 스크립트가 있는지도 점검한다.
5단계: 클라이언트 통보 여부 판단 클라이언트 자격증명이 포함된 환경이 감염된 경우, 해당 사실을 클라이언트에게 투명하게 알리고 함께 대응 계획을 세워야 한다. 이 단계를 미루거나 숨기는 것은 법적·계약적 리스크로 이어진다.
공급망 공격을 사전에 막는 구조 설계 원칙
사고 대응보다 중요한 건 사전 예방이다. 에이전시 차원에서 도입할 수 있는 구조적 방어책을 소개한다.
최소 권한 원칙 적용: 각 프로젝트별로 독립된 자격증명을 사용하고, 토큰의 유효 기간을 최소화한다. 한 프로젝트의 토큰이 다른 프로젝트 리소스에 접근할 수 없도록 격리한다.
패키지 버전 고정 및 무결성 검증: package-lock.json 또는 yarn.lock을 반드시 커밋하고, 패키지 해시를 검증하는 CI 단계를 추가한다. npm audit을 빌드 파이프라인에 포함시키는 것도 기본이다.
GitHub Actions 권한 최소화: 워크플로우에서 사용하는 토큰 권한을 contents: read처럼 필요한 최소 수준으로 제한한다. 외부에서 트리거되는 워크플로우(pull_request_target 등)에 특히 주의한다.
환경별 자격증명 분리: 개발, 스테이징, 프로덕션 환경의 자격증명을 철저히 분리한다. 개발 환경에서 탈취된 자격증명이 프로덕션에 영향을 미치지 않도록 하는 것이 핵심이다.