Repository navigation
fix pnpm audit - #382
fix pnpm audit#382
Conversation
andrew
left a comment
There was a problem hiding this comment.
Routing the audit POSTs before the GET-only gate is the right fix, and the guards and tests look good. Three things before merge.
handleSecurity hand-rolls the upstream request and response relay in internal/handler/npm.go, which Proxy.ProxyUpstream (internal/handler/handler.go:772) already does apart from carrying a request body. The practical problem with the copy is the header loop: it forwards upstream hop-by-hop headers, so an upstream Connection: close is relayed and Go's server honours it on the client connection. That is the issue #326 class of bug, and #380 is centralizing a relay helper for exactly this. Either give ProxyUpstream an optional request body and call it, or, if #380 merges first, use the helper it adds in internal/handler/relay.go.
The comments are doing too much. The const block and the handleSecurity doc comment run to about 24 lines between them, including change history ("Without this, the handler answered the POST with a 405 and a plain-text body"). Keep the dispatch-order note and the reason Content-Encoding has to travel with the body, and drop the rest.
npmSecurityForwardHeaders is a package global with a //nolint:gochecknoglobals on it. As a local slice inside handleSecurity it needs no suppression.
andrew
left a comment
There was a problem hiding this comment.
One more finding, which should have been in the earlier review.
npm audit signatures still fails through the proxy with this change in place. It reads the registry's signing keys from GET /-/npm/v1/keys, which does not match the new security prefix and falls through to the package dispatch: extractPackageName returns -/npm/v1/keys, url.PathEscape turns it into -%2Fnpm%2Fv1%2Fkeys, registry.npmjs.org answers that 405, and the handler reports 502 {"error":"failed to fetch from upstream"}. I ran this against registry.npmjs.org to confirm the mechanism.
It belongs in this PR rather than a separate one for two reasons. The README section added here tells users npm audit works through the proxy, and npm audit signatures is part of that command. And /-/npm/v1/ is the namespace this PR starts routing, so the keys path is the other half of the same gap.
Routing /-/npm/v1/keys to ProxyUpstream ahead of the package and tarball dispatch covers it in about ten lines. The keys document is small and carries no tarball URLs to rewrite, so it needs no caching or rewriting, and ProxyUpstream already applies upstream auth and relays the response. The endpoint predates this PR.
|
Thanks — all four addressed. One thing worth flagging up front: #380 merge between your two reviews (05:35, review one was 05:16). I took the branch you offered for that case and used the helper it added, so this now needs a rebase onto current Relay duplication
You were right that it was a live bug, not just duplication. With the old loop an upstream This also deleted something from my earlier round: I had been skipping The keys endpoint is a GET, so I registered the npm handler in
|
Add npm audit passthrough for pnpm, npm and Yarn
pnpm auditagainst the proxy fails withERR_PNPM_AUDIT_BAD_RESPONSE.The npm handler had a GET-only gate at the top of its router, so the audit
POST to
{registry}/-/npm/v1/security/auditswas answered with a 405 and aplain-text body. The client tried to parse that as an audit report and reported
it as malformed.
npm auditandyarn npm auditfailed the same way.These endpoints are relayed to the configured upstream registry untouched.
There is nothing for the proxy to add: the request body is the dependency tree
being audited, so no two requests share a cache key, and the response carries no
tarball URLs to rewrite.
Changes
/-/npm/v1/security/*before the GET-only gate — audit paths share the/-/prefix with tarball paths, so dispatch order mattersContent-Type,Content-Encoding,AcceptandAccept-Encoding; npm gzips its auditpayload, so the body and the header describing it have to travel together
applyUpstreamAuth, so audits work against private registriesstays a 401 instead of becoming a proxy error
One prefix covers every client:
audits(pnpm, npm full),audits/quick(Yarn, npm fallback) and
advisories/bulk(npm 7+).Guards
containsPathTraversalon the request path(a client aborting mid-upload) returns 400 — reporting both as 413 would send
operators hunting a limit that was never reached
EscapedPath(), so a percent-encoded?or#in the request path cannot inject a query or truncate the URL upstreamContent-Lengthis not copied. If the upstream connection breaksmid-report, letting Go pick the framing makes that a transport error the
client can detect, rather than a 200 with a short body — which is the same
malformed-audit symptom this change exists to remove
The body is buffered rather than streamed so an oversized payload can be
rejected before the upstream request starts; that bounds memory at the cap per
in-flight audit request.
Caveats
Advisories come from upstream's database, not from the proxy's own vulnerability
data, and versions withheld by cooldown are not excluded from the report — so an
audit can flag a version the proxy will not serve. Both are documented in the
README and in the handler comment.
Testing
bodies preserved byte-for-byte, all three endpoints, upstream auth, upstream
errors relayed, unreachable upstream, each request guard, and a regression
test that tarball GETs still reach the download path
go build ./...,go vet ./...,go test ./...andgolangci-lint runallclean
🤖 Generated with Claude Code