npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

verdaccio-maven-s3-proxy

v1.3.0

Published

Maven pull-through cache middleware for Verdaccio: parallel fan-out to configured repositories, dual-stream to client and S3.

Readme

Verdaccio Maven S3 Proxy

Verdaccio 6+ middleware: Maven pull-through cache. Gradle hits /maven2/**; plugin GETs the CDN (S3 behind CloudFront), and on miss fans out to configured upstreams, streams the client, and PUTs S3.

Origin auth is required on /maven2/** and GET /-/maven/urls. Gradle sends HTTP Basic:

username = <shared client id, e.g. maven-client>
password = <OAuth JWT issued by Verdaccio>

The plugin rewrites Basic to Authorization: Bearer <JWT> so a JWT verification middleware (if you install one) sees the same header npm sends. Before serving, this plugin also validates the HS256 signature, exp, and name. Failures return 401 before any CDN or upstream request.

When the cache cannot serve (no upstream reachable, upstream error, misconfiguration, missing JWT secret), /maven2/** answers 404 by default (errors_as_404: true). Gradle treats a repository 5xx as fatal and does not try the next maven { } block; a 404 makes it fall through. Set errors_as_404: false to return the real status (502, 503, 413, …). The cause is always in the logs and the x-error header.

GET /-/maven/health stays unauthenticated for the kubelet and still returns 503 when the plugin cannot serve.

Installation

npm install verdaccio-maven-s3-proxy

Point Verdaccio plugins at the directory that contains this package (or install it next to other plugins). CommonJS build — Verdaccio require()s the plugin and calls the factory without new. Node.js 20 or newer is required.

Local development

npm install
npm run build
plugins: /path/to/plugins

middlewares:
  maven-s3-proxy:
    enabled: true
    # ... see Configuration

Configuration

Verdaccio forwards middlewares.maven-s3-proxy untouched. Schema is this plugin's.

middlewares:
  maven-s3-proxy:
    enabled: true
    bucket: your-artifacts-bucket
    region: eu-west-1
    cdn_url: https://cdn.example.internal
    # service credential the plugin sends on CDN GETs (not a developer JWT)
    # cdn_basic_user: cdn-reader
    # cdn_basic_password: "…"
    never_cache:
      - '**/*.module'
    urls:
      - https://repo.maven.apache.org/maven2
      - https://dl.google.com/dl/android/maven2
    # Exact off-repository hosts that a 3xx may target (object-store hand-offs).
    redirect_hosts:
      - storage.googleapis.com
    # optional; omit to accept any Basic username (password must still be a JWT)
    # basic_users:
    #   - maven-client
    #   - ci-bot
    # default true: cache failures are 404 so Gradle can try the next repository
    # errors_as_404: true

List maven-s3-proxy before any JWT verification middleware under middlewares. The Basic→Bearer adapter is installed immediately; Maven routes are registered after all middleware plugins, producing this chain:

Basic adapter → JWT verifier → Maven route

You still need a Verdaccio JWT secret (same one OAuth UI signs with):

secret: your-verdaccio-hmac-secret

Production: omit s3_endpoint, access_key_id, secret_access_key. SDK uses real AWS endpoints; credentials come from Pod Identity / IRSA. Omit cdn_url to skip the CDN and always fan out to upstreams.

CDN viewer auth: Gradle never hits the distribution. The plugin GETs cdn_url with optional cdn_basic_user / cdn_basic_password. Put that same Basic <base64(user:pass)> string in a CloudFront Function (viewer-request) allowlist. Do not forward the caller's JWT to the edge (Functions cannot verify HS256; Authorization in the cache key would split HIT-CDN per user). Keep Authorization out of the CloudFront cache policy. A 401 from the CDN is logged and the plugin falls through to upstreams.

Options

| Name | Type | Description | | --- | --- | --- | | enabled | boolean | Default true. false makes /maven2/** unusable (404 if errors_as_404, else 503). | | bucket | string | Required. S3 bucket for cached artifacts. | | urls | string[] | Upstream Maven repos. Probed in parallel on a miss; the first configured repo with a 200 wins and lower-priority probes are aborted. Put canonical repos first. | | redirect_hosts | string[] | Exact off-repository hostnames that a 3xx may target. Repository hosts are trusted automatically. Other public hosts are refused unless listed. | | never_cache | string[] | Verdaccio-style globs: stream to client, never store. SNAPSHOT paths and maven-metadata.xml* are always skipped even without a glob. | | region | string | AWS region. Default eu-west-1. | | cdn_url | string | CDN base URL (no trailing slash). Plugin GETs this URL; Gradle never sees it. Omit = skip CDN hop, serve from upstreams only. | | cdn_basic_user / cdn_basic_password | string | Optional viewer Basic the plugin sends on CDN GETs. Must match the edge allowlist (CloudFront Function / nginx). Service credential, not a developer JWT. Omit = unauthenticated hop (anyone who can GET the distribution can read cached artifacts). | | basic_users | string[] | Optional allowlist of Gradle Basic usernames. Omit = any non-empty username. The password is either an OAuth JWT (forwarded as-is) or an opaque CI key, which the adapter normalizes to Bearer base64(key) so verdaccio-static-access-token (listed before the JWT verifier) can swap it for a signed JWT. A key matching no static token is rejected 401 by the Maven handler. | | errors_as_404 | boolean | Default true. Cache failures become 404 so Gradle falls through. false returns the real code: 503 unreachable/misconfigured, 502 upstream/CDN/internal, 413 over the size cap. | | allow_anonymous | boolean | Default false. Dev/test only. When true, a request with no Authorization header is served without authentication; a request that carries a header still takes the normal JWT path (a bad token is still rejected). Logs a startup warning when enabled. Never enable in production. | | s3_endpoint | string | Local harness only (e.g. MinIO). | | access_key_id / secret_access_key | string | Local harness only. |

Routes:

  • GET /maven2/** — artifacts (Basic or Bearer; 401 without a valid JWT; cache failures default to 404)
  • GET /-/maven/health — readiness (kubelet; no auth; 503 if the plugin cannot serve)
  • GET /-/maven/urls — configured urls (same auth as artifacts)

npm routes stay untouched.

Production operations

  • Grant the pod only the S3 actions this plugin uses, scoped to maven2/* and .incoming/* in its cache bucket.
  • Enable bucket versioning for recovery and forensic comparison.
  • Expire incomplete multipart uploads and .incoming/ staging keys with an S3 lifecycle rule.
  • Set CloudFront error caching minimum TTL to zero for missing-origin responses (normally 403 and 404). Otherwise a CDN miss observed just before the S3 PUT can stay cached after the object exists.
  • Purging a compromised object requires both deleting its S3 key and invalidating the matching CloudFront path.
  • Alert on circuit opens, CDN 401s, failed uploads, and fan-out latency.
  • Use Gradle dependency verification or committed dependency checksums. Transport integrity cannot establish publisher authenticity when a configured upstream is compromised.

License

MIT.