2026년 8월 20일, Rust 패키지 레지스트리 crates.io에 올라온 arrayref 0.3.10은 평소와 다른 일을 했다. 이 버전은 이름이 비슷한 proc-macro1을 새 의존성으로 추가했고, 그 패키지의 빌드 스크립트는 컴파일 중 원격 바이너리를 내려받아 실행했다. 애플리케이션을 실행하지 않아도 cargo build만으로 동작이 시작되는 구조다.
RustSec는 영향을 받는 버전을 arrayref = 0.3.10으로 특정했다. 0.3.10은 공개 약 86분 뒤 제거됐지만, 그 사이 2,285회 내려받아졌다. 오래된 Cargo.lock을 가진 프로젝트 다수는 기존 버전을 그대로 사용했지만, 새로 의존성을 해석하거나 yanked 경고를 따라 업데이트한 프로젝트는 따로 점검해야 한다.
왜 단순한 빌드가 원격 실행으로 이어졌나
arrayref는 배열 참조용 매크로를 제공하는 작은 패키지다. 0.3.9까지는 빌드 스크립트와 런타임 의존성이 없었지만, 0.3.10의 Cargo.toml에는 proc-macro1이 추가됐다. Cargo는 소스에서 그 라이브러리를 직접 호출하지 않아도 선언된 일반 의존성을 빌드한다. 의존성 항목 한 줄이 컴파일 경로를 바꾼 이유다.

문제의 proc-macro1 1.0.107은 널리 쓰이는 proc-macro2의 소스 코드를 복사해 빌드가 정상적으로 끝나는 것처럼 보이게 했다. 악성 동작은 라이브러리 코드가 아니라 build.rs에 들어 있었다. SafeDep 분석에 따르면 이 스크립트는 주소를 Base64 조각으로 나눠 다시 조합하고, 인증서를 검증하지 않는 TLS 연결로 대상별 바이너리를 받아 별도 프로세스로 실행했다. Unix에서는 /tmp/rust-setup, Windows에서는 임시 폴더의 PowerShell 스크립트와 VBScript 런처를 사용했다.
내 프로젝트는 어떤 버전을 잠갔는지 어떻게 찾나
첫 확인 대상은 Cargo.toml보다 Cargo.lock이다. 잠금 파일은 실제 빌드에 해결된 버전을 기록한다. 저장소에서 arrayref 0.3.10이 보이면 cargo tree -i arrayref로 어떤 패키지가 이를 끌어왔는지 역으로 확인한다. 그래픽 라이브러리 경로에서는 tiny-skia, sctk-adwaita, winit을 통해 간접 의존성이 생길 수 있었다.

arrayref = 0.3.10이 확인되면 의존성을 0.3.9 이하로 되돌리고 잠금 파일을 다시 만든다. 단순히 최신 버전으로 갱신하는 방식은 여기서 맞지 않는다. RustSec는 수정된 후속 버전이 아니라 0.3.9 이하를 영향 없음으로 분류했다. 패키지 버전을 고친 뒤에는 그 버전을 실제로 빌드했던 개발 환경과 CI 실행기를 별도 대상으로 잡아야 한다.
0.3.10을 빌드한 컴퓨터에서는 어디를 봐야 하나
빌드 이력이 있는 호스트에서는 의존성 교체와 실행 흔적 조사를 함께 진행한다. 공개 분석에 나온 네트워크 주소는 23.254.165.112:9089과 23.254.165.112:443이다. 파일 지표는 Unix의 /tmp/rust-setup, Windows의 rust-setup.ps1과 rust-setup-launch.vbs다. 이 지표가 있거나 해당 통신이 확인된 호스트는 먼저 분리하고 빌드·네트워크 기록을 보존한 뒤, 그 호스트에서 접근 가능한 배포 키와 자격 증명의 교체 범위를 정한다.

코드 리뷰는 함수 호출만 보지 말고 Cargo.toml의 diff, 잠금 파일에 실제로 해결된 버전, 새 패키지의 build.rs와 빌드 의존성도 함께 확인해야 한다. 특히 토큰 처리처럼 네트워크가 필요 없는 패키지에 HTTP 클라이언트·TLS·디코더가 새로 붙으면 빌드 단계부터 조사한다.
