[Bug]: Web UI "Error loading manifests" — all `/-/verdaccio/data/*` endpoints return 404
What happened?
Affected versions: v6.2.0 – v6.7.4 (and v7 betas)
Regression introduced by: #5399 (9e404c1dc)
What happens
Opening the web UI triggers GET /-/verdaccio/data/packages (and sibling endpoints). All of them return:
404 {"error":"File not found"}The UI shows "Error loading manifests" and the package list is never rendered. No server-side error is logged — the request silently falls through to the catch-all handler in src/api/index.ts.
All web API endpoints are affected: /packages, /sidebar, /readme, /search, /login, /reset_password.
Root cause
The bug requires two conditions that were introduced in separate PRs:
Step 1 — PR #5090 (f2cc71cd2, 2025-02-23, shipped in v6.1.0)
src/api/web/api/utils.ts was created with wrapPath / wrapSecPath returning absolute paths that include the /-/verdaccio/ prefix:
export function wrapPath(urlPath: string) {
return `/-/verdaccio/data${urlPath}`; // e.g. "/-/verdaccio/data/packages"
}
export function wrapSecPath(urlPath: string) {
return `/-/verdaccio/sec${urlPath}`;
}At this point the child router had no mount prefix, so routes still matched. ✅
Step 2 — PR #5399 (9e404c1dc, 2025-09-29, shipped in v6.2.0)
A refactor introduced webMiddleware() and mounted the web API child router at WebUrlsNamespace.endpoints ("/-/verdaccio/"):
// src/api/web/index.ts router.use(WebUrlsNamespace.endpoints, webAPIMiddleware(tokenMiddleware, webEndpointsApi)); // "/-/verdaccio/" ← Express strips this prefix before child-router matching
Express strips the mount prefix from req.url before passing the request to the child router. So wrapPath's absolute paths no longer match any registered route. ❌
The failure chain:
- Request: GET /-/verdaccio/data/packages
- webMiddleware mounts child router at "/-/verdaccio/" — Express strips the prefix
- Child router receives req.url = "/data/packages"
- Registered route is "/-/verdaccio/data/packages" → no match
- Falls through to app.get('/*', next(ErrorCode.getNotFound(...))) → 404
Minimal reproducer
const express = require('express'); // 4.22.2
const app = express();
const outer = express.Router();
const inner = express.Router();
// current behaviour of wrapPath()
inner.get('/-/verdaccio/data/packages', (req, res) => res.json({ ok: true }));
outer.use('/-/verdaccio/', inner); // WebUrlsNamespace.endpoints
app.use(outer);
app.use((req, res) => res.status(404).json({ error: 'File not found' }));
// GET /-/verdaccio/data/packages → 404
// inner only sees: /data/packages (prefix was stripped by Express)Fix
wrapPath and wrapSecPath must return paths relative to the mount prefix, omitting the /-/verdaccio/ segment that Express has already consumed:
export function wrapPath(urlPath: string) {
- return `/-/verdaccio/data${urlPath}`;
+ return `/data${urlPath}`;
}
export function wrapSecPath(urlPath: string) {
- return `/-/verdaccio/sec${urlPath}`;
+ return `/sec${urlPath}`;
}Note: WebUrlsNamespace.data = "/data/" and WebUrlsNamespace.sec = "/sec/" already exist in @verdaccio/middleware and express exactly these relative paths — wrapPath/wrapSecPath could use them directly.
Affected versions
v6.0.x and earlier │ ✅ Unaffected — wrapPath does not exist v6.1.x │ ✅ Unaffected — absolute paths present but no mount prefix yet v6.2.0 – v6.7.4 │ ❌ Broken — both conditions present v7.0.0-beta.* │ ❌ Broken — same code
Version
6.x (Stable)
Version details
6.7.4
Output server log info
Node.js Version
24.x.x (LTS)
Package manager
npm
Operating system
linux
Using reverse proxy
- I am using a reverse proxy
Relevant log output
Have I checked other issues?
- I have searched existing issues
Source: verdaccio/verdaccio