PEP 763 – PyPI에서의 삭제 제한
- Author:
- William Woodruff <william at yossarian.net>, Alexis Challande <alexis.challande at trailofbits.com>
- Sponsor:
- Donald Stufft <donald at stufft.io>
- PEP-Delegate:
- Donald Stufft <donald at stufft.io>
- Discussions-To:
- Discourse thread
- Status:
- Withdrawn
- Type:
- Standards Track
- Topic:
- Packaging
- Created:
- 24-Oct-2024
- Post-History:
- 09-Jul-2022, 01-Oct-2024, 28-Oct-2024
- Resolution:
- 21-Sep-2025
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
PEP 철회
이 PEP는 2025년 9월 22일부로 철회되었습니다.
PEP를 논의하는 과정에서, PyPI의 이용 정책은 현재 PEP 프로세스의 일부가 아니며 반드시 그래야 하는 것도 아니므로, PEP가 PyPI의 삭제 정책 변경을 다루기에 반드시 적절한 자리는 아니라는 점이 분명해졌습니다.
초록
사용자가 PyPI에서 파일, 릴리스, 프로젝트를 삭제할 수 있는 시점을 제한할 것을 제안합니다. 프로젝트, 릴리스, 파일은 색인에 업로드된 시점으로부터 72시간 이내에만 삭제할 수 있습니다. 그 이후부터 사용자는 PEP 592에 명시된 “yank” 메커니즘만 사용할 수 있습니다.
이 제한의 예외로, 사전 릴리스 지정자로 표시된 릴리스와 파일은 언제든지 계속 삭제할 수 있습니다. PyPI 관리자는 예를 들어 운영(moderation)이나 보안상의 목적으로 언제든지 파일, 릴리스, 프로젝트를 삭제할 수 있는 권한을 계속 보유합니다.
근거와 동기
PEP 592에서 언급한 바와 같이, PyPI에서 사용자 수준의 프로젝트 삭제를 허용하면 의존성 파손이라는 진퇴양난(catch-22)의 상황이 발생합니다:
프로젝트가 PyPI의 특정 릴리스가 망가져 있을 수 있음을 감지할 때마다, 더 많은 사용자가 의도치 않게 그 버전을 사용하는 것을 막고 싶어 하는 경우가 많습니다. 그러나 저장소에서 기존 파일을 삭제하는 뻔한 해결책은 프로젝트의 특정 버전에 고정(pin)해 둔 사용자를 망가뜨립니다.이로 인해 프로젝트는 진퇴양난의 상황에 놓이게 됩니다. 새 프로젝트가 망가진 것으로 알려진 이 버전을 내려받고 있을 수 있지만, 이를 막기 위해 무언가를 하면 이미 그 버전을 사용하고 있는 프로젝트를 망가뜨리게 됩니다.
기술적인 측면에서, 삭제 문제는 마찬가지로 PEP 592에 명시된 “yank”를 통해 완화됩니다. 그러나 PyPI에서는 여전히 삭제가 허용되고 있으며, 그동안 여러 해에 걸쳐 Python 생태계에 몇 차례 주목할 만한 혼란을 일으켰습니다:
- 2022년 7월: atomicwrites는 프로젝트의 “critical” 지정을 해제하려는 시도로 유지 관리자에 의해 삭제되었는데, 유지 관리자는 프로젝트를 삭제하면 이전에 업로드된 모든 릴리스도 함께 삭제된다는 사실을 깨닫지 못했습니다.
이후 유지 관리자의 동의를 얻어 프로젝트가 복구되었지만, 그 대가로 관리자가 수동으로 조치해야 했고 pytest 같은 프로젝트에 광범위한 다운스트림 장애가 발생했습니다. 2024년 10월 기준으로 atomicwrites는 보관(archived) 상태이지만 여전히 PyPI에서 월 약 450만 회의 다운로드를 기록하고 있습니다.
- 2023년 4월: codecov는 오랜 폐지 기간을 거친 후 유지 관리자들에 의해 삭제되었습니다. 이로 인해 Codecov의 많은 CI/CD 사용자에게 광범위한 장애가 발생했는데, 이들은 CI/CD 로그 안에서 폐지 경고를 확인하기 어려웠던 탓에 폐지 기간이 있었다는 사실을 알지 못했습니다.
이후 유지 관리자들이 프로젝트를 다시 생성하고 (복구되지 않은) 삭제된 릴리스를 대신하기 위해 새 릴리스를 게시했지만, 이는 버전을 고정한 설치가 여전히 깨진 상태로 남았다는 것을 의미했습니다. 2024년 10월 기준으로 이 단일 릴리스는 여전히 PyPI에 있는 유일한 릴리스이며, 월 약 150만 회의 다운로드를 기록하고 있습니다.
- 2023년 6월: python-sonarqube-api는 2.0.2 이전에 배포된 모든 릴리스를 삭제했습니다.
이후 프로젝트의 유지 관리자는 대화를 삭제하고
python-sonarqube-api의 소스 저장소의 태그 기록을 강제 푸시로 덮어써서, 사용자들이 릴리스 간 변경 사항을 비교하려는 노력을 방해했습니다. - 2024년 6월: PySimpleGUI는 라이선스를 변경하고 이전 릴리스를 거의 전부 삭제했습니다. 이로 인해 (라이선스 변경 이전에) PySimpleGUI를 하루에 약 25,000회 다운로드하던 사용자들에게 광범위한 혼란이 발생했습니다.
삭제는 다운스트림에 혼란을 일으킬 뿐 아니라, PyPI의 지속 가능성과 생태계 전반의 보안에도 해로운 영향을 미칩니다:
- 사용자들이 PyPI가 고장 났거나 관리자가 직접 프로젝트를 제거했다고 잘못 믿고 지원 요청을 제출하기 때문에, 삭제는 PyPI 관리자와 모더레이터의 지원 업무 부담을 늘립니다.
- 삭제는 외부(즉, 최종 사용자)의 사고 대응과 분석을 저해하여, “선의의” 유지 관리자 행동과 자신의 흔적을 감추려는 악의적 행위자를 구별하기 어렵게 만듭니다.
Python 생태계는 계속 성장하고 있으므로, 앞으로 일어날 프로젝트 삭제는 위에서 예로 든 삭제들과 그 이상은 아니더라도 적어도 그만큼 큰 혼란을 일으킬 것이라고 합리적으로 가정할 수 있습니다.
이상의 모든 점을 고려하여, 이 PEP는 이제 삭제가 Python 생태계에 주는 이익보다 위험과 해악이 더 크다고 결론짓습니다.
이러한 기술적 인자 외에도, 다른 패키징 생태계에는 사용자가 프로젝트와 그 구성 릴리스를 삭제하는 기능을 제한한 선례도 있습니다. 이 선례는 부록 A에 정리되어 있습니다.
명세
삭제할 수 있는 객체에는 세 가지 유형이 있습니다:
- 파일은 개별 프로젝트 배포판(소스 배포판이나 휠 등)입니다.
예:
requests-2.32.3-py3-none-any.whl. - 릴리스는 같은 버전 번호를 공유하는 하나 이상의 파일을 포함합니다.
예: requests v2.32.3.
- 프로젝트는 하나 이상의 릴리스를 포함합니다.
예: requests.
삭제 자격 규칙
이 PEP는 다음과 같은 삭제 자격 규칙을 제안합니다:
- 파일은 PyPI에 업로드된 지 현재 시각 기준으로 72시간이 지나지 않은 경우, 또는 사전 릴리스 지정자가 있는 경우에만 삭제할 수 있습니다.
- 릴리스는 포함된 파일이 모두 삭제 가능한 경우에만 삭제할 수 있습니다.
- 프로젝트는 모든 릴리스가 삭제 가능한 경우에만 삭제할 수 있습니다.
이 규칙에 따르면 새 프로젝트는 통째로 삭제할 수 있고, 오래된 프로젝트도 새 파일이나 릴리스는 삭제할 수 있지만, 오래된 프로젝트의 오래된 파일이나 릴리스는 삭제할 수 없습니다.
구현
이 PEP의 구현은 주로 웹 인터페이스나 로그인한 사용자의 작업처럼 표준화되어 있지 않고 표준화 대상도 아닌 PyPI의 측면에 관한 것입니다. 따라서 이 절에서는 구현을 동작 측면에서 설명합니다.
변경 사항
- 위의 자격 규칙에 따라, 해당 객체가 삭제 자격을 갖추지 못한 경우 PyPI는 파일, 릴리스, 프로젝트 삭제를 위한 웹 인터페이스 요청을 (PyPI가 선택한 적절한 HTTP 응답 코드로) 거부합니다.
- PyPI는 웹 인터페이스를 수정하여 파일/릴리스/프로젝트를 삭제할 수 없음을 표시합니다. 예를 들어 관련 UI 요소를 “비활성” 스타일로 표시하고 관련 버튼/폼을 클릭할 수 없게 합니다.
보안 영향
이 PEP는 제안된 방식과 관련된 보안상 부정적인 영향을 찾지 못했습니다.
이 PEP는 사소하지만 긍정적인 보안 영향 하나를 확인합니다. 사용자가 제어하는 삭제를 제한함으로써, 이 PEP는 악의적 행위자가 색인에서 악성 코드를 삭제하여 자신의 흔적을 감추기 더 어렵게 만듭니다. 이는 외부(즉, PyPI 관리자가 아닌 측)의 분류 및 사고 대응에 특히 유용합니다. 이러한 작업에서는 방어하는 쪽이 침해 지표를 개발하기 위해 악성 코드 샘플에 쉽게 접근할 수 있어야 하기 때문입니다.
교육 방법
이 PEP는 더 넓은 Python 패키징 커뮤니티(와 그 다운스트림 사용자)가 변경 사항을 이해하도록 돕기 위해 최소 두 가지의 공개 자료를 제안합니다:
- PEP의 성격과 동기, 그리고 PyPI에 미치는 동작상의 영향을 설명하는 PyPI 블로그 공지 게시물입니다.
- 위 게시물로 연결되는 PyPI 자체의 공지 배너입니다.
- 삭제와 얀킹(yanking)의 차이, 그리고 패키지 소유자가 여전히 삭제를 시작할 수 있는 제한된 조건을 설명하는 PyPI 사용자 문서 업데이트입니다.
기각된 아이디어
의존성 관계에 따른 삭제 조건 설정
시간 기반 삭제 기간(deletion windows)의 대안으로, 다운스트림 의존 프로젝트를 기준으로 삭제 자격을 정하는 방식이 있습니다. 예를 들어, PyPI에서 다운스트림 의존 프로젝트가 N개 미만인 경우에만 릴리스를 삭제할 수 있다고 볼 수 있습니다. 여기서 N은 1까지 낮출 수 있습니다.
이 아이디어는 삭제 가능 여부를 삭제가 미치는 파급 효과와 직접 연결한다는 점에서 매력적입니다. npm은 이 방식을 사용합니다. npm은 색인이 알고 있는 다운스트림 의존성이 전혀 없을 때에만 프로젝트 제거를 허용합니다.
이러한 매력에도 불구하고, 이 PEP는 의존성 조건부 삭제가 PyPI에 적합하지 않은 이유로 몇 가지 단점과 기술적 한계를 제시합니다:
- PyPI는 의존성 관계를 알지 못합니다. Python 패키징에서는 프로젝트 빌드와 메타데이터 생성 모두 프로젝트가 지정한 임의의 코드를 실행하는 동적 작업인 경우가 많습니다. 대표적인 예가
setup.py스크립트를 포함하는 소스 배포판입니다. 여기서는setup.py를 실행해야 프로젝트 메타데이터에 기록되는 의존성 집합이 계산됩니다.이는 npm이나 Rust의 crates 같은 생태계와 뚜렷하게 대조됩니다. 이들 생태계에서는 프로젝트 빌드는 동적일 수 있지만 프로젝트 메타데이터 자체는 정적입니다.
이로 인해 PyPI는 여러분 프로젝트의 의존성을 알지 못하며, 임의의 코드를 실행하거나(심각한 보안 위험) PEP 517 및 PEP 621 방식의 정적 메타데이터로 전환하기 위해
setup.py기반 빌드를 장기간에 걸쳐 폐지하지 않는 한 구조적으로 이를 알 수 없습니다. - 직관적이지 않은 권한 모델이 만들어집니다. 의존성 조건부 삭제는 권력 관계를 “역전”시킵니다. 어떤 프로젝트에 의존성을 추가하는 사람은 누구든 그 프로젝트의 삭제를 막을 수 있게 됩니다.
이는 겉보기에는 합리적이지만, 예상치 못한 바람직하지 않은 결과를 만들어 내는 데 악용될 수 있습니다(일부 삭제를 허용하려는 목적에 비추어 볼 때 그렇습니다). 대표적인 예가 npm의 everything 패키지입니다. 이 패키지는 (2023년 12월 30일 기준) npm의 모든 공개 패키지에 의존하므로, 그 패키지들을 삭제할 수 없게 만듭니다.
다운로드 수에 따라 삭제 조건 부여하기
시간 기반 삭제 기간(deletion windows)의 또 다른 대안은 다운로드 횟수를 기준으로 삭제하는 것입니다. 예를 들어, 최근 기간 동안 다운로드 수가 N회 미만인 경우에만 릴리스를 삭제할 수 있다고 볼 수 있습니다.
이 방식에는 프로젝트 삭제 가능 여부를 실제 사용량과 연결한다는 장점이 있습니다. 하지만 이 PEP는 이 방식에서 몇 가지 한계를 확인했습니다:
- 생태계 다양성. Python 생태계에는 사용 패턴이 매우 다양한 프로젝트가 포함되어 있습니다. 고정된 다운로드 임계값을 쓰면 틈새 분야의 프로젝트를 제대로 고려하지 못합니다. 이런 프로젝트는 중요하더라도 다운로드 수가 본래 적습니다.
- 시간 민감성. 다운로드 수가 반드시 프로젝트의 현재 상태나 중요도를 반영하는 것은 아닙니다. 예전에 인기 있던 프로젝트는 최근 다운로드 수가 적더라도, 오래된 시스템을 유지하는 데는 여전히 꼭 필요할 수 있습니다.
- 기술적 복잡성. PyPI 내부에서 프로젝트의 다운로드 수를 조회하는 일은 간단하지 않습니다. 또한 미러나 다른 배포 시스템에서 프로젝트의 다운로드 통계를 모으는 데에도 한계가 있습니다.
부록 A: 다른 생태계의 선례
다음은 여러 패키징 생태계의 삭제 지원 여부를 정리한 표입니다. 어떤 생태계가 이 PEP와 비슷한 방식으로 사용자의 삭제 권한을 제한하면, 그 생태계는 삭제를 지원하지 않는 것으로 간주합니다.
삭제 지원 여부만 담은 이 표의 이전 버전은 2022년 7월에 Donald Stufft 등이 파이썬 토론 포럼에서 작성했습니다.
| Ecosystem (Index) | Deletion | Yanking | Notes |
|---|---|---|---|
| Python (PyPI) | ✅ [1] | ✅ [2] | Deletion currently completely unrestricted. |
| Rust (crates.io) | ❌ | ✅ [3] | Deletion by users not allowed at all. |
| JavaScript (npm) | ❌ [4] | ✅ [5] | Deletion is limited by criteria similar to this PEP. |
| Ruby (RubyGems) | ✅ [6] | ❌ | RubyGems calls deletion “yanking.” Yanking in PyPI’s terms is not supported at all. |
| Java (Maven Central) | ❌ [7] | ❌ | Deletion by users not allowed at all. |
| PHP (Packagist) | ❌ [8] | ❌ | Deletion restricted after an undocumented number of installs. |
| .NET (NuGet) | ❌ [9] | ✅ [10] | NuGet calls yanking “unlisting.” |
| Elixir (Hex) | ❌ [11] | ✅ [11] | Hex calls yanking “retiring.” |
| R (CRAN) | ❌ [12] | ✅ [12] | Deletion is limited to within 24 hours of initial release or 60 minutes for subsequent versions. CRAN calls yanking “archiving.” |
| Perl (CPAN) | ✅ | ❌ | Yanking is not supported at all. Deletion seemingly encouraged, at least as of 2021 [13]. |
| Lua (LuaRocks) | ✅ [14] | ✅ [14] | LuaRocks calls yanking “archiving.” |
| Haskell (Hackage) | ❌ [15] | ✅ [16] | Hackage calls yanking “deprecating.” |
| OCaml (OPAM) | ❌ [17] | ✅ [17] | Deletion is allowed if it occurs “reasonably soon” after inclusion.
Yanking is de facto supported by the available: false marker, which
effectively disables resolution. |
다음과 같은 경향이 나타납니다:
- 대다수의 색인이 삭제를 지원하지 않습니다 (9 대 4)
- 대다수의 색인이 yank를 지원합니다 (9 대 4)
- 압도적 다수의 색인은 둘 중 하나만 지원하거나 둘 다 지원하지 않습니다. 둘 다 지원하는 색인은 드뭅니다 (11 대 2)
- PyPI와 LuaRocks는 삭제와 yank를 모두 지원한다는 점에서 눈에 띄는 예외입니다.
각주
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.