---
title: "해시는 바이트만 증명한다, Collider 1.3.0의 패키지 보안 수리기"
published: 2026-06-29T03:34:41.000Z
canonical: https://jeff.news/article/4353
---
# 해시는 바이트만 증명한다, Collider 1.3.0의 패키지 보안 수리기

Collider 1.3.0은 Meson 패키지 관리 과정에서 해시 검증만으로 막을 수 없는 두 가지 문제를 고쳤다. 저장소 인덱스의 패키지명·버전명을 경로로 쓰는 순간 생기는 경로 탐색 문제와, 인증 요청이 교차 출처 리다이렉트될 때 bearer token이 새 호스트로 넘어가는 문제가 핵심이다.

- Collider 글의 핵심은 제목 그대로임 — 해시는 “이 바이트가 lockfile에 적힌 바이트와 같다”만 증명함
  - 어떤 이름이 어떤 URL로 해석됐는지, 그 URL이 어디로 리다이렉트됐는지, 누가 게시했는지는 해시만으로 알 수 없음
  - Collider 1.3.0은 해시 검증 전에 벌어지는 두 군데 문제를 고쳤다고 설명함

## 저장소 인덱스도 입력값이다

- 첫 번째 문제는 WrapDB 호환 저장소의 `releases.json` 인덱스였음
  - 이 파일은 패키지 이름과 버전 목록을 제공함
  - 문제는 name/version이 단순 라벨이 아니라 실제 파일 경로 일부가 된다는 점임
  - 예를 들면 `subprojects/<name>.wrap`, `~/.config/collider/cache/wraps/<name>_<version>.wrap` 같은 식임

- 악의적인 인덱스가 `../../../../etc/cron.d/x` 같은 이름을 주면 경로 탐색(path traversal)이 됨
  - Collider가 그 값을 파일명으로 쓰면 캐시 디렉터리 바깥으로 빠져나갈 수 있음
  - version 문자열도 똑같은 공격면이 됨
  - 공개 미러나 동료가 띄운 저장소 URL을 쓴다면 Collider가 제어하지 않는 서버 입력을 그대로 믿는 셈임

- 그래서 Collider는 인덱스를 파싱하는 한 지점에서 name/version을 먼저 거르도록 바꿨음
  - `packages_from_releases`가 인덱스를 패키지 엔트리로 만드는 관문이라 여기에 검증을 넣음
  - `is_safe_path_segment`는 빈 문자열, `.`, `..`, 경로 구분자, null byte를 거부함
  - `Path(value).name`이 원래 값과 달라지는 경우도 거부함

> [!WARNING]
> 패키지명과 버전명은 UI에 보이는 문자열처럼 보여도, 패키지 매니저 내부에서는 경로가 되는 순간 공격 입력이 됨.

- 읽기와 쓰기에서 실패 처리는 다르게 가져감
  - 캐시 생성이나 publish 같은 쓰기 경로에서는 traversal 이름을 발견하면 예외를 던짐
  - 검색처럼 외부 `releases.json`을 읽는 흐름에서는 잘못된 엔트리를 스킵하고 debug 로그만 남김
  - 경로를 만드는 각 sink에도 방어 검사를 남겨 defense in depth로 둠

- 아카이브 내부 파일명도 똑같이 untrusted input으로 봄
  - wrap이 가리키는 tar/zip 안에 `../../x` 같은 멤버가 있으면 추출 디렉터리 밖으로 나갈 수 있음
  - Collider는 meson.build를 스캔하기 위해 임시 추출할 때 Python tar data filter와 zip 경로 정리를 사용함
  - 빌드 시점의 실제 추출은 Meson이 담당하고, archive hash로 보호된다고 설명함

## 토큰은 리다이렉트에 따라가면 안 된다

- 두 번째 문제는 `collider publish`와 `collider unpublish`의 bearer token 처리임
  - 이 명령들은 저장소의 `_collider/v1/` 쓰기 API로 Authorization 헤더를 보냄
  - Python 기본 `urllib` opener는 리다이렉트가 발생해도 요청 헤더를 다시 보냄
  - 즉 저장소가 `302 Location: https://another-host/...`를 주면 토큰이 다른 호스트로 넘어갈 수 있었음

- Collider는 인증 호출을 `safe_urlopen`으로 보내고, 출처가 바뀌면 Authorization을 제거하게 바꿈
  - `_origin`은 scheme, host, port를 비교함
  - `https://h`와 `https://h:443`처럼 기본 포트는 같은 origin으로 정규화함
  - 같은 origin 리다이렉트면 토큰을 유지하고, 다른 origin이면 토큰을 빼서 보냄

- 이 수정은 토큰을 “안전하게” 만드는 게 아니라, 엉뚱한 호스트로 보내지 않게 하는 좁은 수리임
  - `collider serve` 자체에는 TLS가 없음
  - 정적 bearer token은 결국 전송 채널이 안전해야 의미가 있음
  - 프로덕션에서는 TLS 종료 reverse proxy를 앞에 둬야 한다고 명시함

```mermaid
sequenceDiagram
    participant 사용자
    participant 콜라이더
    participant 원본저장소
    participant 다른호스트
    사용자->>콜라이더: publish 요청
    콜라이더->>원본저장소: Authorization 포함 쓰기 API 호출
    원본저장소-->>콜라이더: 302 리다이렉트
    콜라이더->>콜라이더: 출처 변경 감지 후 Authorization 제거
    콜라이더->>다른호스트: 토큰 없는 요청
    다른호스트-->>콜라이더: 인증 실패
```

## 해시 다음은 서명이다

- Collider의 기존 hash와 origin pinning은 여전히 중요하지만 한계가 있음
  - hash는 lockfile에 기록된 바이트와 설치할 바이트가 같은지만 보장함
  - origin pinning은 lock 당시의 저장소 URL에서만 설치하게 해서 기본적인 dependency confusion을 막음
  - 하지만 저장소 자체가 뚫렸거나, 처음 lockfile을 만들 때 오염된 인덱스를 봤다면 해시는 그대로 맞아버림

- 오프라인 캐시 fallback에서도 출처 정보는 약해짐
  - 캐시는 wrap이 어떤 저장소에서 왔는지 알 수 없음
  - 그 상황에서는 content hash만 남음
  - 그래서 “어디서 왔는가”와 “누가 만들었는가”는 별개 문제로 남음

- 다음 로드맵은 signing임
  - 게시자가 wrap과 archive에 서명함
  - 소비자는 이미 신뢰하는 키로 서명을 검증함
  - 이렇게 해야 패키지가 특정 신뢰 키 보유자에게 묶이고, hash가 못 하는 provenance 검증을 할 수 있음

---

## 기술 맥락

- 이 글이 좋은 이유는 공급망 보안의 레이어를 분리해서 보여주기 때문이에요. hash는 다운로드한 바이트가 lockfile과 같은지만 확인해요. 그런데 어떤 URL을 고를지, 파일명을 어디에 쓸지, 인증 헤더를 어디까지 보낼지는 hash 전에 이미 결정돼요.

- 경로 탐색을 sink마다 막는 대신 인덱스를 파싱하는 관문에서 먼저 거른 선택도 실용적이에요. 패키지명과 버전이 여러 경로에서 재사용되기 때문에, 매번 파일을 만들기 직전에만 막으면 새 코드가 추가될 때 빠뜨리기 쉽거든요. 그래도 각 sink 검사는 남겨서 방어층을 하나 더 둔 구조예요.

- Authorization 헤더 제거는 HTTP 클라이언트 구현에서 자주 놓치는 부분이에요. 같은 출처 리다이렉트는 정상 배포 구성에서도 필요할 수 있지만, 출처가 바뀌는 순간 토큰을 따라 보내면 인증한 적 없는 서버에 권한을 넘기는 꼴이 돼요.

- 마지막으로 signing 로드맵이 중요한 건 hash와 서명이 푸는 문제가 다르기 때문이에요. hash는 무결성, origin pinning은 저장소 혼동 방지, signing은 게시자 신뢰를 담당해요. 패키지 매니저를 만들거나 사내 아티팩트 배포 시스템을 운영한다면 이 셋을 같은 기능으로 뭉개면 안 돼요.

## 핵심 포인트

- releases.json의 name/version을 신뢰하지 않고 안전한 단일 경로 세그먼트인지 먼저 검증
- 인증 API 호출에서 리다이렉트 출처가 바뀌면 Authorization 헤더를 제거
- 해시는 lockfile의 바이트 일치만 증명할 뿐, 누가 만들었는지는 증명하지 못해 signing을 다음 로드맵으로 제시

## 인사이트

패키지 매니저 보안에서 “해시 찍었으니 끝”이 얼마나 위험한 착각인지 잘 보여주는 글임. 의존성 공급망에서는 바이트 검증, 출처 고정, 경로 정규화, 토큰 전달 정책, 서명까지 각각 막는 공격면이 다르다.
