Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 15:49:16 -05:00
Commit graph

620 commits

Author SHA1 Message Date
Andrew Nesbitt
ac137a3289
Extract portable npm metadata transformations 2026-10-18 17:59:14 +01:00
Andrew Nesbitt
1950c0b6a4
Merge pull request #487 from git-pkgs/dependabot/go_modules/github.com/aws/aws-sdk-go-v2/service/ecr-1.66.2
Bump github.com/aws/aws-sdk-go-v2/service/ecr from 1.66.1 to 1.66.2
2026-10-15 16:55:55 +01:00
dependabot[bot]
b8d5cf4f27
Bump github.com/aws/aws-sdk-go-v2/service/ecr from 1.66.1 to 1.66.2
Bumps [github.com/aws/aws-sdk-go-v2/service/ecr](https://github.com/aws/aws-sdk-go-v2) from 1.66.1 to 1.66.2.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.66.1...service/s3/v1.66.2)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/ecr
  dependency-version: 1.66.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-15 15:33:50 +00:00
Andrew Nesbitt
78b1913865
Merge pull request #490 from git-pkgs/dependabot/go_modules/github.com/prometheus/client_golang-1.25.0
Bump github.com/prometheus/client_golang from 1.24.1 to 1.25.0
2026-10-15 16:31:13 +01:00
Andrew Nesbitt
29046e6013
Merge pull request #489 from git-pkgs/dependabot/go_modules/github.com/aws/aws-sdk-go-v2/config-1.33.7
Bump github.com/aws/aws-sdk-go-v2/config from 1.33.6 to 1.33.7
2026-10-15 16:30:33 +01:00
Andrew Nesbitt
1a4a372c4c
Merge pull request #488 from git-pkgs/dependabot/go_modules/golang.org/x/sync-0.24.0
Bump golang.org/x/sync from 0.23.0 to 0.24.0
2026-10-15 16:29:35 +01:00
Andrew Nesbitt
7e35cfc4dd
Merge pull request #483 from gilesw/feat/upstream-retry-after
Hold requests to an upstream that rate limits the proxy
2026-10-15 16:27:51 +01:00
Andrew Nesbitt
eb29011659
Merge pull request #485 from simonchrz/perf/gzip-rewritten-metadata
Serve cached npm and Composer metadata gzip-compressed
2026-10-15 16:25:09 +01:00
dependabot[bot]
ba61c45077
Bump github.com/prometheus/client_golang from 1.24.1 to 1.25.0
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.24.1 to 1.25.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/v1.25.0/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.24.1...v1.25.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-version: 1.25.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-15 15:16:44 +00:00
dependabot[bot]
fc312a0c59
Bump github.com/aws/aws-sdk-go-v2/config from 1.33.6 to 1.33.7
Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.33.6 to 1.33.7.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.33.6...config/v1.33.7)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-version: 1.33.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-15 15:16:38 +00:00
dependabot[bot]
b884a5f63f
Bump golang.org/x/sync from 0.23.0 to 0.24.0
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.23.0 to 0.24.0.
- [Commits](https://github.com/golang/sync/compare/v0.23.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-15 15:15:16 +00:00
Giles Westwood
b2955ef458 Keep holds through late successes and away from the circuit breaker
A response arriving during a hold was sent before it began, so a late
success no longer releases the host; only one after the hold has ended
does.

Artifact fetches check RateLimitGate.Held before calling the fetcher, so
held cache misses fail with the HeldError without counting as failures
in the host's circuit breaker.

Every handler that answers an upstream fetch error with 502 now answers
a held request with 503 and Retry-After instead.

OnHold and RecordUpstreamRateLimitHold drop the unused duration.
2026-10-15 13:35:12 +01:00
simon chrzanowski
e74e5abc79
Serve cached npm and Composer metadata gzip-compressed
- Keep a gzip copy of each rewrite cache entry when it is smaller, made once
  per rewrite and counted against metadata_rewrite_cache_size
- Serve it with Content-Encoding: gzip to clients that accept gzip, with an
  ETag of its own ("<sha256>-gzip") and Vary: Accept-Encoding
- Rewrites made outside the cache (cache disabled, cooldown on) stay
  uncompressed
- rewriteCache.get and rewrite return rewrittenMetadata
2026-10-15 12:46:55 +02:00
Andrew Nesbitt
8bc8b3ab75
Merge pull request #484 from gilesw/fix/maven-checksums-as-artifacts
Cache Maven checksums and signatures as artifacts
2026-10-15 09:34:54 +01:00
Andrew Nesbitt
88cf5be075
Merge pull request #482 from simonchrz/fix/sandbox-upstream-content
Sandbox upstream content on package routes
2026-10-15 09:31:23 +01:00
Andrew Nesbitt
adf9d3097c
Merge pull request #481 from simonchrz/fix/registry-token-scope
Only send credentialed registry tokens to trusted registries
2026-10-15 09:29:50 +01:00
Andrew Nesbitt
cbf457bceb
Merge pull request #479 from simonchrz/feat/registry-checksums
Verify npm and Composer archives against their SHA-1 shasum
2026-10-15 09:28:48 +01:00
Giles Westwood
e9ecde70ed Hold requests to an upstream that rate limits the proxy
A 429, or a 503 with Retry-After, from an upstream host now stops the
proxy sending that host anything for as long as it asked (capped at an
hour), or for a minute doubling up to fifteen when no Retry-After came.
Held requests fail locally: cached metadata is served stale as before,
and anything uncached answers 503 with the remaining Retry-After so the
client paces itself too.

The gate is a RoundTripper under the auth transport, so metadata,
artifact and passthrough fetches all obey it. Metadata fetches had no
retry or circuit breaker, so a rate-limited upstream kept receiving one
request per cache miss for the whole block. /health lists held hosts
under upstream_holds and proxy_upstream_rate_limit_holds_total counts
holds.
2026-10-14 21:35:17 +01:00
Giles Westwood
6051e20adf Cache Maven checksums and signatures as artifacts
Checksum and .asc files beside a versioned artifact are as immutable as the
artifact, but were routed through the metadata path: uncached until a
first successful fetch, with no retry or circuit breaker. A build pulling
hundreds of artifacts sent as many checksum requests straight to Maven
Central, which rate-limited the proxy, after which every further checksum
request still went upstream and failed.

Only maven-metadata.xml and its own checksums stay on the metadata path,
since the version listing changes with each release.
2026-10-14 21:34:57 +01:00
Simon Chrzanowski
3bbb5aa7a3 Give the detached fetch test time on slow runners
TestServerKeepsFetchAfterClientHangsUp gave the upstream fetch 5s to start
and the follow-up request 5s to be answered. On a Windows CI runner, where
the whole test took 13s, the follow-up timed out awaiting headers with
nothing wrong. The test checks the outcome, not the speed, so allow 30s
for both. A real hang still fails.
2026-10-14 18:14:06 +02:00
Simon Chrzanowski
ba5e6e84c5 Leave Composer dev branch archives unverified
A version's integrity is a single value shared by every archive cached
for it, but a dev branch caches one archive per commit. Recording the
listed head's shasum made a cache hit for an older pinned commit fail its
check, so its archive was discarded and fetched again, which cleared the
head's recorded shasum in turn. The listed shasum only ever describes the
current head, and Composer checks dist.shasum itself.

- Skip the shasum for dev versions
- Test a pinned commit, then the head, then both again from the cache
2026-10-14 18:06:01 +02:00
Simon Chrzanowski
bf74d982d7 Verify npm and Composer archives against their SHA-1 shasum
npm versions published before dist.integrity existed carry only a SHA-1
dist.shasum, and Composer metadata lists only a SHA-1 dist.shasum, so
neither could be checked.

- Accept native integrity given as an algorithm-prefixed hex checksum,
  verified with integrity's exact-digest check, so a recorded "sha1-<hex>"
  is also checked on cache hits
- npm: fall back to dist.shasum when dist.integrity is missing or cannot
  be parsed; a usable dist.integrity still takes precedence
- Composer: verify an archive against its dist.shasum, except another
  commit of a branch fetched in place of the listed one, which the listed
  shasum does not describe
- Fetch unverified as before when no shasum is listed, as Packagist does
  for GitHub archives, or it is not a SHA-1 digest
2026-10-14 18:04:31 +02:00
Simon Chrzanowski
cbb1978026 Bump git-pkgs/integrity to v0.2.0
v0.2.0 adds verification-only SHA-1 and MD5, algorithm-prefixed checksum
parsing and exact-digest verification (git-pkgs/integrity#3).
2026-10-14 18:04:31 +02:00
Andrew Nesbitt
e0fb8be4d9
Merge pull request #473 from simonchrz/fix/artifact-integrity
Verify npm tarballs against dist.integrity
2026-10-14 16:56:28 +01:00
Simon Chrzanowski
6a90fb411f Only send credentialed registry tokens to trusted registries
Credentials configured for a token service were applied to any challenge
naming it. A host the proxy fetches from, such as one in a Composer dist
URL or a Helm index, could answer a /v2/ request with a Bearer challenge
naming that realm, and the proxy fetched a token with the configured
credentials and sent it to that host. A token cached from a legitimate
pull was handed over the same way.

- Follow a challenge to a realm with configured credentials only for a
  registry that has configured credentials of its own, or is on the same
  site (registrable domain) as the realm, as registry-1.docker.io is with
  auth.docker.io; otherwise pass the 401 back
- Keep following anonymous challenges for any registry, as before
- Document the rule
2026-10-14 17:56:18 +02:00
Simon Chrzanowski
cf7eafbbe8 Sandbox upstream content on package routes
Package routes share an origin with /ui, and serve bodies whose
Content-Type comes from upstreams or from whoever pushed a hosted package.
An artifact served as text/html or image/svg+xml ran its scripts with
access to the UI.

- Send X-Content-Type-Options: nosniff and Content-Security-Policy: sandbox
  on every package-route response: cached and streamed artifacts, cached
  metadata, relayed upstream responses and errors
- Archive files browsed from /ui already get the same two headers
2026-10-14 17:53:48 +02:00
Andrew Nesbitt
2a8717cb77
Merge pull request #477 from git-pkgs/fix/oci-referrers-tag-offline
Answer uncached referrers tags with 404 when the upstream is down
2026-10-14 16:52:18 +01:00
Andrew Nesbitt
0add4d7368
Merge pull request #476 from git-pkgs/fix/oci-multiple-accept-headers
Join every Accept header on container manifest requests
2026-10-14 16:51:38 +01:00
Andrew Nesbitt
18f6587f36
Merge pull request #475 from git-pkgs/fix/browse-encode-file-path
Encode file paths when loading files in the source browser
2026-10-14 16:50:51 +01:00
Simon Chrzanowski
2d258ddc4b Verify npm tarballs against dist.integrity
The proxy cached the first tarball upstream sent for a version without
checking it against the packument, so a tampered tarball was served to
every client after it.

- Pass the packument's dist.integrity to the existing integrity checks, so
  a fresh tarball is verified as it is stored or streamed; a mismatch is
  discarded and returns 502, or aborts the streamed response
- Record it with the cached version, so cache hits are verified against it
  through the same check as before
- Verify with integrity.Result.Verify, which accepts any digest of the
  strongest algorithm listed
- Fetch unverified as before when dist.integrity is missing or cannot be
  parsed, or the packument cannot be fetched
2026-10-14 15:40:36 +02:00
Andrew Nesbitt
544e1a5bdd
Note that the container registry is pull-only 2026-10-14 13:17:37 +01:00
Andrew Nesbitt
27f65297c6
Merge pull request #464 from simonchrz/133_local-package-upload
Push npm and Composer packages into the proxy
2026-10-14 13:09:53 +01:00
Andrew Nesbitt
513687ce10
Answer uncached referrers tags with 404 when the upstream is down
Docker falls back to a sha256-<digest> tag when the referrers API fails
and aborts the pull on anything but a manifest or 404.
2026-10-14 12:50:03 +01:00
Andrew Nesbitt
dc809b0762
Join every Accept header on container manifest requests
Docker sends one Accept header per media type. Reading only the first
forwarded just that type upstream and ruled out cached manifests of the
others, so pulls failed when the upstream was down.
2026-10-14 12:43:14 +01:00
Andrew Nesbitt
ba81c1db42
Merge pull request #438 from pinguinfuss/issue-411-mirror-images
Mirror container images with proxy mirror
2026-10-14 12:25:04 +01:00
simon chrzanowski
c0ac418160
Serve pushed artifacts when streaming upstream artifacts
- GetLocalArtifact went through checkCache, which reports a miss when StreamArtifacts is set, so npm and Composer downloads fetched the upstream archive instead of the pushed one
- Split readStored out of lookupCache and read pushed artifacts through it regardless of StreamArtifacts
- Keep the denylist check and the 503/404 answers for unreadable or missing pushed artifacts
- Test npm and Composer downloads of pushed archives with StreamArtifacts set

Refs #133
2026-10-14 13:23:19 +02:00
simon chrzanowski
8265c7d44b
Nest the hosted commands under proxy hosted
proxy list and proxy delete sat next to serve, stats and mirror and read as cache operations. The commands are now proxy hosted push, proxy hosted list and proxy hosted delete, before a release makes the names hard to change. proxy hosted without a known subcommand prints the three.

Refs #133
2026-10-14 13:23:19 +02:00
simon chrzanowski
eca224bb56
Leave denied pushed versions out of package metadata
Pushing and downloading refuse denied versions, but npm and Composer metadata listed every pushed version. A version denied after it was pushed stayed listed, so resolvers picked it and then got 403 on the download. localVersions now leaves denied versions out, which covers both ecosystems and the hosted-only namespaces; npm latest falls back to the highest allowed version. /api/hosted still lists them, so operators can see and delete them.

Refs #134
2026-10-14 13:23:19 +02:00
simon chrzanowski
2a957b92e9
Prefer the root composer.json of a pushed archive
The manifest lookup took the first composer.json at the root or inside a top directory, in zip order. Dist archives keep composer.json files for tests and examples, so tests/composer.json stored before the root file refused the push with a name mismatch. The root composer.json now always wins. Without one, the manifest inside a top directory is used only when there is exactly one; more than one refuses the archive as ambiguous.

Refs #134
2026-10-14 13:23:19 +02:00
simon chrzanowski
e0ab227f14
Never replace a pushed artifact with the upstream copy
checkCache answered nil both when a stored object couldn't be opened and after an integrity failure had cleared the record. Download handlers then fetched the upstream archive for the same version and filename, and recording it turned the pushed row into an upstream one without its manifest and queued the pushed object for deletion. One storage error while downloading a patched version served the unpatched upstream archive and dropped the push.

checkCache now looks at the record on a miss. For a pushed artifact it returns ErrHostedArtifactUnavailable when the object is recorded but couldn't be served, and ErrHostedArtifactMissing when no object is recorded any more. Every path that would fetch upstream goes through it, so none of them can replace the pushed artifact. Downloads answer 503 and 404 for the two.

Refs #133
2026-10-14 13:23:19 +02:00
simon chrzanowski
3dae0fbae0
Stop UpsertPackage from clearing packages.source
The artifact source change added source = excluded.source to the packages upsert as well. source isn't in that INSERT's column list, so every package upsert set the column to NULL. The packages upsert is back to what it was, with a test that an existing source survives it.

Refs #133
2026-10-14 13:23:19 +02:00
simon chrzanowski
69313c26d9
Serve hosted-only npm scopes and Composer vendors from pushed packages alone
A package the upstream doesn't know is served from its pushed versions, but the upstream is still asked about it. Once someone publishes the same name there, their versions are listed next to the pushed ones and a version range can resolve to them.

hosted.npm_scopes and hosted.composer_vendors (PROXY_HOSTED_NPM_SCOPES, PROXY_HOSTED_COMPOSER_VENDORS) name namespaces whose packages are served from pushed versions alone. Their metadata is built from what was pushed, npm latest included, and anything not pushed answers 404. The upstream is never asked about them. Packages in other namespaces are proxied as before.

The npm metadata handler's decisions about pushed versions move into serveLocalMetadata.

Refs #133
Refs #134
2026-10-14 13:23:19 +02:00
simon chrzanowski
0916eabe37
Fix leftover /api/packages references to the hosted endpoints
Refs #133
2026-10-14 13:23:19 +02:00
simon chrzanowski
96241b910b
Serve pushed npm tarballs before the cooldown check
The packument lists pushed versions without cooldown, but the tarball download checked cooldown against the upstream's publish times before looking at the cache. A pushed override of a version the upstream published recently was listed and then withheld with 404. Pushed tarballs are now served right after the denylist check.

Refs #134
2026-10-14 13:23:19 +02:00
simon chrzanowski
c0b2a200c2
Document how pushed versions appear in package metadata
Refs #134
2026-10-14 13:23:19 +02:00
simon chrzanowski
112fc590b7
List pushed Composer versions in package metadata
With hosted packages enabled, a package's p2 metadata lists its pushed versions, built from composer.json without the root-only keys, with a zip dist at the proxy and the time they were pushed. dist.reference is the archive's sha256 so a re-push isn't served from Composer's cache, and shasum is left empty. Dev versions go to the ~dev file and tagged versions to the other.

A pushed version takes the place of an upstream entry with the same version, with or without a leading v. Pushed entries follow the upstream ones and go through the same minified writer, so fields an earlier upstream entry set and the pushed one lacks are written as __unset rather than inherited.

When the upstream doesn't know the package, the document holds only the pushed versions. Like npm, documents with pushed versions bypass the rewrite cache.

Refs #134
2026-10-14 13:23:19 +02:00
simon chrzanowski
c566949573
List pushed npm versions in package metadata
With hosted packages enabled, the packument of a package with pushed versions lists each of them, built from its package.json with dist pointing at the proxy and carrying the archive's sha512, and the time it was pushed. A pushed version takes the place of an upstream entry with the same version. Cooldown and the denylist still filter only upstream versions, and dist-tags stay as upstream has them.

When the upstream doesn't know the package, the packument holds only the pushed versions, with latest set to the highest of them. Requests for a single pushed version are answered from its entry.

Rewrites that include pushed versions bypass the rewrite cache, which is keyed by the upstream document alone, so a push or delete shows on the next request and changes the ETag. Packages without pushed versions take the cached path as before, and with hosted packages disabled there is no extra lookup.

Refs #134
2026-10-14 13:23:19 +02:00
simon chrzanowski
e7449d1540
Read the package manifest of pushed archives
Resolvers take a version's dependencies from the registry metadata, not from the archive, so listing a pushed version needs its manifest. A push now reads package.json from the top directory of an npm tarball, or composer.json from the root or the single top directory of a Composer zip, and keeps it in the new artifacts.manifest column (migration 013_add_artifact_manifest).

The manifest has to name the pushed package and version. A Composer manifest may leave the version out, and its name is compared case-insensitively. An archive that can't be read or whose manifest names something else is refused with 400 and removed from storage.

The version's integrity is now the archive's sha512, which npm clients expect in dist.integrity.

Refs #134
2026-10-14 13:23:19 +02:00
simon chrzanowski
80fdd16719
Add push, list and delete commands for hosted packages
proxy push, proxy list and proxy delete call the /api/hosted endpoints of a running proxy. The proxy URL comes from -url or PROXY_URL and the token from PROXY_HOSTED_TOKEN, so it never ends up in shell history or process listings.

Document the hosted config block, the endpoints, the supported ecosystems and how pushed archives interact with eviction.

Refs #133
2026-10-14 13:23:19 +02:00
simon chrzanowski
5e66688873
Push, list and delete npm and Composer archives through /api/hosted
With hosted.enabled, PUT /api/hosted/{ecosystem}/{name}/{version} stores the request body as the archive for that version, marked local. It replaces any cached upstream copy and is served from the cache without contacting the upstream. GET /api/hosted/{ecosystem}/{name} lists what was pushed and DELETE removes a version again, after which clients get the upstream archive if there is one. Pushing and deleting need the bearer token; listing is open like the rest of the API.

Download handlers look archives up by the filename clients request, so a push is stored under the name its ecosystem derives from name and version: {name}-{version}.tgz for npm, without the scope, and {package}-{version}.zip for Composer. Other ecosystems are refused for now.

The Composer download handler resolves the upstream metadata before it looks at the cache and answers 404 for versions the upstream doesn't list. It now serves a pushed archive first. Only artifacts marked local take that path, so cached upstream archives still go through the metadata and commit checks.

The version's integrity is set to the pushed bytes, so the cache's integrity check verifies them rather than an upstream digest. Names and versions with path separators, dot segments or control characters are refused before anything is stored.

Refs #133
2026-10-14 13:23:19 +02:00