[Draft] PEP 763 - Limiting deletions on PyPI

원문 링크: PEP 763 - Limiting deletions on PyPI

상태: Draft 유형: Standards Track 작성일: 24-Oct-2024

PEP 763 – PyPI에서 삭제 제한 (Limiting deletions on PyPI)

초록 (Abstract)

이 PEP(Python Enhancement Proposal)는 PyPI(Python Package Index)에서 사용자(user)가 파일(files), 릴리스(releases), 프로젝트(projects)를 삭제할 수 있는 시점을 제한하는 것을 제안합니다. 프로젝트, 릴리스 또는 파일은 PyPI에 업로드된 시점으로부터 72시간 이내에만 삭제할 수 있습니다. 72시간이 지난 후에는 PEP 592에 명시된 “yank” 메커니즘만 사용할 수 있습니다.

이러한 제한에는 예외가 있습니다. 사전 릴리스(pre-release) 지정자(specifier)가 표시된 릴리스와 파일은 언제든지 삭제할 수 있습니다. PyPI 관리자(administrator)는 예를 들어 중재(moderation)나 보안 목적(security purposes)으로 언제든지 파일, 릴리스, 프로젝트를 삭제할 수 있는 권한을 유지합니다.

배경 및 동기 (Rationale and Motivation)

PEP 592에서 관찰된 바와 같이, PyPI에서 사용자 수준의 프로젝트 삭제는 의존성(dependency) 손상을 야기하는 딜레마를 초래합니다.

프로젝트가 PyPI의 특정 릴리스에 문제가 있음을 감지할 때, 종종 다른 사용자가 해당 버전을 실수로 사용하는 것을 막고 싶어 합니다. 그러나 기존 파일을 저장소(repository)에서 삭제하는 명백한 해결책은 특정 버전에 고정(pinning)된 사용자에게 문제를 일으킵니다.

이는 새로운 프로젝트가 알려진 문제가 있는 버전을 계속 다운로드할 수 있지만, 이를 막으려 하면 이미 해당 버전을 사용 중인 프로젝트가 손상되는 딜레마에 빠지게 합니다.

기술적인 수준에서 삭제 문제는 PEP 592에 명시된 “yanking”으로 완화됩니다. 그러나 PyPI에서는 삭제가 계속 허용되고 있으며, 지난 몇 년 동안 Python 생태계에 여러 차례 심각한 혼란을 야기했습니다.

  • 2022년 7월: atomicwrites 삭제. 유지보수자(maintainer)가 프로젝트의 “중요(critical)” 지정(designation)을 제거하려다 프로젝트 삭제가 이전에 업로드된 모든 릴리스도 삭제할 것이라는 사실을 깨닫지 못했습니다. 프로젝트는 이후 유지보수자의 동의를 받아 복원되었지만, 수동 관리자 작업과 pytest와 같은 다운스트림(downstream) 프로젝트에 광범위한 손상을 초래했습니다. 2024년 10월 현재 atomicwrites는 보관(archived)되어 있지만 PyPI에서 월 약 450만 건의 다운로드를 기록하고 있습니다.
  • 2023년 4월: codecov 삭제. 장기간의 사용 중단(deprecation) 기간 후에 유지보수자에 의해 삭제되었습니다. 이는 CI/CD 로그 내에서 사용 중단 경고에 대한 가시성(observability)이 제한되어 사용 중단 기간을 알지 못했던 많은 Codecov CI/CD 사용자에게 광범위한 손상을 일으켰습니다. 프로젝트는 이후 유지보수자에 의해 재 생성되었고, 삭제된 릴리스(복원되지 않음)를 보완하기 위해 새로운 릴리스가 게시되었습니다. 이는 고정된(pinned) 설치가 계속 손상된 상태로 남아있음을 의미합니다. 2024년 10월 현재 이 단일 릴리스는 PyPI에서 유일한 릴리스로 남아 있으며, 월 약 150만 건의 다운로드를 기록하고 있습니다.
  • 2023년 6월: python-sonarqube-api 릴리스 삭제. 2.0.2 이전의 모든 릴리스가 삭제되었습니다. 프로젝트의 유지보수자는 이후 대화 내용(conversations)을 삭제하고 python-sonarqube-api의 소스 저장소(source repository)에 대한 태그(tag) 기록을 강제 푸시(force-pushed)하여, 사용자들이 릴리스 간의 변경 사항을 비교하려는 노력을 방해했습니다.
  • 2024년 6월: PySimpleGUI 라이선스 변경 및 삭제. 라이선스를 변경하고 이전 릴리스 대부분을 삭제했습니다. 이로 인해 사용자들에게 광범위한 혼란이 발생했는데, (라이선스 재 변경 전에는) 하루 약 25,000회 PySimpleGUI를 다운로드했습니다.

다운스트림에 대한 파괴적인 영향 외에도, 삭제는 PyPI의 지속 가능성뿐만 아니라 생태계 전체의 보안에도 해로운 영향을 미칩니다.

  • 사용자들이 PyPI가 고장났거나 관리자 자체가 프로젝트를 제거했다고 오해하여 지원 요청(support requests)을 잘못 제출하기 때문에, 삭제는 PyPI 관리자와 중재자의 지원 업무 부담을 증가시킵니다.
  • 삭제는 외부(즉, 최종 사용자)의 사고 대응(incident response) 및 분석을 방해하여, “선의적인(good faith)” 유지보수자 행동과 흔적을 감추려는 악성 행위자(malicious actor)를 구별하기 어렵게 만듭니다.

Python 생태계는 계속 성장하고 있으며, 이는 미래의 프로젝트 삭제가 위에 언급된 삭제들만큼, 또는 그 이상으로 파괴적일 것이라고 합리적으로 가정할 수 있음을 의미합니다.

위의 모든 점을 고려할 때, 이 PEP는 현재 삭제가 이점보다 Python 생태계에 더 큰 위험과 해를 끼친다고 결론 내립니다. 이러한 기술적 주장 외에도, 다른 패키징 생태계(packaging ecosystems)에서도 사용자가 프로젝트 및 그 구성 릴리스를 삭제하는 능력을 제한하는 선례가 있습니다. 이 선례는 부록 A에 문서화되어 있습니다.

명세 (Specification)

삭제 가능한 객체에는 세 가지 유형이 있습니다.

  • 파일(Files): 개별 프로젝트 배포본(distribution)입니다 (예: 소스 배포본 또는 wheel). 예시: requests-2.32.3-py3-none-any.whl.
  • 릴리스(Releases): 동일한 버전 번호를 공유하는 하나 이상의 파일을 포함합니다. 예시: requests v2.32.3.
  • 프로젝트(Projects): 하나 이상의 릴리스를 포함합니다. 예시: requests.

삭제 자격 규칙 (Deletion eligibility rules)

이 PEP는 다음 삭제 자격 규칙을 제안합니다.

  • 파일(File): 현재 시간으로부터 72시간 미만에 PyPI에 업로드되었거나, 사전 릴리스 지정자(pre-release specifier)를 가지고 있는 경우에만 삭제 가능합니다.
  • 릴리스(Release): 포함된 모든 파일이 삭제 가능한 경우에만 삭제 가능합니다.
  • 프로젝트(Project): 모든 릴리스가 삭제 가능한 경우에만 삭제 가능합니다.

이러한 규칙은 새 프로젝트가 완전히 삭제되는 것을 허용하고, 오래된 프로젝트가 새 파일 또는 릴리스를 삭제하는 것을 허용하지만, 오래된 프로젝트가 오래된 파일 또는 릴리스를 삭제하는 것을 허용하지 않습니다.

구현 (Implementation)

이 PEP의 구현은 주로 웹 인터페이스(web interface) 및 로그인한 사용자 작업(signed-in user operations)과 같이 표준화되지 않거나 표준화 대상이 아닌 PyPI 측면과 관련됩니다. 결과적으로 이 섹션에서는 동작 측면에서 구현을 설명합니다.

변경 사항 (Changes)

위의 자격 규칙에 따라, PyPI는 해당 객체가 삭제 자격이 없는 경우 파일, 릴리스 또는 프로젝트 삭제에 대한 웹 인터페이스 요청(적절한 HTTP 응답 코드 사용)을 거부합니다. PyPI는 웹 인터페이스를 수정하여 파일/릴리스/프로젝트의 삭제 부적격성(ineligibility)을 표시할 것입니다. 예를 들어, 관련 UI 요소를 “비활성(inactive)”으로 스타일링하고 관련 버튼/폼을 클릭할 수 없게 만듭니다.

보안 영향 (Security Implications)

이 PEP는 제안된 접근 방식과 관련된 부정적인 보안 영향을 식별하지 않습니다.

이 PEP는 한 가지 사소한 긍정적인 보안 영향을 식별합니다. 사용자 제어 삭제를 제한함으로써, 이 PEP는 악성 행위자가 인덱스에서 악성코드(malware)를 삭제하여 흔적을 감추는 것을 더 어렵게 만듭니다. 이는 특히 외부(즉, PyPI 관리자가 아닌) 분류(triage) 및 사고 대응(incident response)에 유용하며, 방어 당사자(defending party)가 침해 지표(indicators of compromise)를 개발하기 위해 악성코드 샘플에 쉽게 접근해야 할 때 특히 유용합니다.

교육 방법 (How To Teach This)

이 PEP는 더 넓은 Python 패키징 커뮤니티(packaging community) (및 그 다운스트림 소비자)가 변경 사항을 이해하는 데 도움이 될 최소한 두 가지 대중 공개 자료를 제안합니다.

  • PEP의 본질, 동기, PyPI에 대한 행동적 영향을 설명하는 PyPI 블로그 게시물.
  • 위 게시물로 연결되는 PyPI 자체의 공지 배너.
  • 삭제(deletion)와 얀킹(yanking)의 차이점, 그리고 패키지 소유자(package owners)가 삭제를 시작할 수 있는 제한적인 조건을 설명하는 PyPI 사용자 문서 업데이트.

거부된 아이디어 (Rejected Ideas)

의존성 관계에 따른 삭제 조건화 (Conditioning deletion on dependency relationships)

시간 기반 삭제 기간에 대한 대안은 다운스트림 의존자(downstream dependents)를 기반으로 한 삭제 자격입니다. 예를 들어, 릴리스는 PyPI에서 N개 미만의 다운스트림 의존자를 가지고 있는 경우에만 삭제 가능하다고 간주될 수 있으며, N은 1만큼 낮을 수 있습니다.

이 아이디어는 삭제 자격을 파괴성(disruptiveness)과 직접 연결하기 때문에 매력적입니다. npm은 이를 사용하며, 인덱스에 알려진 다운스트림 의존성이 없는 경우 프로젝트 제거를 조건으로 합니다.

그럼에도 불구하고, 이 PEP는 의존성 조건부 삭제가 PyPI에 적합하지 않은 몇 가지 단점과 기술적 한계를 식별합니다.

  • PyPI는 의존성 관계를 알지 못함: Python 패키징에서 프로젝트 빌드(project builds)와 메타데이터(metadata) 생성은 종종 동적(dynamic) 작업이며, 임의의 프로젝트 지정 코드(arbitrary project-specified code)를 포함합니다. 이는 setup.py 스크립트를 포함하는 소스 배포본에 의해 전형적으로 나타나며, setup.py 실행은 프로젝트 메타데이터에 인코딩된 의존성 집합을 계산하는 역할을 합니다. 이는 프로젝트 빌드는 동적일 수 있지만 프로젝트의 메타데이터 자체는 정적인 npm 및 Rust의 crates와 같은 생태계와는 현저한 대조를 이룹니다. 결과적으로 PyPI는 프로젝트의 의존성을 알지 못하며, 임의의 코드 실행(심각한 보안 위험) 또는 PEP 517 및 PEP 621 스타일의 정적 메타데이터(static metadata)를 선호하는 setup.py 기반 빌드의 장기적인 사용 중단(long-tail deprecation)을 수행하지 않고서는 이를 아키텍처적으로 알 수 없습니다.
  • 직관적이지 않은 권한 모델 초래: 의존성 조건부 삭제는 “역전된(reversed)” 권한 관계를 초래하여, 프로젝트에 의존성을 도입하는 누구든지 해당 프로젝트가 삭제되는 것을 막을 수 있습니다. 이는 표면적으로 합리적이지만, 예상치 못하고 바람직하지 않은 결과(일부 삭제를 가능하게 하는 맥락에서)를 초래하기 위해 남용될 수 있습니다. 이에 대한 주목할 만한 예시는 npm의 everything 패키지인데, 이는 (2023년 12월 30일 기준으로) npm의 모든 공개 패키지에 의존하며, 따라서 해당 패키지의 삭제를 방지합니다.

다운로드 수에 따른 삭제 조건화 (Conditioning deletion on download count)

시간 기반 삭제 기간에 대한 또 다른 대안은 다운로드 수(download count)를 기반으로 삭제하는 것입니다. 예를 들어, 릴리스는 지난 기간 동안 N개 미만의 다운로드를 가지고 있는 경우에만 삭제 가능하다고 간주될 수 있습니다.

프로젝트 삭제 가능성을 사용량과 연결함으로써 이점을 제공하지만, 이 PEP는 이 접근 방식에 대한 몇 가지 한계를 식별합니다.

  • 생태계 다양성: Python 생태계는 매우 다양한 사용 패턴을 가진 프로젝트를 포함합니다. 고정된 다운로드 임계값은 자연적으로 다운로드 수가 적지만 중요도가 높은 틈새 프로젝트를 적절하게 설명하지 못할 것입니다.
  • 시간 민감도: 다운로드 수는 프로젝트의 현재 상태나 중요성을 반드시 반영하지 않습니다. 이전에 인기 있었던 프로젝트는 최근 다운로드 수가 적을 수 있지만, 여전히 이전 시스템을 유지하는 데 중요할 수 있습니다.
  • 기술적 복잡성: PyPI 내에서 프로젝트의 다운로드 수에 접근하는 것은 간단하지 않으며, 미러(mirrors) 또는 다른 배포 시스템에서 프로젝트의 다운로드 통계를 수집할 가능성은 제한적입니다.

부록 A: 다른 생태계의 선례 (Appendix A: Precedent in other ecosystems)

다음은 다양한 패키징 생태계에서의 삭제 지원에 대한 표입니다. 이 PEP와 유사한 방식으로 사용자의 삭제 수행 능력을 제한하는 경우, 해당 생태계는 삭제를 지원하지 않는 것으로 간주됩니다.

생태계 (인덱스) 삭제 (Deletion) 얀킹 (Yanking) 비고
Python (PyPI) 현재 삭제가 완전히 무제한입니다.
Rust (crates.io) 사용자 삭제는 전혀 허용되지 않습니다.
JavaScript (npm) 이 PEP와 유사한 기준으로 삭제가 제한됩니다.
Ruby (RubyGems) RubyGems는 삭제를 “얀킹(yanking)”이라고 부릅니다. PyPI의 얀킹 용어는 전혀 지원되지 않습니다.
Java (Maven Central) 사용자 삭제는 전혀 허용되지 않습니다.
PHP (Packagist) 설치 횟수가 문서화되지 않은 특정 수 이상이 되면 삭제가 제한됩니다.
.NET (NuGet) NuGet은 얀킹을 “목록 해제(unlisting)”라고 부릅니다.
Elixir (Hex) Hex는 얀킹을 “은퇴(retiring)”라고 부릅니다.
R (CRAN) 초기 릴리스 후 24시간 이내 또는 후속 버전의 경우 60분 이내로 삭제가 제한됩니다. CRAN은 얀킹을 “아카이빙(archiving)”이라고 부릅니다.
Perl (CPAN) 얀킹은 전혀 지원되지 않습니다. 적어도 2021년 현재 삭제는 장려되는 것으로 보입니다.
Lua (LuaRocks) LuaRocks는 얀킹을 “아카이빙(archiving)”이라고 부릅니다.
Haskell (Hackage) Hackage는 얀킹을 “사용 중단(deprecating)”이라고 부릅니다.
OCaml (OPAM) 포함 후 “합리적으로 빨리” 발생하는 경우 삭제가 허용됩니다. available: false 마커를 통해 얀킹이 사실상 지원되며, 이는 효과적으로 해결(resolution)을 비활성화합니다.

다음과 같은 경향이 나타납니다.

  • 대다수의 인덱스가 삭제를 지원하지 않습니다 (9 대 4).
  • 대다수의 인덱스가 얀킹을 지원합니다 (9 대 4).
  • 압도적인 대다수의 인덱스는 삭제 또는 얀킹 중 하나만 지원하거나 둘 다 지원하지 않지만, 둘 다 지원하지는 않습니다 (11 대 2).

PyPI와 LuaRocks는 삭제와 얀킹을 모두 지원하는 주목할 만한 예외입니다.

참고 자료: https://peps.python.org/pep-0763/

⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.

Comments