주요 내용
- 공개 PyPI는 패키지가 어떤 기능을 하는지보다는 누가 패키지를 게시했는지를 확인합니다.
- SecureChain은 패키지가 사용자의 환경에 도달하기 전에 이를 검사하고 재구성합니다.
- 재빌드된 아티팩트에는 새로운 해시 값이 할당되므로, 고정된 요구사항 파일을 다시 생성해야 합니다.
- 설치 후에도 전체 종속성 트리를 대상으로 패치 작업이 계속 진행됩니다.
공개 PyPI는 업로드된 파일이 해당 프로젝트에 대해 인증 및 권한 부여를 받았는지 확인하고, 업로드된 각 파일에 대한 해시 값을 공개합니다. 하지만 PyPI는 해당 파일이 가리키는 소스 저장소에서 비롯된 것임을 증명하기 위해 파일을 재구성하거나, 코드가 실행해도 안전한지 여부를 검토하지는 않습니다. 이러한 한계 때문에 사람들은 대개 PyPI의 대안을 찾게 되는데, 이는 단순히 패키지의 출처뿐만 아니라 패키지가 어떤 기능을 하는지까지 평가해 주는 서비스를 의미합니다.
이 게시물에서는 SecureChain for Python이 설치 후 어디에서 검사를 수행하고, 재빌드하며, 패치를 지속적으로 적용하는지, 그리고 그 적용 범위가 어디까지인지에 대해 단계별로 설명합니다.
공개 PyPI가 확인하는 사항과 PyPI 대체 서비스가 제공하는 추가 기능
pip가 공개 PyPI에서 패키지를 설치할 때는 몇 가지 간단한 검사만 수행되며, 이 중 어느 것도 패키지가 사용자의 환경에 설치된 후 어떤 동작을 할지 확인하지 않습니다.
릴리스를 게시하려면 인증된 유지관리자 계정이 필요합니다. 업로드된 모든 파일에는 게시된 해시가 부여되며, pip는 인덱스에서 제공받은 해시와 다운로드된 파일을 대조하여 전송 과정에서 파일이 손상되었는지 확인합니다. 하지만 이 해시는 동일한 출처의 파일과 함께 전달되기 때문에, 파일 내부의 내용을 누군가가 검토했다는 증거라기보다는 아티팩트가 손상되지 않은 상태로 도착했음을 증명하는 것에 불과합니다. 해시 검증 모드 이것이 더 엄격한 선택지입니다. 왜냐하면 --require-hashes 인덱스에서 제공된 해시는 해당 요건을 충족할 수 없습니다. 해당 아티팩트는 사용자가 로컬에서 승인한 해시와 일치해야 합니다.
Trusted Publishing에 참여하는 유지보수자는 정적 API 토큰 대신 유효 기간이 짧은 OIDC 자격 증명을 받게 되며, PyPI는 아티팩트를 게시자 신원과 암호학적으로 연결하고, SLSA 출처 정보를 통해 원본 위치와도 연결하는 디지털 증명서를 제공할 수 있습니다. 이는 유용한 출처 신호이지만, 그 어느 것도 아티팩트 자체가 무해하다는 사실을 입증하지는 못합니다. 승인된 게시 자격 증명이나 워크플로가 침해되면, 인증만으로는 해당 경로를 통해 전달된 내용에 대해 아무것도 알 수 없으며, PyPI의 보고 및 격리 프로세스는 일반적으로 아티팩트가 공개되기 전에 선별하는 것이 아니라, 악성 릴리스가 이미 설치 가능한 상태가 된 후에야 작동합니다.
단순히 패키지 다운로드 경로만 변경하는 보안이 강화된 PyPI 미러 역시 동일한 취약점을 그대로 물려받게 됩니다. 이는 파이썬에만 국한된 문제도 아닙니다. 이는 많은 공급망 관리 도구가 결코 메우지 못하는 사각지대인데, 패키지를 해시 값이나 CVE 피드와만 비교할 뿐, 실제로 실행되었을 때 어떤 동작을 하는지에 대해서는 전혀 검증하지 않기 때문입니다.
출산 후가 아니라 출산 전에 선별 검사를 실시해야 합니다
SecureChain은 패키지가 빌드 에이전트나 개발자의 컴퓨터로 전달되기 전에 해당 패키지의 소스 코드를 분석하여 의심스러운 행동을 식별하고, 검사에서 불합격된 항목은 차단합니다. 이는 대부분의 도구가 작동하는 파이프라인 단계와는 다른 지점입니다. 의존성이 해결되거나 설치된 후에야 이를 확인하는 스캐너는 이미 환경에 존재하는 요소에 대해서만 보고할 뿐입니다. 전달 전 선별을 통해 검사에서 불합격된 패키지는 소비 환경에 도달하지 못하게 됩니다.
2026년에 발생한 세 가지 사건은 그 시기가 왜 중요한지를 보여준다. LiteLLM은 지난 3월 자격 증명 탈취 코드를 배포했다 자체 CI 파이프라인에서 문제가 발생한 Trivy 빌드로 인해 프로젝트의 PyPI 토큰이 유출된 이후. 지난 5월, durabletask, 마이크로소프트의 ‘Durable Task SDK’는 GitHub Actions의 비밀 정보에서 유출된 API 토큰을 통해 해킹당했습니다. 그리고 6월에는, Hades가 PyPI에 등록되었습니다 37개의 악성 휠 아티팩트가 포함되어 있으며, Orca에서는 26개, Socket에서는 19개의 패키지로 집계되었습니다. 해당 휠들은 .pth 임포트 시가 아닌 인터프리터가 시작될 때 실행되는 시작 후크로, 이는 3개월 전 LiteLLM의 악성 배포본 중 하나가 사용했던 것과 동일한 수법이다.
이 세 가지 사례 모두 해당 패키지를 사용하는 애플리케이션에 버그가 있을 필요는 없었습니다. 각각은 승인된 배포 경로를 따랐으며, 인증을 통해 해당 경로가 승인되었는지 확인할 수는 있지만, 그 경로를 통해 전달되는 아티팩트가 안전한지 여부에 대해서는 아무것도 말해 주지 않습니다. 동일한 캠페인 계보는 npm을 통해 확산된 Shai-Hulud 사례로도 거슬러 올라가는데, 바로 이것이 핵심입니다. 배포 전 검사는 어떤 생태계에서든 이러한 패턴이 나타날 때 이를 포착하기 위해 존재하는 것입니다.
소스 코드로부터 재컴파일하기, 그리고 이를 통해 실제로 검증되는 사항
대부분의 파이썬 패키지는 PyPI가 해당 패키지가 유래했다고 주장하는 소스 저장소와 대조하기 위해 독립적으로 재컴파일하지 않는 배포 아티팩트를 통해 설치됩니다. 패키징 시스템 내에는 휠이 소스와 일치하도록 강제하는 메커니즘이 없기 때문에, 휠은 일관된 해시 값을 가지고 문제없이 설치될 수 있으면서도, 연결된 저장소에는 존재하지 않는 코드를 포함할 수 있습니다.
SecureChain은 통제된 환경에서 검증된 소스 코드를 바탕으로 패키지를 재빌드하므로, 설치된 패키지는 불투명한 업스트림 바이너리가 아닌 해당 소스 코드로 거슬러 올라갈 수 있습니다. 이러한 검증을 통해 입증되는 내용은 패키지의 종류에 따라 다릅니다.
| 유물 유형 | 포함 사항 | 재현 정확도 |
|---|---|---|
| 순수 파이썬 휠 | 소스 파일, 컴파일 없음 | 메타데이터와 타임스탬프가 다르긴 하지만, 꽤 잘 일치합니다. |
| 컴파일된 확장 기능이 포함된 휠 | C, C++ 또는 Rust로 빌드된 플랫폼별 바이너리 | 바이트 단위로 비교할 수 없습니다. 동일한 소스 코드라도 툴체인에 따라 컴파일 결과가 다를 수 있기 때문입니다. |
| 소스 배포판 | 소스 트리와 패키징 및 빌드 메타데이터 | 비교할 만한 가장 대표적인 사례는, 빌드 백엔드가 빌드 중에 코드를 실행할 수 있다는 점입니다. |
순수한 파이썬 휠에는 파이썬 소스 코드와 생성된 패키징 메타데이터만 포함되어 있으며, 컴파일된 바이너리는 포함되어 있지 않으므로, 재빌드된 결과물과 공개된 휠을 비교하는 작업은 대부분 파일 수준에서 이루어집니다. C, C++ 또는 Rust 확장 모듈로 빌드된 휠은 특정 툴체인과 플랫폼에 종속된 컴파일된 출력물이며, 이를 재빌드하면 동일한 바이너리가 아닌 다른 바이너리가 생성됩니다. 이는 실질적인 한계이며, 데이터 또는 ML 팀이 설치하는 패키지의 상당 부분에 해당합니다. 소스 배포판은 그 중간에 위치하며, 일상적으로는 덜 흔하지만 소스와 빌드 입력 요소를 직접 노출하므로 재빌드 시 활용할 수 있는 정보가 가장 많습니다.
따라서 파이썬에 대한 정직한 관점은 균일하기보다는 단계적이라고 볼 수 있습니다. 순수 파이썬의 경우 강력하고, sdists의 경우 구조적으로 유용하며, 컴파일된 확장 기능이 포함된 경우에는 그 효과가 약해집니다. PyPI 전체에 걸쳐 바이트 단위의 정확한 재구성이 가능하다고 주장하는 것은, 일단 컴파일된 출력이나 환경에 의존하는 출력이 포함되면 재구축을 통해 입증할 수 있는 범위를 과대평가하는 것입니다.
재구성이 고정된 해시에 미치는 영향
해시 고정(Hash pinning)은 각 종속성을 명시적으로 승인된 하나 이상의 아티팩트 해시로 제한합니다.
pip-compile --generate-hashes requirements.in
pip install --require-hashes -r requirements.txt
이러한 해시 값은 특정 파일에 연결되어 있습니다. SecureChain은 PyPI의 원본 아티팩트를 다시 제공하는 대신 소스 코드부터 패키지를 재빌드하기 때문에, 기본 소스 코드가 정확히 일치하더라도 재빌드된 패키지의 해시 값은 PyPI에 게시된 해시 값과 달라집니다. 따라서 SecureChain이 재빌드하는 패키지를 포함하는 모든 핀을 새로운 인덱스에 맞춰 다시 생성해야 하므로, 전환 과정에는 실질적인 마이그레이션 단계가 수반됩니다.
파일럿 진행 중에 처리하기보다는 파일럿을 시작하기 전에 미리 계획을 세워 두세요. 먼저 단일 리포지토리에서 새로운 인덱스를 기준으로 요구사항 파일을 다시 생성하고, 설치가 성공적으로 완료되었는지 및 애플리케이션이 이전과 동일하게 작동하는지 확인한 다음, 현재 고정된 해시를 사용하는 리포지토리와 파이프라인의 수를 기준으로 배포 범위를 결정하세요. 해시 고정이라는 개념 자체가 아니라, 그 목록의 규모가 마이그레이션의 실제 규모를 결정합니다.
URL은 하나, 두 개가 아님
SecureChain에서 pip을 설정하는 방법은, SecureChain이 귀하의 계정에 제공하는 엔드포인트를 사용하는 단 하나의 설정으로 귀결됩니다:
pip config set global.index-url https://<your-securechain-endpoint>/simple/
사용 index-url, 아니라 extra-index-url. pip는 패키지가 어느 인덱스에서 제공되어야 하는지에 대한 개념을 가지고 있지 않습니다. 두 인덱스 모두 동일한 패키지 이름에 대해 호환되는 후보를 제공하는 경우, pip는 이를 종합적으로 고려하여 버전이 더 높은 공개 후보를 선택할 수 있는데, 이것이 바로 의존성 혼동 위험입니다. --extra-index-url 생성합니다.
문서 내용을 무턱대고 믿기보다는 CI 러너에 실제로 어떤 설정이 되어 있는지 확인해 보세요. PIP_INDEX_URL 그리고 PIP_EXTRA_INDEX_URL 환경에서 설정, 자동으로 재정의 pip.conf, 그리고 다른 팀에서 물려받은 기본 이미지는 그러한 요소가 눈치채지 못한 채 슬쩍 끼어들게 되는 흔한 방법 중 하나입니다.
pip config list
pip config get global.index-url
설치 후에 수정되며, 단순히 설치 전에 검토만 하는 것이 아닙니다.
스크리닝은 패키지가 전달되는 시점의 상태를 확인합니다. 종속성의 수명 주기 대부분은 그 이후에 이루어지므로, 나중에 문제가 있음을 발견하는 것은 실제로 실행 중인 버전에 대한 패치된 빌드를 확보하는 것과는 별개의 작업입니다.
단 하나의 최상위 패키지만으로도 pip의 리졸버를 통해 수십 개의 전이적 종속성을 불러올 수 있는데, 이 중 대부분은 팀 내 누구도 직접 검토하지 않은 것이며, 이들 중 어느 하나라도 환경에 실행 가능한 코드와 새로운 취약점을 유입시킬 수 있습니다. SecureChain은 단순히 표시되는 항목뿐만 아니라 전체 종속성 그래프 전반에 걸쳐 동일한 검증, 선별 및 패치 작업을 적용합니다. requirements.txt.
이미 운영 환경에 배포된 패키지에 대한 새로운 CVE가 누구도 통제할 수 없는 일정에 따라 공개됩니다. 이를 알려주는 도구는 많습니다. SecureChain은 이러한 공개 내용을 모니터링하고, 이미 고정된 버전에 맞춰 수정 사항을 제공합니다. 이는 pip-audit 출력 결과나 수동으로 보안 권고 사항을 검토하는 방식으로는 해결되지 않는 부분입니다.
결국 유지보수 담당자가 릴리스를 중단하게 됩니다. 공개 PyPI는 기존 릴리스를 계속 호스팅하지만, 그 시점부터는 이후에 어떤 문제가 발생하더라도 새로운 패치는 제공되지 않습니다. TuxCare의 ‘Endless Lifecycle Support’는 SecureChain을 지원하여, 카탈로그에 이미 등록된 패키지에 대한 수정 사항을 역포팅하므로, 팀은 패치가 적용되지 않은 종속성과 아무도 계획하지 않았던 재작성 사이에서 선택을 강요받지 않아도 됩니다.
SecureChain을 사용자의 Python 스택과 비교하여 확인하기
자신의 의존성 트리를 추출하여, 순수 파이썬과 컴파일된 확장 기능의 비율을 파악하고, 이미 해시 확인 모드로 실행 중인 저장소가 몇 개인지 확인해 보세요. 이것이 바로 SecureChain for Python으로 전환하는 것이 여러분의 스택에 미칠 실제 영향이며, 단순한 추정이 아닙니다.
SecureChain을 파이썬 패키지 보안 도구로 평가할 때는 사양서보다는 이러한 구체적인 사항들을 귀사의 파이프라인과 비교해 보는 것이 핵심입니다. 여기서 다룬 취약점이 귀사의 최근 사고 검토 결과와 일치한다면, SecureChain for Python에 대한 견적을 요청하고 귀사의 의존성 트리에 대한 카탈로그 지원 범위에 대해 명확한 답변을 받아보시기 바랍니다.
자주 묻는 질문
PyPI의 대안은 무엇인가요?
개발자와 빌드 시스템에 공개 PyPI 대신 파이썬 패키지를 제공하는 인덱스입니다. 일부 옵션은 단순히 업스트림 패키지를 미러링하거나 캐시하는 반면, 다른 옵션은 심사 및 재빌드와 같은 제어 기능을 추가하여 단순한 미러링만으로는 메울 수 없는 취약점을 보완합니다.
pip install 명령어로 악성 코드를 실행할 수 있나요?
네. 소스 배포판을 설치하면 패키지 빌드 중에 패키지 관리 시스템이 제어하는 빌드 백엔드 코드를 실행할 수 있으며, 설치된 패키지는 나중에 임포트 시점에 코드를 실행하거나 실행 파일과 같은 시작 메커니즘을 통해 코드를 실행할 수 있습니다. .pth Hades 휠이 악용했던 바로 그 파일입니다. SecureChain은 배포 전에 패키지의 소스 코드를 분석하여 의심스러운 동작을 식별하고, 악성 패키지가 환경에 도달하기 전에 차단합니다.
재빌드된 패키지는 여전히 업스트림 소스와 일치하나요?
이는 패키지 유형에 따라 다릅니다. 순수 파이썬 휠에는 컴파일된 바이너리가 포함되어 있지 않으므로, 재빌드한 결과물을 공개된 휠과 파일 수준에서 상당히 직접적으로 비교할 수 있습니다. 반면, 컴파일된 C, C++ 또는 Rust 확장 기능이 포함된 휠은 플랫폼별 빌드 산출물이며, 소스 코드가 동일하더라도 재빌드하면 일치하는 바이너리가 아닌 다른 바이너리가 생성됩니다.
SecureChain으로 전환하면 고정된 해시 값이 깨지게 되나요?
네, 어떤 패키지든 재빌드됩니다. SecureChain은 PyPI의 원본 아티팩트를 다시 제공하는 대신 재빌드된 패키지를 제공하므로, 소스 코드가 동일하더라도 해시 값은 공개 PyPI에 게시된 것과 다릅니다. 고정된 요구 사항 파일을 다시 생성하는 것은 마이그레이션 과정에서 필수적인 단계이며, 예외적인 경우가 아닙니다.
SecureChain은 requirements.txt에 명시된 항목뿐만 아니라 전이적 의존성도 다루나요?
네. 단 하나의 직접적인 종속성만으로도 아무도 직접 검토하지 않은 수십 개의 패키지가 유입될 수 있으며, 이러한 패키지들은 실행 가능한 코드와 취약점을 환경에 유입시킬 수 있습니다. SecureChain은 팀이 나열한 최상위 패키지뿐만 아니라 전체 종속성 그래프 전반에 걸쳐 검증, 선별 및 패치 작업을 수행합니다.
파이썬 패키지의 지원 종료 시에는 어떤 일이 발생하나요?
아무것도 없습니다. 바로 이것이 문제입니다. 공개 PyPI는 해당 패키지를 삭제하거나 지원 중단으로 표시하지 않으며, 다른 이유로 삭제되거나 격리되지 않는 한 이전 릴리스는 대개 계속 다운로드할 수 있습니다. 중단되는 것은 업스트림 측인데, 유지보수자가 손을 떼면 해당 패키지에 대한 보안 패치가 더 이상 제공되지 않습니다. SecureChain은 그 시점 이후에도 등록된 패키지에 계속 패치를 적용하므로, 패키지가 지원 종료에 도달한다고 해서 자동으로 보안 취약점에 노출되거나 서둘러 대체 패키지를 찾아야 하는 것은 아닙니다.


