Skip to content

apt proxies: pool and by-hash files are immutable only once verified, and invalidate cache evicts them - #898

Merged
schmitch merged 1 commit into
mainfrom
claude/760-apt-proxy-verified-immutable
Nov 4, 2026
Merged

schmitch merged 1 commit into
mainfrom
claude/760-apt-proxy-verified-immutable

Conversation

@schmitch

@schmitch schmitch commented Nov 3, 2026

Copy link
Copy Markdown
Member

Fixes #760

What changed

Immutable only once verified (crates/formats/apt/src/proxy.rs, layout.rs)

  • A pool file is marked immutable only when the upstream's Packages index lists it with both a size and a SHA-256. In every other case it stays an ordinary cached file, re-validated after the metadata TTL: Lookup::Unknown, an index that doesn't list the file or lists it without a usable digest, a failed Packages fetch, or no binary-all (Ubuntu's _all packages).
  • A by-hash/<type>/<digest> copy is checked for every hash type apt asks for: MD5Sum, SHA1, SHA256 and SHA512. layout::by_hash_digest replaces by_hash_sha256 and checks the digest's length for its type. A name that is not a digest of its type is not immutable.
  • FileLocation (plugin API) has sha512, sha1 and md5 (hex) next to sha256, plus has_digest(). The host (crates/server/src/files/proxy.rs) checks every digest it is given before it caches the file, and answers 502 on a mismatch, as it already did for SHA-256.
  • During re-validation, a cached copy that does not match the digest the index now gives is fetched again unconditionally, without If-None-Match/If-Modified-Since, so a stale validator cannot keep a wrong copy.

Evicting immutable files

  • Invalidate cache (whole repository) on a package format's proxy now also calls raw_dao::release_immutable. This makes the apt proxy's immutable files ordinary cached files that are due for re-validation, and drops their upstream validators. On its next download each file is fetched again, checked against the index, replaced if it is wrong, and kept for good again once verified. While the upstream is down, the cached copy is still served. These files count in files_expired.
  • DELETE /admin/repositories/{name}/files?path=… now works on a package format's proxy (images excepted), so an administrator can evict one path or a directory. Hosted package repositories still refuse it, because their files are the signed indexes.
  • Web UI, as AGENTS.md asks: a new Cached files panel (web/src/components/CachedFilesPanel.tsx) on an apt proxy's edit page, with a path field and a Delete cached file button. The Proxy cache panel's text for apt now says that invalidation re-checks the packages and by-hash copies, and its result reports the files due for re-validation.
  • Docs: docs/apt.md (proxies) and docs/format-plugins.md (file-at).

The optional check of Packages against a verified InRelease using the upstream's public key is out of scope and not done here. docs/apt.md now says the proxy does not do that check, and apt still checks every file against the signed indexes.

Tests

  • crates/server/tests/apt.rs:
    • proxies_check_by_hash_copies_of_every_hash_type: a by-hash MD5Sum/SHA1/SHA512 copy whose content is not its digest gets 502, and a correct one gets 200.
    • proxies_revalidate_packages_they_could_not_check: a package no index lists is not immutable. A new upstream copy replaces it once the TTL has passed.
    • invalidate_cache_and_deletes_evict_checked_packages: a verified package is not asked for again. After Invalidate cache it is fetched again and the re-uploaded copy replaces it, then it is kept for good again. An admin DELETE …/files?path= evicts it.
    • All three fail without the fix: I stashed the source changes and kept the tests. The by-hash test got 200 where it expected 502, and the other two tests served the old copy.
  • Apt crate unit tests: immutability for Unknown, a failed index, an unlisted file, a malformed digest, no index at all, and every by-hash hash type with its digest length.
  • web/src/components/CachedFilesPanel.test.tsx: the panel sends the DELETE with the path, and shows an error when the delete fails.
  • Ran cargo fmt --all --check, cargo clippy -p forge-plugin-api -p forge-format-apt -p forge-db -p forge-server --all-targets -- -D warnings, and cargo test on those crates (912 passed before the rebase; apt and raw again after rebasing onto main with apt proxies: a pool path of no nameable package answers 404 to a selector-scoped reader #881). Also ran cargo xtask changelog --check, and in web/: pnpm run tsc and vitest run (623 passed).

Changelog

docs/changes/2.2.0/760.fixed.md. It is filed under fixed, not security, because apt checks every file against the upstream's signed indexes, so a wrong copy was never installed. The harm was that it was kept for good ("Hash Sum mismatch" until the proxy was recreated).

Not fixed here

  • Each first download of a package whose index does not exist (Ubuntu's binary-all) still costs up to three upstream 404s (Packages.gz, .xz, plain), which are not cached. Now that those packages are re-validated, this happens again once per metadata TTL for each such package. Caching the absence of an index would be a separate change.

Generated by Claude Code

An apt proxy stored every pool file and by-hash copy as immutable, even
when it had checked nothing: a package no Packages index lists (Ubuntu's
_all packages), one fetched while the index was unavailable, and MD5Sum,
SHA1 and SHA512 by-hash copies. A wrong copy was then served for good.

- The plugin marks a pool file immutable only when the upstream's
  Packages index gave it a size and SHA-256, and a by-hash copy only
  when its name is a digest of its hash type. FileLocation carries
  SHA-512, SHA-1 and MD5 too, and the host checks every one it gets.
- A re-validation whose cached copy contradicts the index's digest
  fetches unconditionally.
- Invalidate cache releases an apt proxy's immutable files: they are
  re-validated without validators on their next download, checked anew
  and replaced when wrong.
- DELETE /admin/repositories/{name}/files evicts a file of a package
  format's proxy; the repository page gets a "Cached files" panel for
  apt proxies.

Fixes #760

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012e6DJYJ9Bfma4cFLf9LWgh
@schmitch
schmitch added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit a391272 Nov 4, 2026
20 checks passed
@schmitch
schmitch deleted the claude/760-apt-proxy-verified-immutable branch November 4, 2026 06:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

apt proxies: pool and by-hash files are stored as immutable even when no check ran, and a wrong one is never refreshed or evicted

2 participants