feat: quarantine new releases (supply-chain attack prevention) #25

Merged
unkinben merged 1 commits from benvin/issue-22-quarantine into master 2026-04-28 23:13:28 +10:00
Owner

Closes #22

Summary

Adds per-remote quarantine support: immutable artifacts published within a configurable window (quarantine_days) are blocked with 404, giving teams time to detect malicious packages before they reach consumers.

Changes

  • ConfigManager.get_quarantine_config() — reads quarantine_new / quarantine_days from remote config
  • RedisCache.store/get_artifact_published() — persists the upstream Last-Modified header per artifact (namespace pkg:published:)
  • proxy._check_quarantine() — raises HTTP 404 for artifacts within the quarantine window; fails open when the publish date is unknown
  • proxy._fetch_last_modified() — HEAD-requests upstream to discover publish date on cache-hit when not yet stored
  • Both generic (/api/v1/remote/) and Docker (/v2/) proxy routes wired with quarantine checks on cache-hit and cache-miss paths
  • remotes.yaml — pypi example updated with quarantine_new: true, quarantine_days: 3
  • README.md — documents the quarantine feature with a config example

Tests

21 new tests across test_config.py, test_cache.py, and test_routes.py:

  • TestGetQuarantineConfig — config parsing edge cases
  • TestArtifactPublished — Redis key format, store/retrieve, unavailable-Redis no-op
  • TestQuarantine — cache-miss and cache-hit blocking, fail-open (no Last-Modified), disabled quarantine pass-through, 404 detail string format

Docker verification

  • Config endpoint shows quarantine_new: true, quarantine_days: 3 for pypi remote
  • Fetching pip-21.0-py3-none-any.whl (published 2021) returns 200 — correctly outside quarantine window
  • Mutable simple index pages (/simple/pip/) are never quarantined
Closes #22 ## Summary Adds per-remote quarantine support: immutable artifacts published within a configurable window (quarantine_days) are blocked with 404, giving teams time to detect malicious packages before they reach consumers. ## Changes - `ConfigManager.get_quarantine_config()` — reads `quarantine_new` / `quarantine_days` from remote config - `RedisCache.store/get_artifact_published()` — persists the upstream `Last-Modified` header per artifact (namespace `pkg:published:`) - `proxy._check_quarantine()` — raises HTTP 404 for artifacts within the quarantine window; fails open when the publish date is unknown - `proxy._fetch_last_modified()` — HEAD-requests upstream to discover publish date on cache-hit when not yet stored - Both generic (`/api/v1/remote/`) and Docker (`/v2/`) proxy routes wired with quarantine checks on cache-hit and cache-miss paths - `remotes.yaml` — pypi example updated with `quarantine_new: true`, `quarantine_days: 3` - `README.md` — documents the quarantine feature with a config example ## Tests 21 new tests across `test_config.py`, `test_cache.py`, and `test_routes.py`: - `TestGetQuarantineConfig` — config parsing edge cases - `TestArtifactPublished` — Redis key format, store/retrieve, unavailable-Redis no-op - `TestQuarantine` — cache-miss and cache-hit blocking, fail-open (no Last-Modified), disabled quarantine pass-through, 404 detail string format ## Docker verification - Config endpoint shows `quarantine_new: true`, `quarantine_days: 3` for pypi remote - Fetching `pip-21.0-py3-none-any.whl` (published 2021) returns 200 — correctly outside quarantine window - Mutable simple index pages (`/simple/pip/`) are never quarantined
unkinben added 1 commit 2026-04-28 23:02:11 +10:00
feat: quarantine new releases to prevent supply chain attacks
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
3bd3ca8b74
Add per-remote quarantine support: when quarantine_new=true and quarantine_days=N,
immutable artifacts published within the last N days are blocked with 404 until
the quarantine window expires.

- ConfigManager.get_quarantine_config() reads quarantine_new/quarantine_days
- RedisCache.store/get_artifact_published() persist Last-Modified per artifact
- proxy._check_quarantine() enforces the window; fails open when date is unknown
- proxy._fetch_last_modified() HEAD-requests upstream to discover publish date
- Docker proxy route wires quarantine checks on both cache-hit and cache-miss
- remotes.yaml: quarantine_new/quarantine_days added to pypi example (3-day window)
- README: documents quarantine configuration
unkinben merged commit be25fc19f7 into master 2026-04-28 23:13:28 +10:00
unkinben deleted branch benvin/issue-22-quarantine 2026-04-28 23:13:28 +10:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unkin/artifactapi#25