- Authors
- Name
- 들어가며: 왜 지금 PyPI 공급망 보안인가
- PyPI 공급망 공격 유형 분석
- 실패 사례 분석
- 다층 방어 전략
- pyproject.toml 보안 설정 모범 사례
- 추가 방어 기법
- 보안 체크리스트
- 결론
- 참고 자료
들어가며: 왜 지금 PyPI 공급망 보안인가
2025년 하반기부터 2026년 초까지 Python PyPI를 대상으로 한 공급망 공격이 전례 없는 수준으로 급증했습니다. 2025년 7월부터 2026년 1월 사이에만 128개의 팬텀 패키지가 총 121,539회 다운로드되었고, 주간 평균 3,903회의 악성 설치가 발생했습니다. The Hacker News에 따르면 2026년 2월 dYdX 공급망 공격처럼 암호화폐 지갑 탈취와 RAT(원격 접근 트로이목마) 배포를 결합한 고도화된 공격이 등장하고 있습니다.
이 글에서는 실제 발생한 공격 사례를 분석하고, 개발팀이 즉시 적용할 수 있는 다층 방어 전략을 실전 코드와 함께 제시합니다.
PyPI 공급망 공격 유형 분석
공격 유형 비교표
| 공격 유형 | 설명 | 대표 사례 | 위험도 |
|---|---|---|---|
| 타이포스쿼팅(Typosquatting) | 유명 패키지 이름의 오타 변형 등록 | termncolor(termcolor 위장), sisaws(sisa 위장) | 높음 |
| 의존성 혼동(Dependency Confusion) | 내부 패키지와 동일한 이름의 공개 패키지 등록 | 기업 내부 패키지명 탈취 | 매우 높음 |
| 악성 빌드 스크립트 | setup.py/pyproject.toml의 빌드 훅에 악성 코드 삽입 | 설치 시 자동 실행되는 백도어 | 높음 |
| 계정 탈취(Account Hijacking) | 패키지 관리자 계정 탈취 후 악성 버전 배포 | dYdX(2026.02), Ultralytics(2024.12) | 매우 높음 |
| 팬텀 패키지(Phantom Package) | 유용해 보이는 가짜 패키지를 대량 등록 | AI/ML 도구 위장 패키지 | 중간 |
| StarJacking | 인기 GitHub 저장소의 URL을 도용하여 신뢰도 위장 | PyPI 메타데이터 조작 | 중간 |
1. 타이포스쿼팅(Typosquatting)
가장 빈번한 공격 유형입니다. 공격자는 requests 대신 reqeusts, colorama 대신 colorizr처럼 유명 패키지와 유사한 이름으로 악성 패키지를 등록합니다. 2025년 7월 발견된 termncolor는 합법적인 termcolor 패키지를 위장했으며, sisaws와 secmeasure 패키지는 SilentSync RAT를 배포하는 것으로 확인되었습니다.
PyPI는 현재 프로젝트 생성 시 타이포스쿼팅 시도를 자동 탐지하고 플래그를 지정하는 기능을 도입했지만, 모든 변형을 차단하지는 못합니다.
2. 의존성 혼동(Dependency Confusion)
2021년 Alex Birsan이 처음 공개한 이 공격은, 기업이 내부적으로 사용하는 비공개 패키지 이름과 동일한 이름의 패키지를 공개 PyPI에 등록하는 방식입니다. pip는 기본적으로 공개 인덱스의 높은 버전 번호를 우선 설치하므로, 공격자가 9999.0.0 같은 극도로 높은 버전을 등록하면 내부 패키지 대신 악성 패키지가 설치됩니다.
3. 계정 탈취를 통한 정상 패키지 변조
가장 파괴력이 큰 공격 유형입니다. 정상적인 패키지의 관리자 인증 정보를 탈취하여 악성 버전을 배포합니다. 이 경우 패키지 이름 자체는 정상이므로 탐지가 매우 어렵습니다.
실패 사례 분석
사례 1: dYdX 공급망 공격 (2026년 2월)
2026년 1월 28일 공개된 이 사건에서, 공격자는 암호화폐 탈중앙화 거래소 dYdX의 개발자 인증 정보를 탈취하여 npm 패키지(@dydxprotocol/v4-client-js)와 PyPI 패키지(dydx-v4-client)에 악성 버전을 배포했습니다.
공격 특징:
- PyPI 패키지에는 100회 반복 난독화된 악성 코드가 삽입됨
- npm과 PyPI 양쪽에 동시 배포하는 크로스 에코시스템 공격
- 암호화폐 지갑 인증 정보 탈취 + RAT(원격 접근 트로이목마) 동시 배포
- 패키지 관리 인프라에 직접 접근하여 정상 빌드 프로세스를 우회
교훈: dYdX는 사용자에게 감염된 머신을 격리하고, 클린 시스템에서 새 지갑으로 자금을 이동하며, 모든 API 키와 인증 정보를 교체할 것을 권고했습니다. 이 사례는 2FA(이중 인증)와 Trusted Publisher 설정의 중요성을 보여줍니다.
사례 2: Ultralytics 공급망 공격 (2024년 12월)
세계 최고의 컴퓨터 비전 AI 라이브러리인 Ultralytics(YOLO)가 GitHub Actions 워크플로우 침해를 통해 공격당했습니다.
공격 타임라인:
- 1차 공격(12월 4-5일): 악성 버전 8.3.41, 8.3.42 배포 (약 12시간 노출)
- 2차 공격(12월 7일): GitHub Actions를 우회하여 직접 PyPI에 버전 8.3.45, 8.3.46 배포
공격 메커니즘:
- 공격자가 git 브랜치 이름을 악용하여 GitHub Actions CI/CD 파이프라인의 인증 정보를 탈취
- 탈취한 PyPI API 토큰으로 XMRig(Monero 암호화폐 채굴기)가 포함된 악성 버전 배포
- 이 공격 기법(Pwn Request)은 2021년부터 알려진 방식이었으나 방어되지 않았음
교훈: PyPI API 토큰의 범위를 특정 프로젝트와 버전으로 제한하고, GitHub Actions 워크플로우에서 외부 입력(브랜치 이름, PR 제목 등)을 검증해야 합니다. 또한 Trusted Publisher를 사용하면 토큰 탈취 자체를 방지할 수 있습니다.
다층 방어 전략
┌──────────────────────────────────────────────────────────────┐
│ PyPI 공급망 보안 다층 방어 │
├──────────┬──────────────┬──────────────┬─────────────────────┤
│ Layer 1 │ Layer 2 │ Layer 3 │ Layer 4 │
│ │ │ │ │
│ 의존성 │ 취약점 │ 빌드 환경 │ 런타임 │
│ 관리 │ 스캐닝 │ 보안 │ 모니터링 │
│ │ │ │ │
│ Lockfile │ pip-audit │ Trusted │ SBOM │
│ Pinning │ safety │ Publisher │ 추적 │
│ │ │ │ │
│ Hash │ GitHub │ PEP 740 │ 의존성 │
│ 검증 │ Dependabot │ Attestation │ 감사 │
│ │ │ │ │
│ Private │ Snyk / │ 2FA / │ 이상 탐지 │
│ Index │ Socket.dev │ OIDC │ │
└──────────┴──────────────┴──────────────┴─────────────────────┘
Layer 1: 의존성 관리 강화
Lockfile Pinning과 해시 검증
의존성을 정확한 버전과 해시로 고정하면, 패키지가 변조되었을 때 설치를 차단할 수 있습니다.
# pyproject.toml - uv/pip 호환 의존성 관리
[project]
name = "my-secure-app"
requires-python = ">=3.11"
dependencies = [
"requests==2.31.0",
"cryptography==42.0.5",
"pydantic==2.6.1",
]
[tool.uv]
# 프라이빗 인덱스 우선 설정 (의존성 혼동 방어)
index-url = "https://my-company.jfrog.io/pypi/simple/"
extra-index-url = "https://pypi.org/simple/"
[tool.uv.pip]
# 해시 검증 필수화
require-hashes = true
# requirements.txt - 해시 고정 예시
requests==2.31.0 \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7f0edf3fcb0fce8aea3fbd5951d bdf0f4
cryptography==42.0.5 \
--hash=sha256:6e2b11c55d260d03a8cf29ac9b5e0608c3cb2b6f56af2f20f2132764710 68e5c
pydantic==2.6.1 \
--hash=sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5 d1fa9
프라이빗 인덱스 우선 설정 (의존성 혼동 방어)
# pip.conf - 프라이빗 인덱스 우선 설정
[global]
index-url = https://my-company.jfrog.io/pypi/simple/
extra-index-url = https://pypi.org/simple/
[install]
# 해시 검증을 기본 활성화
require-hashes = true
uv를 사용하는 경우 더 강력한 의존성 혼동 방어가 가능합니다.
# pyproject.toml - uv의 인덱스 전략 설정
[tool.uv]
# "first-match" 전략: 첫 번째 인덱스에서 패키지를 찾으면 다른 인덱스 검색 안 함
index-strategy = "first-match"
[[tool.uv.index]]
name = "internal"
url = "https://my-company.jfrog.io/pypi/simple/"
default = true
[[tool.uv.index]]
name = "pypi"
url = "https://pypi.org/simple/"
Layer 2: 취약점 스캐닝
pip-audit로 알려진 취약점 검사
pip-audit은 Google이 후원하고 Trail of Bits가 개발한 오픈소스 도구로, PyPI JSON API를 통해 Python Packaging Advisory Database의 취약점 정보를 조회합니다.
# pip-audit 설치 및 실행
pip install pip-audit
# 현재 환경 스캐닝
pip-audit
# requirements.txt 기반 스캐닝
pip-audit -r requirements.txt
# 취약점 자동 수정 (안전한 최신 버전으로 업그레이드)
pip-audit --fix
# JSON 형식 출력 (CI/CD 파이프라인 연동용)
pip-audit -f json -o audit-report.json
# 특정 취약점 무시 (오탐 또는 비적용 사례)
pip-audit --ignore-vuln PYSEC-2024-XXXX
Safety CLI로 악성 패키지 탐지
Safety는 취약점 검사 외에 악성 패키지 탐지 기능도 제공합니다.
# Safety 설치 및 실행
pip install safety
# 현재 환경 스캐닝
safety check
# requirements.txt 기반 스캐닝
safety check -r requirements.txt
# JSON 출력 형식
safety check --output json
# 전체 프로젝트 디렉토리 스캐닝 (악성 패키지 탐지 포함)
safety scan --target ./my-project/
취약점 스캐닝 도구 비교
| 기능 | pip-audit | Safety CLI | Snyk |
|---|---|---|---|
| 취약점 DB | PyPI Advisory DB (OSV) | SafetyDB (PyUp) | Snyk Vulnerability DB |
| 악성 패키지 탐지 | 미지원 | 지원 | 지원 |
| 자동 수정 | 지원 (--fix) | 미지원 | 지원 |
| 라이선스 검사 | 미지원 | 유료 버전 지원 | 지원 |
| CVSS 점수 | 미지원 | 유료 버전 지원 | 지원 |
| CI/CD 통합 | GitHub Actions 제공 | GitHub Actions 제공 | 네이티브 통합 |
| 비용 | 무료 (Apache 2.0) | 무료/유료 | 무료/유료 |
| 추천 용도 | CI/CD 자동 검사 | 개발 환경 보안 | 엔터프라이즈 |
Layer 3: 빌드 환경 보안 - Trusted Publisher와 PEP 740
Trusted Publisher 설정
PyPI Trusted Publisher는 OpenID Connect(OIDC)를 사용하여 GitHub Actions 등 CI/CD 플랫폼에서 토큰 없이 안전하게 패키지를 배포할 수 있게 합니다. API 토큰이 존재하지 않으므로 탈취 자체가 불가능합니다.
# .github/workflows/publish.yml - Trusted Publisher 기반 배포
name: Publish to PyPI
on:
release:
types: [published]
permissions:
id-token: write # OIDC 토큰 발급에 필요
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install build dependencies
run: pip install build
- name: Build package
run: python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
# Trusted Publisher 사용 시 password/token 불필요
# PyPI에서 GitHub 저장소를 Trusted Publisher로 등록해야 함
with:
attestations: true # PEP 740 디지털 어테스테이션 자동 생성
verify:
needs: build
runs-on: ubuntu-latest
steps:
- name: Verify attestation
run: |
pip install pypi-attestations
python -m pypi_attestations verify my-package
PEP 740 디지털 어테스테이션
PEP 740은 PyPI 패키지에 대한 암호학적 검증 가능한 증명(Attestation)을 정의합니다. Sigstore 기반의 키리스(keyless) 서명을 사용하며, 패키지가 어떤 소스 저장소에서 빌드되었는지 검증할 수 있습니다.
# 어테스테이션 검증 (소비자 측)
pip install pypi-attestations
# 특정 패키지의 어테스테이션 확인
python -c "
import requests
resp = requests.get(
'https://pypi.org/integrity/requests/2.31.0/'
)
attestations = resp.json()
print(f'Attestation count: {len(attestations)}')
for att in attestations:
print(f' Publisher: {att.get(\"publisher\", \"unknown\")}')
"
Layer 4: CI/CD 보안 파이프라인 구축
GitHub Actions 종합 보안 파이프라인
# .github/workflows/security.yml
name: Python Supply Chain Security
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# 매일 오전 9시(KST) 정기 스캐닝
- cron: '0 0 * * *'
permissions:
contents: read
security-events: write
jobs:
dependency-audit:
name: Dependency Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
pip install pip-audit safety cyclonedx-bom
- name: Run pip-audit
run: |
pip-audit -r requirements.txt \
-f json \
-o pip-audit-report.json \
--desc on
continue-on-error: false
- name: Run Safety check
run: |
safety check -r requirements.txt \
--output json \
> safety-report.json
continue-on-error: true
- name: Check for critical vulnerabilities
run: |
python3 -c "
import json, sys
with open('pip-audit-report.json') as f:
report = json.load(f)
vulns = report.get('dependencies', [])
critical = [v for v in vulns if v.get('vulns')]
if critical:
print(f'CRITICAL: {len(critical)} vulnerable packages found')
for pkg in critical:
name = pkg['name']
version = pkg['version']
for vuln in pkg['vulns']:
vid = vuln['id']
fix = vuln.get('fix_versions', ['N/A'])
print(f' - {name}=={version}: {vid} (fix: {fix})')
sys.exit(1)
print('No vulnerabilities found')
"
- name: Upload audit reports
if: always()
uses: actions/upload-artifact@v4
with:
name: security-reports
path: |
pip-audit-report.json
safety-report.json
sbom-generation:
name: Generate SBOM
runs-on: ubuntu-latest
needs: dependency-audit
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install cyclonedx-bom
- name: Generate CycloneDX SBOM
run: |
cyclonedx-py environment \
--output sbom.json \
--output-format json \
--schema-version 1.5
- name: Validate SBOM
run: |
python3 -c "
import json
with open('sbom.json') as f:
sbom = json.load(f)
components = sbom.get('components', [])
print(f'SBOM generated: {len(components)} components')
print(f'Format: CycloneDX {sbom.get(\"specVersion\", \"unknown\")}')
for comp in components[:5]:
name = comp.get('name', 'unknown')
version = comp.get('version', 'unknown')
print(f' - {name}@{version}')
if len(components) > 5:
print(f' ... and {len(components) - 5} more')
"
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.json
lockfile-integrity:
name: Lockfile Integrity Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Verify hash-pinned dependencies
run: |
pip install --require-hashes \
-r requirements.txt \
--dry-run \
--no-deps
continue-on-error: false
- name: Check for unpinned dependencies
run: |
python3 -c "
import re, sys
unpinned = []
with open('requirements.txt') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#'):
if '==' not in line and '--hash' not in line:
unpinned.append(line)
if unpinned:
print('WARNING: Unpinned dependencies found:')
for dep in unpinned:
print(f' - {dep}')
sys.exit(1)
print('All dependencies are version-pinned')
"
SBOM 생성과 관리
SBOM(Software Bill of Materials)은 소프트웨어에 포함된 모든 구성 요소를 문서화한 목록입니다. 미국 행정명령 14028호 이후 공급망 투명성의 핵심 요소가 되었습니다.
# CycloneDX로 Python 프로젝트 SBOM 생성
pip install cyclonedx-bom
# 현재 가상 환경 기반 SBOM 생성
cyclonedx-py environment \
--output sbom.json \
--output-format json \
--schema-version 1.5
# requirements.txt 기반 SBOM 생성
cyclonedx-py requirements \
--input-file requirements.txt \
--output sbom-requirements.json \
--output-format json
# SPDX 형식으로도 생성 가능
pip install spdx-tools
# sbom_validator.py - SBOM 검증 및 분석 스크립트
import json
import sys
from datetime import datetime
def validate_sbom(sbom_path: str) -> dict:
"""SBOM 파일을 검증하고 요약 리포트를 생성합니다."""
with open(sbom_path) as f:
sbom = json.load(f)
components = sbom.get("components", [])
metadata = sbom.get("metadata", {})
report = {
"timestamp": datetime.now().isoformat(),
"spec_version": sbom.get("specVersion", "unknown"),
"total_components": len(components),
"components_without_version": [],
"components_without_license": [],
"components_without_purl": [],
}
for comp in components:
name = comp.get("name", "unknown")
if not comp.get("version"):
report["components_without_version"].append(name)
if not comp.get("licenses"):
report["components_without_license"].append(name)
if not comp.get("purl"):
report["components_without_purl"].append(name)
# 검증 결과 출력
print(f"SBOM Validation Report")
print(f"=" * 50)
print(f"Spec Version: {report['spec_version']}")
print(f"Total Components: {report['total_components']}")
print(f"Missing Versions: {len(report['components_without_version'])}")
print(f"Missing Licenses: {len(report['components_without_license'])}")
print(f"Missing PURLs: {len(report['components_without_purl'])}")
# 품질 점수 계산
total = report["total_components"]
if total > 0:
quality_score = (
1
- (
len(report["components_without_version"])
+ len(report["components_without_license"])
+ len(report["components_without_purl"])
)
/ (total * 3)
) * 100
print(f"Quality Score: {quality_score:.1f}%")
report["quality_score"] = quality_score
return report
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python sbom_validator.py sbom.json")
sys.exit(1)
validate_sbom(sys.argv[1])
pyproject.toml 보안 설정 모범 사례
[project]
name = "my-secure-app"
version = "1.0.0"
requires-python = ">=3.11"
dependencies = [
"requests>=2.31.0,<3.0",
"cryptography>=42.0.0,<43.0",
"pydantic>=2.6.0,<3.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
# 보안 관련 도구 설정
[tool.pip-audit]
# pip-audit 설정
desc = "on"
progress-spinner = "on"
output = "json"
[tool.safety]
# Safety CLI 설정
output = "json"
continue-on-error = false
[tool.ruff]
# 보안 관련 린트 규칙 활성화
select = [
"S", # flake8-bandit (보안 취약점 탐지)
"B", # flake8-bugbear
]
[tool.bandit]
# Bandit 정적 보안 분석 설정
exclude_dirs = ["tests", "venv"]
skips = []
추가 방어 기법
setup.py 빌드 스크립트 검사
악성 패키지의 상당수가 setup.py의 install 훅에 악성 코드를 삽입합니다. 패키지 설치 전에 setup.py 내용을 검사하는 습관이 필요합니다.
# 패키지 설치 전 소스 코드 검사
pip download --no-binary :all: --no-deps suspect-package
# 다운로드된 소스를 압축 해제 후 setup.py 검사
# 또는 pip의 --no-build-isolation 옵션으로 빌드 스크립트 실행을 제한
pip install --no-build-isolation --only-binary :all: package-name
GitHub Dependabot 설정
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: 'pip'
directory: '/'
schedule:
interval: 'daily'
reviewers:
- 'security-team'
labels:
- 'dependencies'
- 'security'
open-pull-requests-limit: 10
# 보안 업데이트만 자동 PR 생성
allow:
- dependency-type: 'direct'
# 메이저 버전 업데이트는 수동 검토
ignore:
- dependency-name: '*'
update-types: ['version-update:semver-major']
pre-commit 훅으로 로컬 검사
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pypa/pip-audit
rev: v2.7.3
hooks:
- id: pip-audit
args: ['-r', 'requirements.txt']
- repo: https://github.com/PyCQA/bandit
rev: 1.7.8
hooks:
- id: bandit
args: ['-r', 'src/', '-ll']
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
보안 체크리스트
프로젝트에 즉시 적용할 수 있는 공급망 보안 체크리스트입니다.
의존성 관리
- 모든 의존성이 정확한 버전으로 고정되어 있는가 (
==사용) - requirements.txt 또는 lockfile에 해시가 포함되어 있는가 (
--hash) - 프라이빗 패키지에 대해 내부 인덱스가 우선 설정되어 있는가
- 사용하지 않는 의존성이 제거되어 있는가
- 의존성 업데이트 주기가 정의되어 있는가 (Dependabot/Renovate)
CI/CD 보안
- pip-audit 또는 Safety가 CI 파이프라인에 통합되어 있는가
- CRITICAL/HIGH 취약점 발견 시 빌드가 실패하도록 설정되어 있는가
- SBOM이 빌드마다 자동 생성되는가
- GitHub Actions에서 Trusted Publisher를 사용하고 있는가
- PyPI API 토큰의 범위가 최소 권한으로 제한되어 있는가
계정 보안
- PyPI 계정에 2FA(이중 인증)가 활성화되어 있는가
- PyPI API 토큰이 프로젝트별로 분리되어 있는가
- API 토큰이 코드 저장소에 하드코딩되어 있지 않은가
- GitHub Actions secrets가 안전하게 관리되고 있는가
모니터링
- 의존성 변경 사항에 대한 알림이 설정되어 있는가
- SBOM 기반 취약점 모니터링이 운영되고 있는가
- 새로운 CVE 발표 시 영향 분석 프로세스가 있는가
결론
PyPI 공급망 공격은 단일 도구나 단일 정책으로 막을 수 없습니다. 의존성 잠금(Lockfile Pinning + Hash Verification), 취약점 스캐닝(pip-audit + Safety), 빌드 환경 보안(Trusted Publisher + PEP 740 Attestation), 런타임 모니터링(SBOM 추적)을 결합한 다층 방어 전략이 필요합니다.
특히 2026년 현재 주목해야 할 세 가지 핵심 조치는 다음과 같습니다.
- Trusted Publisher 전환: PyPI API 토큰 대신 OIDC 기반 Trusted Publisher를 사용하여 인증 정보 탈취 위험을 근본적으로 제거
- PEP 740 어테스테이션 활용: 패키지의 출처를 암호학적으로 검증하여 변조 여부를 확인
- SBOM 자동화: 빌드마다 SBOM을 생성하고 지속적으로 취약점 모니터링을 수행
공급망 보안은 한 번 설정하고 끝나는 것이 아니라, 지속적으로 업데이트하고 감시해야 하는 운영 프로세스입니다. 오늘 당장 위의 체크리스트를 기반으로 팀의 보안 상태를 점검해 보시기 바랍니다.
참고 자료
- PyPI 2025 연간 리뷰 - PyPI 공식 블로그
- PyPI 디지털 어테스테이션 지원 - PyPI 공식 블로그
- PEP 740 - 디지털 어테스테이션을 위한 인덱스 지원 - Python PEP
- dYdX npm/PyPI 패키지 공급망 공격 - The Hacker News
- Ultralytics 공급망 공격 분석 - PyPI 공식 블로그
- Ultralytics 공급망 공격 방어 방법 - Legit Security
- pip-audit - Python 환경 취약점 감사 도구 - PyPA GitHub
- Safety CLI - Python 의존성 취약점 스캐너 - PyPI
- CycloneDX Python SBOM 생성기 - CycloneDX GitHub
- 공급망 공격은 우리의 가정을 악용한다 - Trail of Bits Blog
- 어테스테이션: PyPI의 새로운 세대 서명 - Trail of Bits Blog
- Are we PEP 740 yet? - Trail of Bits
- Sigstore 기반 PyPI 어테스테이션 GA - Sigstore Blog