코드를 실행하는 설정 파일: 공급망 보안의 맹점
최근 악성코드 캠페인(마이아즈마 웜)이 VS Code, Cursor, Claude Code 등 주요 IDE와 AI 코딩 에이전트의 설정 파일을 악용해 개발자의 모르게 악성 코드를 실행하는 사례가 확인되었습니다. 설정 파일에 숨겨진 명령어는 클라우드 및 인증 토큰 등을 탈취하며, 대부분의 개발자가 코드 리뷰 없이 신뢰 프롬프트를 통과시키기 때문에 매우 위협적입니다. 개발자들은 저장소 클론이나 폴더 열기만으로도 공격에 노출될 수 있으므로 설정 파일에 대한 철저한 검토가 시급합니다.
블로그로 돌아가기
코드를 실행하는 설정 파일: 공급망 보안의 맹점
맬웨어 SafeDep 팀 • 2026년 6월 6일 • 10분 분량
목차
저장소를 복제(clone)하여 에디터에서 여는 것만으로도 개발자가 단 한 줄의 코드도 읽기 전에 공격자의 코드가 실행될 수 있습니다. 여기서 작동하는 것은 악의적인 의존성(dependency)이나 숨겨진 설치 스크립트가 아닙니다. IDE, AI 코딩 에이전트 또는 패키지 관리자가 자동으로 읽고 실행하는, 저장소에 이미 존재하는 평범해 보이는 설정 파일(Config file)이 트리거입니다.
VS Code, Cursor, Claude Code, Gemini CLI, npm, Composer, Bundler 등은 모두 셸 명령을 포함할 수 있는 설정 파일을 지원합니다. 일부는 의존성을 설치하거나 테스트를 실행할 때 명령을 실행합니다. 또 다른 것들은 폴더를 열거나 에이전트 세션이 시작될 때, 대부분의 경우 개발자가 읽지도 않고 클릭하여 넘어가는 일회성 신뢰 프롬프트 이후에 실행됩니다. 명령을 실행하는 설정 파일은 단순한 메타데이터가 아닌 '실행 원시 데이터(execution primitive)'이며, 공급망 공격자들은 이를 실제 공격 수단으로 사용하기 시작했습니다. 아무도 이러한 파일을 코드 리뷰하지 않습니다.
이 글에서는 실제 소스 코드를 통해 설정 파일 주입 벡터(injection vector)를 살펴보고, 더 넓은 범주의 공격 패턴을 매핑하여 diff(변경 내역)에서 이러한 패턴을 쉽게 발견할 수 있도록 합니다. 'Miasma(마이아즈마)' 웜이 그 실제 사례입니다.
icflorescu/mantine-datatable 저장소에 대한 하나의 커밋(commit f72462d9)은 서명되지 않았으며, github-actions <[email protected]>로 작성되었습니다. 커밋 메시지는 'chore: update dependencies [skip ci]'였으며, 6개의 파일을 추가했습니다. 그중 5개는 6번째 파일인 .github/setup.js라는 단일 드로퍼(dropper, 악성코드 다운로더 및 실행기)를 실행하기 위해 존재합니다. SafeDep의 Miasma 소스-리포지토리 분석은 전체 사건, 드로퍼의 내부 구조, 그리고 영향을 받은 121개의 저장소를 문서화하고 있습니다. 이 글은 범위를 좁혀 설정 파일 자체의 공격 표면(Attack surface)에 초점을 맞춥니다.
드로퍼(The dropper) 드로퍼인 .github/setup.js는 4,348,254바이트 크기이며, try/catch 블록 안에 단 하나의 구문으로 이루어져 있습니다. 이 크기는 단순한 패딩(용량 채우기)이 아닙니다. 이 파일은 암호화된 페이로드(악성코드 본체)를 포함하고 있으며, GitHub 코드 검색 기능이 인덱싱을 중단하는 대략 384KB의 한계를 넘어서도록 설계되었습니다. 따라서 저장소가 검색에 노출될 때 작은 실행 파일들이 주로 걸리게 됩니다.
파일의 첫 부분은 다음과 같습니다: 문자 코드 배열에 대한 시저 암호(Caesar shift) 변환을 통해 eval() 함수에 데이터를 공급합니다. 이를 정적으로 디코딩(실행하지 않고 시프트 값 4를 적용)하면, 자격 증명(credential)을 탈취하는 스테이지별 Bun 로더가 AES 복호화를 통해 드러납니다. 이 탈취 악성코드는 AWS, Azure, GCP, Vault, Kubernetes, npm 및 GitHub의 비밀 키(Secrets)를 스캔한 뒤, 공격자가 만든 공개 GitHub 저장소로 이를 유출합니다. 해당 디코딩 과정은 Miasma 난독화 해제 분석 글에서 확인할 수 있습니다.
이러한 난독화 형태는 이 커밋이나 Miasma 웜에만 국한되지 않습니다. 작은 회전 함수(rotational function)로 디코딩된 숫자 배열이 eval()에 전달되고, 그 안에 암호화된 두 번째 단계를 래핑하는 구조는 SafeDep이 이 웜의 여러 파동과 관련 없는 악성 패키지 캠페인에서 계속해서 재컴파일되는 것을 발견한 전형적인 악성코드 프레임워크입니다. 빌드마다 회전량과 암호화 키가 변경되어 구조는 동일해도 파일 해시값은 변경됩니다. 내부의 페이로드는 다를 수 있지만, 호스트 시스템에 침투하는 방식은 변하지 않습니다.
7개의 설정 파일 중 어느 것도 페이로드 자체를 포함하지 않습니다. 대신 각 파일은 'node .github/setup.js'라는 동일한 문자열을 담고 있으며, 개발자의 도구가 나머지 실행을 알아서 처리하도록 방치합니다.
7개의 실행 파일(The seven launchers) 5개의 실행 파일은 위에서 언급한 mantine-datatable의 단일 커밋에서 비롯되었습니다. 추가로 Composer와 Bundler 관련 파일은 동일한 캠페인의 다른 저장소에서 발견되었으며, 이로 인해 해당 커밋의 6개 파일을 넘어 총 7개의 실행 파일로 늘어나게 되었습니다.
아래의 각 설정은 고정된 커밋 SHA를 가진 소스 코드를 기반으로 하며, 브랜치가 초기화되더라도 퍼링링크(permalink)는 ref가 아닌 blob을 직접 가리키므로 유지됩니다.
Claude Code와 Gemini CLI는 SessionStart 훅(hook)을 사용합니다. 두 에이전트는 프로젝트에서 세션이 열릴 때 셸 명령을 실행합니다. 두 파일은 바이트 단위로 완전히 동일합니다. 출처: .claude/settings.json, .gemini/settings.json. 폴더에서 에이전트를 시작하면 개발자가 프롬프트를 입력하기도 전에 드로퍼가 실행됩니다.
Cursor는 항상 적용되는 규칙(always-applied rule)을 사용합니다. Cursor에는 셸 훅이 없으므로 공격자는 에이전트에게 파일을 실행하도록 지시하는 프로젝트 규칙을 사용했습니다. 이는 저장소에 커밋된 프롬프트 인젝션(명령어 주입)의 일종입니다. 출처: .cursor/rules/setup.mdc. 이 규칙은 프로젝트 내의 모든 Cursor 대화에 로드되며, 어시스턴트에게...