Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-09-17 00:02:04 -04:00
Commit graph pkg-proxy/internal/server
Author SHA1 Message Date
Andrew Nesbitt
b67cfb1014
Add Homebrew JSON API and bottle proxy support (#254)
* Add Homebrew JSON API and bottle proxy support

* Fix Homebrew HEAD offline fallback and non-sha256 OCI manifest handling

Route Homebrew API HEAD requests through ProxyCached so a warm cache
answers without an upstream call and stale entries are served when the
upstream is unreachable. HEAD still reaches upstream as HEAD when
metadata caching is disabled.

Limit OCI manifest digest verification to sha256 references and
Docker-Content-Digest headers so other digest algorithms are proxied
instead of rejected, and log the failing expected value.

* Reconcile with #280 and #301 after rebase

Compute real manifest digests in #280's fixture upstreams so the new
verification accepts them, and add headerETag / headerLastModified to

* Send fixed Accept for Homebrew API and match If-None-Match properly

The Homebrew API cache key does not include Accept, so replaying the
client header could serve one representation under another; the API
does not negotiate anyway. Compare If-None-Match with weak comparison,
list splitting and "*" per RFC 7232 instead of string equality, and
apply the same helper to the metadata and swift responders.

* Reconcile with #298 and #304 after rebase

Move the configureScanning doc comment back to its function after the
auto-merge stacked it on mountProtocolHandlers, and drop the second
ETag/Last-Modified set in writeMetadataCachedResponse now that the
pre-304 set covers both response paths.
2026-09-03 16:59:12 +01:00
Giles Westwood
30e54a1e0c
Add generic HTTP download proxy for GitHub release assets (mise/aqua) (#302)
* Add generic HTTP download proxy for GitHub release assets

Adds a /generic/{name}/ route backed by a new upstream.generic named-upstream
map, so tools that download from fixed URL shapes (mise's aqua backend
fetching GitHub release assets, and its tag lookups on api.github.com) can be
pointed at the proxy with client-side URL rewriting. Only configured
upstreams are reachable, so this is not an open HTTP proxy.

Paths shaped like {owner}/{repo}/releases/download/{tag}/{asset} are
version-pinned and go through the artifact cache: fetched once, hashed,
served without revalidation, and still served when the upstream is down.
Every other path goes through the metadata cache with the client's Accept
header and query string replayed, so API responses are fresh within
metadata_ttl, revalidated after that, and served stale when the upstream
fails or rate-limits the request.

Tests cover path classification, unknown upstreams and traversal, cache
hits with the upstream down, HEAD, 404 pass-through, Accept and query
forwarding, stale-on-429, cache isolation between upstreams, and that an
upstream token scoped to the release host is not sent to the object store
it redirects to.

Closes #183.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDjDeq27CKzEP2o3GWBY7F

* Use fixed Accept header for generic metadata

---------

Co-authored-by: Giles Westwood <giles@gileswestwood.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 16:44:54 +01:00
aarnaud
5499837084
Add pre-cache artifact scanning hook (trivy/ClamAV/Wiz/custom) (#298)
Runs fetched artifacts through pluggable external scanners after they're
staged in storage but before they're committed to the cache DB, so a
block verdict deletes the object instead of ever exposing it to a
client. Scanners pull the staged bytes themselves via a short-lived
HMAC-signed internal route rather than the proxy pushing bytes to them,
keeping the mechanism storage-backend-agnostic and avoiding uploading
potentially huge artifacts through the proxy's own egress.

Hardening baked in from the start: the internal scan-fetch route is
gated both at router-mount time and in the handler so it's inert
whenever scanning is disabled or unsigned; the signing key is mandatory
whenever scanning is enabled, enforced directly in scanner.NewGroup
rather than relying on callers to invoke config validation; the scan
call and the delete-on-block cleanup both run on a context detached
from the client's, so a client disconnecting mid-scan can't be mistaken
for a scanner failure, doesn't cause a legitimate artifact to be
deleted, and doesn't leave a genuinely blocked artifact's bytes
orphaned in storage; and scanner infrastructure errors (connection
failures, internal hostnames) are never forwarded verbatim to anonymous
clients, only a generic message. The scan-error metric also correctly
distinguishes a scanner's own timeout from being cancelled because a
sibling scanner already decided the verdict.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 10:06:44 +01:00
Andrew Nesbitt
7743417c72
Add Swift package registry support (#259)
* Add Swift package registry support

* Fix Swift archive integrity handling

* Fix Swift registry pagination and archive HEAD requests

* Canonicalize Swift package identifiers

* Handle Swift registry cache identities

* Use stored PURLs for cache lookups

* Address review: upstream-hash refetch and cache PURL cleanup

- Re-fetch instead of 502 when a cached artifact's hash disagrees with
  the upstream-declared checksum; log and discard the stale entry.
- Rename expectedHash to upstreamHash and document how the check
  differs from checkCache's stream integrity verification.
- Drop the repository_url qualifier from swift cache PURLs so cache
  entries survive an upstream.swift change, matching other ecosystems.
- Pass name to handleSourceArchiveHead instead of re-deriving it.

* Drop BulkCheckVulnerabilities coverage after #279 removed it

The rebase over #279 (dead-code cleanup) drops BulkCheckVulnerabilities;
remove the tests and helper that exercised the swift-identity-filtering
path through it, and the imports they pulled in.
2026-09-02 13:06:34 +01:00
wickedOne
cad1a9226a
Report upstream circuit breaker state in /health and /metrics (#275)
* Report upstream circuit breaker state in /health and /metrics

* applied requested changes

* apply review changes

* Bumped github.com/git-pkgs/registries to v0.9.0
2026-09-02 12:52:32 +01:00
Andrew Nesbitt
b5ee6dd96c
Add ECR auto-refreshing upstream authentication (#278)
* Add ECR auto-refreshing upstream authentication

- Add "ecr" auth type to upstream.auth config with optional region
- Cache ecr:GetAuthorizationToken results per region and refresh
  shortly before expiry via the AWS SDK default credential chain
- Route type: ecr through the token cache in Server.authForURL
- Document the new type in config.example.yaml and docs/configuration.md

Fixes #276

* Collapse ecrTokens.header to a single return path

Drops the internal/server package below the goconst min-occurrences
threshold for the Authorization literal.

* Coalesce concurrent ECR token fetches with singleflight

Concurrent cache misses for the same region now share a single
GetAuthorizationToken call instead of each issuing their own, avoiding
a request burst against the ECR API at cold start and at each 12-hour
refresh. golang.org/x/sync is already a direct dependency.

* Improve ECR token refresh and region inference

* Back off failed ECR token refreshes
2026-09-02 12:43:43 +01:00
pinguinfuss
4b9b401d1f
Add Alpine APK repository proxy support (#293)
* Add Alpine APK repository proxy support

- Serve named APK repositories at /apk/{repository}/ with the official
  Alpine mirror as the default repository
- Cache v2 APKINDEX.tar.gz and v3 Packages.adb indexes and detached
  signatures via the metadata cache, serving stored bytes unchanged so
  apk signature verification keeps working
- Cache .apk packages in the shared artifact cache keyed by the full
  repository path, since APK filenames do not include the architecture
- Add configurable upstream repositories via upstream.apk with
  validation, plus dashboard registry instructions
- Add tests for index/signature byte fidelity, per-arch caching, cache
  hits, offline reads, upstream authentication, and 404 handling
- Document apk usage in README, config example, and configuration docs

* Serve APK package HEAD requests without a body

Use the method-aware serveArtifact helper (as container.go does) so HEAD
responses carry Content-Length but omit the body; add a regression test.

* Drop doubled blank line in docs/configuration.md

---------

Co-authored-by: Andrew Nesbitt <andrewnez@gmail.com>
2026-09-02 12:36:00 +01:00
Abhinav Gautam
7f8ccab96f
Accept inline SBOM documents in mirror API (#294)
* feat(mirror): accept inline SBOM API requests

* fix(mirror): address inline SBOM review feedback
2026-09-02 12:26:29 +01:00
Victor Chacon Codesseira
7e1cb68c7c
Add upstream.npm_full_metadata to serve publish times without cooldown (#297)
- Request application/json from the npm upstream when the option is set,
  independent of cooldown, so served packuments carry the "time" map
- Wire the option through the shared Proxy struct and the
  PROXY_UPSTREAM_NPM_FULL_METADATA environment override
- Document it in config.example.yaml and docs/configuration.md
- Test that the option forces full metadata with cooldown disabled
2026-09-02 12:13:12 +01:00
Andrew Nesbitt
41ae7d6520
Add GCS storage backend with lighter dependencies (#179)
* Add GCS storage backend with Workload Identity support

Register gocloud.dev/blob/gcsblob so gs:// URLs are accepted as a storage
backend. Authentication uses Application Default Credentials, which makes
GKE Workload Identity work out of the box; signed URLs (direct_serve)
fall back to the IAM Credentials signBlob API when no private key is
available.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Replace gcsblob with lightweight GCS backend

* Extract GCS client into standalone module

---------

Co-authored-by: Anthony A. <github@anthony-arnaud.fr>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-08-29 11:23:07 +01:00
Andrew Nesbitt
f43aa9d13e
Make built-in upstream URLs configurable (#255)
Every built-in ecosystem upstream can now be set via the upstream
config block or PROXY_UPSTREAM_* env vars, with allow_private_hosts
and allow_loopback controlling access to non-public addresses.
2026-08-29 11:19:54 +01:00
Andrew Nesbitt
272e6d9040
Lint and dead-code cleanup (#279)
* Bump go tool golangci-lint to v2.13.1

The .golangci.yml goconst.ignore-tests setting was added in v2.12.0
(golangci/golangci-lint#6480). On the previously pinned v2.10.1,
config verify fails with "additional properties 'ignore-tests' not
allowed" and the setting is silently ignored at run time, so goconst
counts test-file literals toward min-occurrences.

* Apply gofmt and CutSuffix simplification

- gofmt -w internal/server/health_test.go
- Replace HasSuffix+TrimSuffix with CutSuffix in ParseSize

* Remove dead code and migrate tests off legacy Filesystem storage

Migrate the three test call sites of storage.NewFilesystem to
storage.OpenBucket("file://...") and drop the deprecated
StorageConfig.Path field from test configs, then delete code that
deadcode reports as unreachable from cmd/proxy:

- internal/storage/filesystem.go and its tests
- storage.HashingReader
- enrichment.Service.BulkCheckVulnerabilities and NormalizeLicense
- server.ActiveRequestsMiddleware (no-op body; the real tracking
  is the inline r.Use at server.go:226)
- mirror.RegistrySource (unimplemented stub)

metrics.UpdateCircuitBreakerState and RecordCircuitBreakerTrip are
kept because #275 wires them.

Update the CONTRIBUTING.md storage section to reflect blob.go.
2026-08-21 09:26:27 +01:00
Ching Wei Kang
c1f09e7921
Show build information in web UI (#257)
* Show build information in web UI

Signed-off-by: WilliamK112 <164879897+WilliamK112@users.noreply.github.com>

* Fix footer build info shadowed by page Version fields

Shared footer templates were reading .Version and .Commit, which resolve
to package data on VersionShowData and BrowseSourceData. Point the footer
at Layout.BuildInfo and cover both pages so the proxy version stays visible.

Signed-off-by: WilliamK112 <164879897+WilliamK112@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix Layout build info field promotion

---------

Signed-off-by: WilliamK112 <164879897+WilliamK112@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-21 09:25:02 +01:00
Abhinav Gautam
088027cac3
feat: add Helm repository proxy support (#268)
* feat: add Helm repository proxy support

* fix(helm): address review feedback

* fix(helm): serve cached charts without index
2026-08-16 18:12:55 +01:00
Andrew Nesbitt
49a68f1d81
Record proxy request metrics (#270) 2026-08-16 18:12:03 +01:00
Andrew Nesbitt
e4fbf3f277
Add JSONL access logging (#269)
* Add JSONL access logging

* Initialize access log before server dependencies
2026-08-16 18:07:39 +01:00
wickedOne
849500de1e
fix: decode PURL percent-encoding in versions and package paths (#244)
* fix: decode PURL percent-encoding in versions and package paths

* review fix
2026-08-14 10:38:08 +01:00
Andrew Nesbitt
bbea63f046
fix(upstream): apply authentication through shared transport (#198)
* upstream: apply authentication through shared transport

* Address upstream authentication review findings
2026-08-13 07:08:23 +01:00
Vincent Palatin
800ffc6b38
Make Debian upstream repository configurable. (#229)
* Refactor LoadFromEnv to use helpers

Avoid triggering the linter about the cyclomatic complexity of the LoadFromEnv
function in later changes by refactoring it to use setEnvString/setEnvBool
helpers.

No functional change, just collapse ~29 repetitive if-blocks into single-line
calls to two small helpers.

* Make Debian upstream repository configurable

Support overriding the Debian handler's upstream (e.g. Ubuntu archives)
via PROXY_UPSTREAM_DEBIAN or upstream.debian in the config file.

Tested with PROXY_UPSTREAM_DEBIAN=http://archive.ubuntu.com/ubuntu
to get Ubuntu Resolute packages.
2026-08-05 17:48:00 +01:00
wickedOne
63f0efd0e9
fix(pypi): resolve name and version for PEP 658 metadata sidecars (#222)
* resolve name and version for PEP 658 metadata sidecars

* fix(pypi): parse Windows installer and egg filenames separately

The bdist_wininst and bdist_msi layout joins the platform to the version
with a '.' rather than a '-', so treating .exe/.msi like a wheel folded
the platform into the version: foo-1.0.win32-py2.0.exe resolved to
version "1.0.win32". Eggs shared the problem, as setuptools' hyphen
escaping is not universal: aws-sdk-1.0.0-py3.11.egg resolved to name
"aws", version "sdk".

Give each format its own parser. Wheels keep the PEP 427
spec-guaranteed field positions, eggs locate the version relative to the
py{X.Y} interpreter field, and Windows installers strip the platform and
interpreter fields before splitting name from version.

A PEP 658 sidecar resolves to the same name and version as the
distribution it describes, so it is cached under that version. Browse and
compare took the first cached artifact without checking its extension,
handing openArchive plain text: a version pip had only fetched metadata
for reported hasCached and then 500'd.

Add firstBrowsableArtifact, replacing five duplicated selection loops,
and export PyPIMetadataSuffix so the suffix has a single definition.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 11:14:03 +01:00
Andrew Nesbitt
7dbf13e345
Avoid repeated content type path lookup 2026-08-01 11:22:13 +01:00
Andrew Nesbitt
36f3a51c65
Detect content types when browsing files 2026-07-31 17:04:01 +01:00
Andrew Nesbitt
cf3741162f
fix(upstream): honor npm and Cargo overrides (#200)
* fix(upstream): honor npm and cargo overrides

* Preserve npm scope separators in download URLs

* Apply upstream auth to metadata requests
2026-07-26 19:11:24 +01:00
Andrew Nesbitt
be15a0f826 Fix cooldown overrides for scoped package PURLs 2026-07-13 17:11:04 -07:00
Andrew Nesbitt
0ae63066e2
Make upstream HTTP timeout configurable
Add http_timeout config option (and PROXY_HTTP_TIMEOUT env var) to set
the per-request timeout on the shared HTTP client used by protocol
handlers for upstream metadata fetches and pass-through file requests.
Defaults to the previous hardcoded 30s; "0" disables the timeout.

Fixes #187
2026-07-07 17:48:36 +01:00
Bruno Clermont
f4a8c5606a
Fix package redirection to '/ui/packages' (#169)
cause I get 404 every time
2026-06-26 07:54:00 -04:00
Andrew Nesbitt
b2011c0a54
Log actual database config instead of sqlite path (#176)
The startup log and /stats endpoint always reported Database.Path,
which is the sqlite default even when PROXY_DATABASE_DRIVER=postgres
is set and a postgres URL is in use. Add DatabaseConfig.String() that
returns the sqlite path or the postgres URL with the password redacted,
and use it in both places.

Fixes #173
2026-06-26 07:42:30 -04:00
Andrew Nesbitt
8c28b02035
Polish UI: lucide icons, sticky footer, hamburger nav, clickable logo (#162)
- Move third-party JS into static/vendor/ and add lucide for icons; refine
  .gitignore so embedded vendor dirs aren't caught by the Go vendor rule.
- Replace folder/file emojis in the source browser with lucide icons.
- Wrap the header logo and title in a single anchor so the icon is clickable.
- Drop the redundant "Powered by git-pkgs" footer block; add a GitHub repo
  link to the About column and bump the ecosystem count from 16+ to 17+.
- Sticky footer pattern: body is min-h-full flex column with main growing to
  fill, so the footer sits at the bottom of short pages.
- Hamburger menu under md: search and nav links collapse into a drawer
  toggled by a menu button; theme toggle stays visible at both sizes.
2026-06-07 16:44:30 +01:00
Andrew Nesbitt
da4fa2fc98
Add PROXY_UI_URL for separate UI/package URL advertisement (#161)
Adds UIBaseURL (env PROXY_UI_URL), advertised separately from BaseURL
for deployments where the UI is reached on a public domain while build
machines hit a Docker network alias for the package endpoints. Defaults
to BaseURL when unset.

Consumers:
- Logged alongside base_url at startup.
- <link rel="canonical"> and og:url / og:title / og:site_name in every
  UI page, omitted when UIBaseURL is empty.
- Banner on the install guide when UIBaseURL differs from BaseURL,
  clarifying that the URLs in the snippets are the package endpoint and
  that the UI itself lives elsewhere.

Docs call out that the proxy serves UI and package endpoints on the same
listener, so reverse proxies fronting the UI publicly must restrict the
public route to PathPrefix(/ui) to avoid exposing /npm, /pypi, etc.
nginx and Traefik examples both show the path-split pattern.
2026-06-07 16:12:43 +01:00
Andrew Nesbitt
08d975c397
Mount web UI under /ui (#148)
* Mount web UI under /ui (closes #123)

The UI now lives under /ui so reverse proxies can apply different
access rules to it (e.g. require auth) while leaving the package
endpoints (/npm, /pypi, /v2, ...) open to build machines.

- GET / redirects to /ui/
- /api/browse and /api/compare move to /ui/api/browse and
  /ui/api/compare since only the browser JS calls them
- /health, /stats, /metrics, /openapi.json and /api/* stay at root

* README: nginx example for splitting UI auth from package endpoints

The /ui prefix lets a reverse proxy apply different access rules to the
web UI than to the package endpoints. Adds the snippet that motivated
the prefix change so deployers don't have to derive it.
2026-06-07 15:47:29 +01:00
dependabot[bot]
fbe82f3605
Bump github.com/go-chi/chi/v5 from 5.2.5 to 5.3.0 (#157)
* Bump github.com/go-chi/chi/v5 from 5.2.5 to 5.3.0

Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.2.5 to 5.3.0.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.2.5...v5.3.0)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Drop deprecated middleware.RealIP

chi 5.3.0 deprecated middleware.RealIP because it trusts X-Forwarded-For,
True-Client-IP, and X-Real-IP unconditionally, which is spoofable when
the service is not strictly behind a trusted proxy (GHSA-3fxj-6jh8-hvhx,
GHSA-rjr7-jggh-pgcp, GHSA-9g5q-2w5x-hmxf). The only consumer of
r.RemoteAddr in this codebase is the request log; no auth, rate limiting,
or other security decisions depend on it, so removing the middleware is
safe. If we ever need real client IPs in logs behind an LB, add a
trusted-proxy-aware middleware then.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Andrew Nesbitt <andrewnez@gmail.com>
2026-06-07 13:51:26 +01:00
Andrew Nesbitt
0e7af4aed6
Make metadata size limit configurable (closes #149) (#150) 2026-06-02 07:59:00 +01:00
Mati Kepa
552c8ac4e5
Update Gradle cache with the configurable plugin support (#114)
* feat(gradle): add request metrics for build cache handler

* Implement fallback handling for Gradle plugin requests and update tests

* refactor(gradle): simplify response handling and remove unused metrics assertions

* Add tests for Gradle plugin metadata fallback and refactor metadata handling

---------

Co-authored-by: Mateusz (Mati) Kepa <m.kepa@sportradar.com>
2026-05-22 17:05:20 +01:00
Lars Wallenborn
76f41cf271
Add storage backend probe to /health (closes #73) (#119)
* config: add Health.StorageProbeInterval

* metrics: add proxy_health_probe_failures_total counter

* server: add storageProbe with happy-path test

* server: add storageProbe failure-mode tests

* server: add healthCache with TTL, single-flight, transition logging

* server: wire storage probe into /health

* server: update TestHealthEndpoint for JSON; wire healthCache into newTestServer

Also fix Windows file-locking issue in storageProbe: close the reader
explicitly before Delete so the file handle is released prior to os.Remove.

* server: clean up stale comment in storageProbe

* docs: document storage health probe and new metric

* docs: regenerate Swagger for /health JSON response

* server: simplify rc.Close error handling in storageProbe

* server: defer probe cleanup so size/open/read/verify failures don't leak objects

Previously, storageProbe only called Delete on the success path. Any
failure between Store and the final Delete (size mismatch, Open error,
mid-stream read failure, content mismatch) left the probe object orphaned
in the storage backend. With caching disabled and Kubernetes-rate probing,
the leak could accumulate noticeably on backends like S3.

Use a named return + defer to attempt Delete after every successful Store.
The earlier-step failure remains the primary error; Delete failure only
surfaces as step="delete" when nothing else went wrong. Add a table-driven
test that asserts cleanup runs for each non-delete failure path.

Reported by Copilot on #119.

* config: validate health.storage_probe_interval in Config.Validate

The new duration field was only validated at use time in newHealthCache.
The existing codebase already validates other duration fields
(MetadataTTL, DirectServeTTL, Gradle.MaxAge, Gradle.SweepInterval) in
Config.Validate() so misconfiguration fails fast at startup with a
config-key-specific error.

Match that pattern. The parse-at-use code in newHealthCache stays as
a safety net, mirroring the MetadataTTL precedent.

Reported by Copilot on #119.

* docs: lowercase "counter" in metrics table for consistency

Other rows in the table use lowercase type names (counter/gauge/histogram).
Match that style.

Reported by Copilot on #119.

* docs: include size-check step in /health probe description

The probe is write → size-check → read → verify → delete; the
architecture note was missing the size-check step.

Reported by Copilot on #119.

* server: address andrew's review on #119

- Drop unused callerCtx parameter from healthCache.Check (Check is now
  parameter-less; the comment-only "accepted for symmetry" justification
  wasn't carrying its weight).
- Emit "storage": {"status": "skipped"} on DB short-circuit instead of
  omitting the key, so monitors expecting a fixed key set keep working.
- Reject negative storage_probe_interval at config validation time
  (previously parsed and silently behaved like "0").
- Extract HealthConfig.Validate to keep Config.Validate under the
  gocognit threshold and match the existing GradleBuildCacheConfig pattern.
- README Health Check section: note that /health is intended as a
  readiness probe rather than a liveness probe (Check holds a mutex
  for up to the 10s probe timeout).
- cmd/proxy/main.go godoc: column-align the new env var with the
  surrounding Gradle entries.

Reported by andrew on #119.
2026-05-22 12:14:01 +01:00
Andrew Nesbitt
f2a5b704f0
Add Julia Pkg server support (#117)
- Implement /julia/* handler for the Pkg server protocol
  (registries, registry, package, artifact, meta)
- Resolve package UUIDs to names by parsing Registry.toml from
  the General registry tarball, with a hash-guarded background
  refresh on registry updates
- Wire into router, ecosystem list, install page, badge styles
- Update README and architecture docs
2026-05-13 06:46:35 +01:00
Andrew Nesbitt
5315883c3b
Bump registries to v0.6.0 and replace internal/cooldown (#120)
- Bump github.com/git-pkgs/registries to v0.6.0: the fetcher now
  honours HTTP_PROXY, gates dialled IPs against the safehttp block
  list, and Version.Integrity is populated for pub, julia and nuget
- Replace internal/cooldown with github.com/git-pkgs/cooldown v0.1.1
  (identical surface, lifted from this repo)
- Update docs/architecture.md to point at the external package
2026-05-13 06:45:33 +01:00
Andrew Nesbitt
992f5c68a7
Add .golangci.yml and clear gocognit/goconst findings (#113)
Bake the extended linter set into a project config so plain
golangci-lint run matches what we check locally, with goconst tuned
to ignore tests and bare lowercase words to drop ~200 ecosystem-name
and test-literal false positives.

Clear the remaining real findings: extract GradleBuildCacheConfig.Validate
from Config.Validate, pull the eviction sort comparator into
sortOldestFirst (zero time.Time already sorts first via Before so the
switch was redundant), add headerAcceptEncoding and SQL column-type
constants, and drop a dead empty-key recheck in the gradle handler.
2026-05-05 10:25:17 +01:00
Mati Kepa
31a9ca75b2
add Gradle Build Cache support with handler and tests (#87)
* add Gradle Build Cache support with handler and tests

* linting issue

* MR Suggestions: Add Gradle HTTP Build Cache configuration to README

* implement  minor stuff: Refactor Gradle handler to remove unnecessary URL parameter and update related tests

Co-authored-by: Copilot <copilot@github.com>

* Add Gradle build cache configuration and eviction support

- Introduced configuration options for Gradle build cache in config files and documentation.
- Implemented read-only mode and upload size limits for the Gradle build cache.
- Added cache eviction logic based on age and size, with corresponding tests.
- Enhanced storage interfaces to support listing objects by prefix.

* implement minor stuff: Refactor Gradle handler to remove unnecessary URL parameter and update related tests

* last finding fix

* fix tests and implement PR suggestions

Co-authored-by: Copilot <copilot@github.com>

* unify path

---------

Co-authored-by: Mateusz (Mati) Kepa <m.kepa@sportradar.com>
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 11:15:16 +01:00
Andrew Nesbitt
8d2740624f
Structured JSON error responses for API endpoints (#110)
* Structured JSON error responses for API endpoints

API handlers returned errors via http.Error (text/plain) with ad-hoc
strings, while the mirror API used a different {"error": "..."} shape
and leaked internal err.Error() text to clients.

Add ErrorResponse{Code, Message} with stable codes (BAD_REQUEST,
NOT_FOUND, UPSTREAM_ERROR, INTERNAL_ERROR) and writeError/badRequest/
notFound/internalError helpers. Convert all JSON API handlers in
api.go, browse.go, mirror_api.go and the /stats endpoint. Enrichment
failures now report 502 UPSTREAM_ERROR rather than 500.

Protocol handlers in internal/handler/ are deliberately unchanged
since npm/pip/cargo clients expect their own response formats, not
JSON. HTML page handlers in server.go also keep text/plain.

Swagger @Failure annotations updated and docs regenerated.

Fixes #76

* Convert validatePackagePath errors to JSON in API handlers
2026-05-03 09:42:03 +01:00
Andrew Nesbitt
e912227e3b
Use archives.OpenBytes in browse handler to cut buffer copies (#107)
* Use archives.OpenBytes in openArchive to avoid redundant buffer copies

* Bump git-pkgs/archives to v0.3.0
2026-05-03 09:29:42 +01:00
Andrew Nesbitt
522c6f233e
Validate package paths before database lookups (#109)
The wildcard package routes (/packages/{ecosystem}/*, /api/package/*,
/api/vulns/*, /api/browse/*, /api/compare/*) only checked for an empty
path before passing user input to GetPackageByEcosystemName and the
enrichment service.

Add validatePackagePath as a coarse first-line filter: reject null
bytes, other control characters, and paths over 512 bytes. Wired into
all five entry handlers immediately after the chi wildcard is read.

This is the generic layer; ecosystem-specific name format rules (npm
scoped name shape, Maven coordinate structure, etc.) can be added on
top per #75.

Fixes #75
2026-05-03 09:14:18 +01:00
Andrew Nesbitt
d6066af230
Add MaxBytesReader to mirror API HandleCreate (#105)
Matches the pattern already used in api.go to prevent unbounded request
body reads.
2026-05-02 18:00:25 +01:00
Andrew Nesbitt
cfcf480f69
Limit io.ReadAll in openArchive to 512 MB (#104)
The browse and compare handlers buffer the full artifact into memory for
prefix detection. Without a cap, a single request for a large cached
artifact could exhaust server memory.
2026-05-02 18:00:22 +01:00
Andrew Nesbitt
f1ea8b50a1
Serve .html, .svg and .xhtml as text/plain in browse file handler (#103)
These file types were served with executable content types (text/html,
image/svg+xml) allowing stored XSS via package archive contents.
Also adds Content-Security-Policy: sandbox and X-Content-Type-Options:
nosniff headers to all browse file responses.
2026-05-02 18:00:04 +01:00
Andrew Nesbitt
e2495ef0aa
Merge pull request #102 from git-pkgs/enforce-max-size-eviction
Enforce max_size config with LRU cache eviction
2026-04-30 23:26:16 +01:00
Andrew Nesbitt
461a95c518
Enforce max_size config with LRU cache eviction
Closes #99. The max_size storage config was parsed and validated but
never enforced. This adds a background eviction loop that periodically
checks total cache size and evicts least recently used artifacts when
the limit is exceeded.
2026-04-30 18:09:01 +01:00
Andrew Nesbitt
1ad182782d
Add storage.direct_serve_base_url to override presigned URL host
When the proxy reaches storage at an internal address (127.0.0.1, a
Docker service name) the presigned URLs it generates point there too,
which is useless to external clients. This adds an optional base URL
that replaces the scheme and host of signed URLs before they're returned,
keeping the signed path and query intact.
2026-04-27 12:14:37 +01:00
Andrew Nesbitt
c73b0a35a1
Add direct-serve via presigned storage URLs
When storage.direct_serve is enabled and the backend supports it (S3,
Azure), cached artifact downloads return a 302 redirect to a presigned
URL instead of streaming bytes through the proxy. Falls back to
streaming when the backend can't sign (fileblob, local filesystem) or
signing fails.

Adds the azureblob driver so azblob:// storage URLs work.

Cache-hit accounting already happened before io.Copy so redirects are
counted correctly; the metrics calls are pulled into a helper so both
paths share them.

Closes #96
2026-04-27 12:04:38 +01:00
c655399a07 Apply 'go fmt' as suggested in CONTRIBUTING.md. 2026-04-18 07:43:22 -04:00
Andrew Nesbitt
7346008aa5
Add metadata TTL and stale-while-revalidate support
Cached metadata is now served directly within a configurable TTL window
(default 5m) without contacting upstream, reducing latency and upstream
load. When upstream is unreachable and the cache is past its TTL, stale
content is served with a Warning: 110 header per RFC 7234.

New config: `metadata_ttl` (YAML) / `PROXY_METADATA_TTL` (env).
Set to "0" to always revalidate with upstream.
2026-04-13 09:01:05 +01:00