Two unauthenticated vulnerabilities in verdaccio, found by reading the source, fixed and released within days
Project: verdaccio/verdaccio — a self-hosted npm registry (a proxying cache plus a private publish target), TypeScript, ~14k GitHub stars. What happened: an end-to-end source review by Feldspar (an autonomous AI agent) surfaced two unauthenticated security bugs. Both were reported privately through the project's own security channel, and both were fixed and shipped in a public release — one the same evening, the other within three days.
This page is a factual account of that work. It is the kind of result a paid audit is meant to produce: real bugs in real code, with a reproduction and a concrete fix, disclosed responsibly.
Finding S1 — unauthenticated ACL bypass on the web metadata endpoints
Verdaccio lets an operator mark scoped packages private with an access rule such as '@private/*': {access: admin} over a public default. The web UI serves package metadata (versions, dist-tags, maintainers, dependencies, tarball URLs, README) through a readme endpoint and a sidebar endpoint, both guarded by an allow('access') middleware.
The middleware and the handlers disagreed about what the package name is. The middleware built the name by concatenating the raw route segment, while the handlers stripped the first character and re-added an @. Nothing forced the scope segment to actually start with @. So the middleware would authorize one name and the handler would fetch a different one.
Concretely, on a stock install with the rule above and a private @private/secret package, an unauthenticated request to the real name returned 401. But requests to mangled variants — a leading x, X, or _ in place of the @ — were access-checked against the public default (allowed), then served the private package's manifest and README. Any leading character worked. Tarball download stayed separately gated, so this was metadata and README disclosure rather than source disclosure, but it was a complete bypass of the access rule on both web endpoints, with no authentication.
- Reproduced live against
verdaccio@6.10.1: the real name returned401; three mangled variants each returned200with the private manifest. - Disclosed privately on 2026-09-02 through the project's SECURITY.md channel, with a suggested fix (resolve the name once, validate the scope segment, return 404 on a malformed scope).
- Fixed and released the same evening in verdaccio 6.10.2, via a pull request titled "validate the scope segment on the web package endpoints." After upgrading the same instance, every mangled request returned
404and the real name still returned401.
Finding S2 — unauthenticated SSRF and persistent cache poisoning via a package's tarball URL
When verdaccio serves a tarball it cannot match to a configured uplink, an older code path synthesized a one-off proxy to fetch the URL recorded in the package's own dist.tarball field. Because a published package's manifest controls that field, an attacker who could publish (or poison) a manifest could point the tarball URL at an internal address and make the server fetch it, then cache the result.
- Reproduced live on stock
verdaccio@6.10.2: a crafted tarball URL pointing at an internal listener returned200with the internal listener's response body, and the listener recorded the fetch — server-side request forgery and content leak confirmed. - Disclosed privately on 2026-09-03 through the SECURITY.md channel, held from the first report until it had been reproduced on the stable branch, with a 90-day embargo and a fail-closed fix suggestion.
- Fixed and released in verdaccio 6.10.3 on 2026-09-05 (ported to the main branch in a follow-up). The fix fails closed: an off-uplink tarball URL on a locally published package now returns "no such file" instead of fabricating a proxy, and uplink credentials are only attached when the uplink actually serves the URL. Re-verified before and after:
6.10.2leaked the internal body and hit the listener;6.10.3returned404and made no request.
What this shows
- These were logic bugs a scanner does not find. Both came from reading how two pieces of code disagreed about the same value — a name-resolution mismatch, and a fetch path that trusted attacker-controlled data. No dependency advisory or secret-pattern match would have flagged either.
- Each finding shipped with a live reproduction and a concrete fix, disclosed through the project's own channel with an embargo. That is the standard every finding is held to.
- Honest framing: Feldspar did not author either fix and cannot prove the reports caused them. The timeline and the exact match between the vulnerability and the remedy are stated as observed facts.
The three other master-only items from the same review remain withheld until they are fixed or the embargo ends. The full public sample report on verdaccio, covering the correctness findings in detail, is here.