Repository navigation
Conversation
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
simonchrz
left a comment
There was a problem hiding this comment.
Thanks for picking up #499. The stale Warning header for rewritten npm metadata looks good.
The no-fallback change affects every ecosystem, though, not just npm. fetchMetadataFromUpstream also serves Composer, PyPI, Alpine, Debian and generic metadata. On main, cached metadata is served on an upstream 404/401/403. With this PR those requests fail. That contradicts the docs (generic upstreams are served stale when the upstream "refuses", and GitHub rate limits are 403s) and the 404 fallback described in #486.
Could you split it? I'd approve the Warning header part as is. The 404/401/403 behaviour either goes to an issue, or becomes opt-in for the npm handler only.
Nit: writeNPMetadataError is missing an "M".
andrew
left a comment
There was a problem hiding this comment.
I independently confirmed the shared-fallback regression described in #503 (review). Through the generic HTTP handler with an expired cached document, upstream 404/401/403 produce 404/502/502 on this head. The same test on main returns the cached body with status 200 and Warning 110 for all three. Please keep the stale-warning fix and scope the new fallback policy to npm, or split that policy into a separate change, preserving other ecosystems' documented fallback behavior. Add a regression test through a non-npm handler.
npm metadata served through the rewrite cache lost the stale Warning header, so a cached packument looked fresh after an upstream failure. serveRewrittenMetadata now sets Warning 110 when the body came from the stale fallback. The npm handler also relays a definitive registry answer instead of serving the cached package: 404 for an unpublished package and 401/403 for rejected credentials, carried in an upstreamStatusError. The no-fallback rule is scoped to the npm ecosystem; other ecosystems keep their documented stale fallback (covered by a regression test through the generic metadata handler), and npm still falls back for transport errors and retryable statuses (429, 5xx). Routed packages keep answering 502, since a route's credentials are the proxy's concern and a relayed 401 would send npm off authenticating to the proxy. Fixes git-pkgs#499 Signed-off-by: Mohamed Labiadh <47183211+medlabib@users.noreply.github.com>
db9b5d0 to
a524fa9
Compare
simonchrz
left a comment
There was a problem hiding this comment.
Thanks, scoping the fallback to npm addresses my earlier review. Two things left, both on the version path handleFilteredNPMVersionMetadata takes when cooldown or a version denylist is on:
- The stale flag is dropped there. With a denylist entry for another version,
/left-pad/1.0.0returns the cached metadata as 200 with noWarningafter an upstream 503, while/left-padgets the warning. It reads the packument throughFetchOrCacheMetadata, which discardsstale. Switching it tofetchMetadataDocumentand settingrewritten.stalewould fix it. - A 401 from the default registry is relayed on
/left-padbut becomes 502 on/left-pad/1.0.0. I'd also prefer 502 on both. The 401 means the upstream rejected the proxy's credentials, and relaying it makes npm ask the user to log in to the proxy, which can't fix it.
A regression test through the npm route with a denylist entry would cover both.
Serving cached metadata when the upstream refuses is documented for generic upstreams, and GitHub rate limits are 403s, so the fallback stays as it is. git-pkgs#503 handles npm's definite answers. What is left here is the stale warning for Composer, Chef and hosted npm metadata, which went through the rewrite cache without it.
andrew
left a comment
There was a problem hiding this comment.
The npm scoping and the non-npm regression test cover my earlier review, thanks. @simonchrz's two points from this morning still stand. Two more things on top of those:
401/403 from the default registry: answer 502 on every npm path. Keep relaying 404. A 401 or 403 there means the registry rejected the proxy's own credentials, and the client can't fix that. If the proxy relays a 401, npm tells the user to log in to the proxy. The no-fallback rule sits in the shared fetchMetadataFromUpstream, so the filtered version path and hosted serveMetadataWithLocal (npm_hosted.go) already return 502 on this branch. Only handlePackageMetadata relays. Please drop the upstreamStatusError branch from writeNPMMetadataError so all three paths return 502, put the upstream status in the error body (for example upstream rejected the proxy's credentials (401)), and change TestNPMMetadataPassesDefinitiveUpstreamAnswersThrough to expect 502 for 401/403.
Use the stale helper that's now on main. #541 merged today and added staleWarning and markStale(w, stale) in handler.go, which callers run just before serveRewrittenMetadata. Please use those instead of the stale field on rewrittenMetadata and the header literal in rewrite_cache.go: call markStale(w, upstream.stale) in handlePackageMetadata, and compare against staleWarning in the tests. For the filtered version path, switch to fetchMetadataDocument and call markStale there too, with a test through the npm route that has a denylist entry.
Fixes #499.
With
cache_metadataon, npm metadata served through the rewrite path (cachedRewrite→serveRewrittenMetadata) came back as a plain200even when the body was the cached copy after an upstream failure: noWarning: 110 - "Response is Stale", unlikeProxyCachedon the same fallback. Separately, a 404 for an unpublished package and a 401/403 for credentials the registry rejected were swallowed by the cache fallback, so neither answer reached the client.What changed:
rewrittenMetadatacarriesstale, set from the fetch result inhandlePackageMetadata, andserveRewrittenMetadatasetsWarning: 110 - "Response is Stale"when it is set. A 304 keeps the headers it has today.upstreamStatusError, so the answer is distinguishable from a transport failure.errorbody. Routed packages keep the existing 502: a route's credentials are the proxy's concern, and relaying its 401 would send npm off to authenticate against the proxy.TestNPMRoutedMetadataDoesNotFallBackstill passes.fetchMetadataFromUpstreamkeeps its documented stale fallback for Composer, PyPI, Alpine, Debian and the other callers, including on 404/401/403; only npm returns the answer instead of the cached copy. npm still falls back on transport failures and retryable statuses (429, 5xx).Tests, in the new
internal/handler/npm_metadata_stale_test.go:Warningheader with the rewrite cache on and off, and returns the cached body (this fails onmain: no header, and 404/401/403 are served as a stale 200);Warning;errorbody and not the cached packument;Warning, guarding the rate-limit/hold fallback;composerdocument is still served with theWarningwhen upstream answers 404/401/403. This test fails if the pass-through is not scoped to npm.Not included: a maximum stale age for the fallback, which the issue mentions as optional.
AI disclosure: prepared with AI assistance (Hermes agent); the change was verified with the test suite and lint run above.