Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 07:39:19 -05:00
Commit graph pkg-proxy/internal/handler
Author SHA1 Message Date
Andrew Nesbitt
c1a1149bd8
Merge pull request #622 from simonchrz/feat/apk-snapshots
Serve dated snapshots of Alpine APK indexes
2026-11-03 09:36:40 +00:00
Andrew Nesbitt
abee8ef879
Merge pull request #640 from simonchrz/feat/npm-stream-rewrite
Stream npm packuments through storage when rewriting them
2026-11-03 09:33:36 +00:00
Andrew Nesbitt
7cb905638c
Keep proxyCachedWithEncoding doc on the function it describes 2026-11-03 09:28:29 +00:00
Andrew Nesbitt
47ed4b4194
Merge pull request #649 from simonchrz/fix/truncated-upstream-metadata
Fail metadata reads when upstream cuts a document off
2026-11-03 09:22:25 +00:00
Andrew Nesbitt
83a0822979
Merge pull request #644 from simonchrz/mirror-parallel-blobs
Fetch an image's blobs concurrently when mirroring
2026-11-03 09:15:16 +00:00
Simon Chrzanowski
26cae44e41 Bump cooldown to v0.3.1 and set cooldown directly in stream tests
v0.3.1 parses a Config once when it is first used concurrently, so the
stream tests no longer need cooldown.NewPolicy.
2026-11-02 11:33:11 +01:00
Simon Chrzanowski
98ff4fd4a2 Merge remote-tracking branch 'origin/main' into feat/npm-stream-rewrite
# Conflicts:
#	cmd/proxy/main.go
2026-11-01 12:54:47 +01:00
Simon Chrzanowski
b08cebf9e4 Cap npm packuments streamed into storage
metadata_max_size bounds what is read into memory, but a packument
streamed from upstream into storage had no limit, and with cooldown was
stored twice. It is now capped by metadata_stream_max_size, 1 GB by
default. A document over the cap fails as an upstream failure: nothing is
stored, and the stored copy, if any, is served instead.

The metadata settings move into configureMetadata to keep serve under
the maintidx limit.
2026-11-01 12:53:00 +01:00
Simon Chrzanowski
6dbda21faf Move the in-memory truncation fix to its own pull request
The change to ReadMetadataSized and fetchUpstreamMetadata, with its tests,
is now #649. upstreamBodyReader stays for storeUpstreamMetadata, in a file
of its own that #649 adds with the same content.
2026-11-01 12:49:09 +01:00
Simon Chrzanowski
143eef7f05 Test snapshots on cache hits and record them through proxyCachedWithEncoding
TestAPKSnapshots_CacheHit covers the cache-hit branch with a metadata TTL
above 0. proxyCachedWithEncoding takes optional hooks for a cache hit and a
fetched body, so the APK handler no longer keeps a copy of it.
2026-11-01 12:48:37 +01:00
Simon Chrzanowski
358b1355a9 Fail metadata reads when upstream cuts a document off
ReadMetadataSized took the transport's io.ErrUnexpectedEOF for the end of
a body shorter than its size hint. A document upstream closed before its
Content-Length was served with a 200 and cached, or failed the cooldown
filter with a 502, instead of the cached copy being served. Upstream
bodies are now read through a reader that marks their read errors, and
such a body fails the read as an upstream failure.
2026-11-01 12:48:04 +01:00
Simon Chrzanowski
7b91897fbb Merge remote-tracking branch 'origin/main' into feat/apk-snapshots
# Conflicts:
#	internal/server/server.go
2026-11-01 12:44:35 +01:00
Simon Chrzanowski
fd16ba1664 Let in-flight blobs finish when one fails
errgroup.WithContext cancelled the blobs being fetched as soon as one
failed. A client pull sharing one of those fetches failed with it, and the
next run had to fetch them again. A failure now only stops further blobs
from starting.
2026-10-31 18:15:11 +01:00
Simon Chrzanowski
f0c96e87a8 Keep the stored copy when upstream cuts a metadata document off
A body that ended before its Content-Length was read as complete in
memory: ReadMetadataSized took the transport's io.ErrUnexpectedEOF for
the end of a body shorter than its size hint. The half document was
served and cached, or failed the filter with a 502, instead of the stored
copy being served. Upstream bodies are now read through a reader that
marks their read errors, and such a failure is an upstream failure on
both paths: streamed, it no longer counts as a failure to store, which
had sent the request to the bounded in-memory path.

A stored packument whose bytes no longer match its row's digest is now
fetched again like a missing one.

Tests also cover concurrent requests sharing one fetch, client
revalidation and upstream failures with nothing cached. The stream tests
build cooldown through cooldown.NewPolicy as the server does, since a
bare cooldown.Config parses itself on first use without locking.
2026-10-31 17:50:24 +01:00
Simon Chrzanowski
11ff4cf314 Fetch an npm packument again when its stored copy is gone within the TTL
A row within its TTL was served without checking its blob, so a missing
blob failed the streamed read and the request fell back to the bounded
in-memory path. A read that finds the stored copy missing now fetches the
packument again into storage, ignoring that row, and reads it once more,
for packument, version and tarball requests.
2026-10-31 16:55:15 +01:00
simon chrzanowski
1979fb098e
Accept Swift source repository PURLs in the denylist and retention rules (#641)
* Accept Swift source repository PURLs in the denylist and retention rules

Closes #613. pkg:swift/github.com/apple/swift-nio, as SBOM tools and GHSA
write Swift packages, is resolved to its registry identity through the
upstream's /identifiers:

- Denylist entries are resolved at startup, and the proxy refuses to start
  when one can't be, since a skipped entry would let the version through.
- Retention keys are accepted at validation and kept pending. The
  retention loop resolves them before each sweep, and Swift isn't swept
  while any is pending, so a rule that keeps a package is never skipped.

The mirror's source URL helper moves to the handler as SwiftSourceURL.

* Resolve Swift source denylist entries in proxy mirror too

serve resolved source-form Swift denylist entries through the upstream,
but proxy mirror built its denylist directly, so such an entry never
matched a mirror download. Both now use server.ConfigureDenylist, and
neither runs when an entry can't be resolved.

* Apply Swift source policies to every listed identity

A Swift registry may list several identities for one source repository,
but a denylist entry or retention rule naming the repository only
covered the first. Denying pkg:swift/github.com/acme/widget@1.0.0 let
acme.widget through when the registry listed other.widget first, and a
never-evict rule cleared the cached apple.swift-nio archive when
other.swift-nio came first.

Both now cover every identity the registry lists. A retention rule that
conflicts with another rule for any of them stays pending, with none
applied. Mirroring a source PURL still downloads the first identity.
2026-10-31 11:34:05 +00:00
Andrew Nesbitt
3eadb3eb46
Preserve Swift and Go compatibility when upgrading PURL (#642)
* Handle Swift registry identities in cache and vulnerability APIs

* Preserve Go cache and policy behavior when upgrading PURL
2026-10-31 09:49:35 +00:00
Simon Chrzanowski
a79a58b04e Guard the fake registry's manifests in mirror tests
TestMirrorImageKeepsTagMovedByPullDuringWalk moves a tag while a blob is
served. With blobs fetched concurrently, another blob request reads the
manifest map at the same time, which the race detector caught on
Windows.
2026-10-31 08:27:39 +01:00
Simon Chrzanowski
4ae534eba3 Fetch an image's blobs concurrently when mirroring
The image mirror fetched the config and layer blobs of a manifest one
after another, so a run took as long as all layers together. It now
fetches up to three at once, as many as a Docker pull does by default.
For a Home Assistant image (550 MB in ten blobs) that would leave
mostly the time of its largest layer.
2026-10-31 07:58:51 +01:00
Simon Chrzanowski
04f732e147 Record APK snapshots from the document a request serves
A snapshot was recorded after the response by reading the metadata cache
again, so a refresh that replaced the cached copy in between lost the
version the request served. Record it from the served bytes before
responding instead, and compare a new snapshot with the version current
at its own time, so one recorded late keeps its place in the history.
2026-10-30 19:26:36 +01:00
Simon Chrzanowski
3ce582c733 Keep the last good Composer index when upstream sends one that doesn't parse
parseComposerUpstreamIndex now returns an error for a packages.json that
is not a JSON object or has the advisories and filter fields in the wrong
shape, and fetchUpstreamIndex passes it to the metadata cache as validate.
A malformed 200 is no longer stored: with metadata caching the stored
copy is served as when upstream fails, and without it the error keeps
the copy in memory.
2026-10-30 19:25:41 +01:00
Simon Chrzanowski
760623e617 Serve the stored npm packument on upstream 404 and refetch on a 304 for a missing blob 2026-10-30 19:25:09 +01:00
Simon Chrzanowski
f3c38bee42 Merge remote-tracking branch 'origin/main' into feat/composer-advisories 2026-10-30 19:21:32 +01:00
Simon Chrzanowski
ddba210a60 Serialize snapshot recording per document on Postgres
AddMetadataSnapshot takes a transaction-scoped advisory lock on the
document before its check and insert, so two replicas recording the same
version can't both read the previous digest and both insert. SQLite runs
one write at a time, so it keeps the single statement.
2026-10-30 15:31:31 +01:00
Simon Chrzanowski
53cac1fb74 Merge remote-tracking branch 'origin/main' into feat/apk-snapshots 2026-10-30 15:27:57 +01:00
Simon Chrzanowski
8e760ca26d Merge remote-tracking branch 'origin/main' into feat/npm-stream-rewrite
# Conflicts:
#	internal/handler/handler.go
2026-10-30 15:25:21 +01:00
Simon Chrzanowski
8c9e87f15f Stage fetched packuments, fall back on storage errors, stream version lookups
A fetched packument is now stored under a staging path and checked before
it replaces the cached copy, so an abbreviated one no longer overwrites a
full copy; the full copy is served as when upstream fails. A failure to
store falls back to the in-memory path, as caching is best effort there.

Requests for a single version or dist-tag and the tarball download's
cooldown and integrity lookup read the stored packument as a stream too,
keeping only the one version entry they need.
2026-10-30 15:24:18 +01:00
simon chrzanowski
cb566d5df6
Expire cooldown rewrites when a withheld version becomes available (#638)
A cached rewrite made with cooldown on stays right until a version it held
back leaves cooldown, so it now expires then instead of after a fixed
time. Handlers ask cooldown through Proxy.cooldown, which reports each
withheld version to the rewrites running at that moment. A rewrite that
held nothing back is kept for the shortest cooldown, at most a day.
2026-10-30 14:06:42 +00:00
Simon Chrzanowski
5b57994c09 Stream npm packuments through storage when rewriting them
Closes #525. With metadata caching and the rewrite cache on, a packument
is streamed from upstream into storage and rewritten from there in two
passes: the first collects the time map, dist-tags and version names, the
second copies each kept version and rewrites its tarball URL. Neither the
packument nor its rewrite is held in memory uncompressed, so
metadata_max_size no longer limits packuments.

Both passes check the stored bytes against the digest in the cache row, so
a document replaced in between is not rewritten from mixed bytes. Documents
the stream can't handle (abbreviated, no versions object) and rewrite
errors fall back to the in-memory rewrite.
2026-10-30 14:14:10 +01:00
Simon Chrzanowski
77525b0d78 Store each snapshot recording at a path of its own
Two requests recording the same index version in the same millisecond
stored to one path, and the one whose row was not added deleted the
blob the other's row points at. The path now ends in a random suffix.

Also trims comments that restated the PR, notes in the docs that turning
snapshots off leaves them in place, and moves the git cache setup out of
serve, which lint found too long after the merge.
2026-10-30 10:26:21 +01:00
Simon Chrzanowski
77e07d4be9 Merge remote-tracking branch 'origin/main' into feat/apk-snapshots
# Conflicts:
#	internal/config/config_test.go
2026-10-30 10:23:29 +01:00
Andrew Nesbitt
63a9d9aac2
Merge remote-tracking branch 'origin/main' into feat/composer-advisories 2026-10-30 08:51:44 +00:00
Andrew Nesbitt
e09844e3d7
Merge pull request #610 from simonchrz/feat/mirror-brew
Mirror Homebrew bottles
2026-10-30 08:47:36 +00:00
Andrew Nesbitt
54ad028652
Merge pull request #629 from simonchrz/fix/cooldown-unsupported-ecosystems
Show cooldown only for ecosystems that apply it
2026-10-30 08:43:36 +00:00
Andrew Nesbitt
37ecc3d8ce
Trim BottleImage doc comment and rejoin the brew docs list 2026-10-30 08:40:35 +00:00
Andrew Nesbitt
2928b5c27f
Detect Helm's cooldown check in the handler coverage test 2026-10-30 08:36:39 +00:00
Simon Chrzanowski
39d178f629 Expire cooldown rewrites when a withheld version becomes available
A cached rewrite made with cooldown on stays right until a version it held
back leaves cooldown, so it now expires then instead of after a fixed
time. Handlers ask cooldown through Proxy.cooldown, which reports each
withheld version to the rewrites running at that moment. A rewrite that
held nothing back is kept for the shortest cooldown, at most a day.
2026-10-30 09:33:56 +01:00
Andrew Nesbitt
e0e212942f
Trim npm attestations doc comments 2026-10-30 08:29:33 +00:00
Simon Chrzanowski
50e65a31d8
Serve npm attestations through the proxy
Packuments list each version's provenance at dist.attestations.url on
registry.npmjs.org. npm takes only the path of that URL and resolves it
against the configured registry, so behind the proxy `npm audit
signatures` asked for /-/npm/v1/attestations/... at the proxy's root,
outside /npm, and could not verify attestations.

The rewrite now points dist.attestations.url at
/npm/-/npm/v1/attestations/{name}@{version}, and the handler serves that
path. It reads the version's attestations URL from the packument and
fetches it from the package's registry, the route's for routed packages;
a URL on another host is not followed and the registry's standard path
is asked instead. Attestations never change once published, so they are
cached as immutable metadata.

Versions blocked by the denylist get 403 and versions held back by
cooldown 404, as their tarballs do. Pushed versions have no upstream
attestations and get 404.
2026-10-30 08:26:27 +00:00
Simon Chrzanowski
58e87c2571 Cache cooldown rewrites for a 24th of the shortest cooldown
An hour is long next to a cooldown of a few hours. The TTL is now a 24th
of the shortest cooldown configured, between a minute and an hour, and
the RubyGems and Hex section no longer says a minute.
2026-10-29 19:19:12 +01:00
Simon Chrzanowski
10bc2f3309 Keep rewritten metadata gzip-only and longer with cooldown
pnpm 12 checks every lockfile entry against the registry on each install
unless trustLockfile is set, so each install reads the packument of every
package in the lockfile. The rewrite cache kept each document twice, plain
and gzipped, and with cooldown on for a minute only, so these packuments
were rewritten and compressed again on every install.

Keep only the gzip copy of a cached rewrite, about an eighth of the
document, and decompress it for clients and handlers that need the plain
bytes. Keep rewrites made with cooldown on for an hour. Answer HEAD for
packuments, which pnpm 12 sends before each GET.
2026-10-29 10:52:39 +01:00
Andrew Nesbitt
28ce5f49fc
Make the slow-client storage failure test deterministic 2026-10-28 15:57:06 +00:00
Simon Chrzanowski
2ea13b00d6 Show cooldown only for ecosystems that apply it
The package page showed the default cooldown for every ecosystem, also
for Debian, Alpine, OCI and generic files, which have no publish time to
check. It now shows none there, and the dashboard marks an ecosystem
override for one as not supported. The docs list Helm, which applies
cooldown from the index's created times.
2026-10-28 16:25:08 +01:00
Simon Chrzanowski
2a8ceb9389
Mirror Homebrew bottles
pkg:brew PURLs failed with "unsupported ecosystem: brew", although listing their versions worked. Homebrew publishes a formula's bottles as an image on its artifact upstream, which proxy mirror can already fetch, so a brew PURL is now resolved to that image and mirrored like one: the formula's JSON API gives the tag, which carries the revision and the bottles' rebuild, and --platform selects bottles like image platforms.

Listing a formula's versions returned the versioned siblings the Homebrew client adds (openssl@3.6 for openssl@3) as versions of it; they are now left out.
2026-10-28 15:18:14 +00:00
Andrew Nesbitt
1c4b9d9994
Merge pull request #621 from simonchrz/feat/go-vulndb
Serve the Go vulnerability database at /go/vulndb
2026-10-28 13:45:46 +00:00
Simon Chrzanowski
9240f9edef Keep the upstream Composer index in memory
Serving packages.json fetched the upstream's on every request with
cache_metadata off, and after each TTL with it on, so every Composer
command waited on the upstream and on http_timeout when it was down.
The parsed index is now kept for the metadata TTL, or an hour when it
is 0, and a failed fetch is not retried within that time.
2026-10-28 14:10:48 +01:00
Simon Chrzanowski
32406d69cd Merge remote-tracking branch 'origin/main' into feat/composer-advisories 2026-10-28 14:07:39 +01:00
Andrew Nesbitt
58925cadea
Merge pull request #609 from simonchrz/feat/stream-cache-miss
Send artifacts to the client while a cache miss is stored
2026-10-28 12:59:52 +00:00
Andrew Nesbitt
7eed8bc135
Refer to govulncheck's -db flag in comments 2026-10-28 12:58:27 +00:00
Andrew Nesbitt
626429d337
Merge pull request #624 from simonchrz/feat/npm-dist-tags
Answer npm dist-tag ls from the served packument
2026-10-28 12:45:16 +00:00