ITADN
npm/cli/Issues

[BUG] npm audit fails: advisories/bulk returns a gzip body with no Content-Encoding header

#9804Openjakobarsement 创建于 2026-07-26
BugNeeds Triage
J
jakobarsementcommented
### Is there an existing issue for this? - [x] I have searched the existing issues ### This issue exists in the latest npm version - [x] I am using the latest npm ### This is not just a request to bump a dependency for a CVE - [x] This is not solely a request to bump a dependency for a CVE ### Current Behavior `npm audit` fails for any real project: ``` npm warn audit invalid json response body at https://registry.npmjs.org/-/npm/v1/security/advisories/bulk reason: Unexpected token '\x1f', "\x1f\x8b\x08\x00..." is not valid JSON npm error audit endpoint returned an error ``` `POST /-/npm/v1/security/advisories/bulk` returns **HTTP 200 with a gzip-compressed body but no `Content-Encoding` header**, so the client has no way to know it should decompress, and `JSON.parse` receives raw gzip bytes. `Content-Type` and `Content-Length` are absent too — the entity headers appear to be stripped wholesale. Full response headers, verbatim from `curl -D -`: ``` HTTP/2 200 date: Sun, 26 Jul 2026 06:13:01 GMT cf-ray: a211418d5b14e88f-DFW cf-cache-status: DYNAMIC server: cloudflare set-cookie: __cf_bm=... ``` The body begins with the gzip magic number: ``` 00000000: 1f8b 0800 0000 0000 0003 b5d3 5d6b db30 ............]k.0 ``` **The payload itself is fine** — piping it through `gunzip` yields valid, schema-conformant advisory JSON: ```json {"lodash":[{"id":1106913,"url":"https://github.com/advisories/GHSA-35jh-r3h4-6jhm","title":"Command Injection in lodash","severity":"high"}]} ``` **It is size-gated at roughly 1024 bytes**, which is why trivial requests look healthy while any real lockfile fails every time: | request | uncompressed size | encoding | |---|---|---| | `{"minimist":["1.2.5"]}` | 292 B | plain JSON ✅ | | `{"lodash":["4.17.21"]}` | 986 B | plain JSON ✅ | | `{"lodash":["4.17.20"]}` | 1570 B | **unlabelled gzip** ❌ | This is consistent with a default `gzip_min_length` / 1024-byte compression threshold. It may also explain why status.npmjs.org still reports the Security Audit component as fully operational — a health-check payload under the threshold would pass cleanly. **`Accept-Encoding` is ignored entirely**, so there is no client-side escape. All six variants return unlabelled gzip: | `Accept-Encoding` | first 2 bytes of body | |---|---| | `gzip` | `1f8b` | | `identity` | `1f8b` | | `br` | `1f8b` | | `deflate` | `1f8b` | | `*` | `1f8b` | | *(header omitted)* | `1f8b` | Requesting `identity` — explicitly asking for an uncompressed body — still returns gzip. Ordinary packument routes (e.g. `GET /minimist`) are unaffected and return correct `content-type` / `content-encoding` / `vary`, so this appears scoped to the bulk-advisory route. ### Impact Because the Quick Audit fallback was removed from the client (npm/cli@080a0f2) and the legacy endpoint has since been retired, a malformed bulk response now hard-fails rather than degrading. **Downgrading npm does not help** — older clients receive 400/410 from the retired endpoint. This blocks `npm audit` in CI for every project whose advisory response exceeds the threshold, which in practice is all of them. Also reproduces via pnpm, bun, and `audit-ci`, since they consume the same endpoint. Prior art for the same class of bug on the same infrastructure: npm/npm#12196 and npm/npm#12205 (2016) — *"registry returns gzip'ed response even if the client has no accept-encoding header."* ### Expected Behavior The response should either be sent uncompressed, or include `Content-Encoding: gzip` (and `Content-Type: application/json`) so clients can decode it. `Accept-Encoding: identity` should be honoured. ### Steps To Reproduce Minimal, no project required: ```bash # 1. Small response — works fine, returns plain JSON curl -s -X POST -H 'Content-Type: application/json' \ -d '{"minimist":["1.2.5"]}' \ https://registry.npmjs.org/-/npm/v1/security/advisories/bulk | head -c 80 # 2. Larger response — returns gzip bytes with NO Content-Encoding header curl -s -D - -X POST -H 'Content-Type: application/json' \ -d '{"lodash":["4.17.20"]}' \ https://registry.npmjs.org/-/npm/v1/security/advisories/bulk | head -c 400 # 3. Proof the body is valid gzip'd JSON, just mislabelled: curl -s -X POST -H 'Content-Type: application/json' \ -d '{"lodash":["4.17.20"]}' \ https://registry.npmjs.org/-/npm/v1/security/advisories/bulk | gunzip | head -c 200 # 4. Confirm Accept-Encoding is ignored — identity still returns gzip: curl -s -X POST -H 'Content-Type: application/json' -H 'Accept-Encoding: identity' \ -d '{"lodash":["4.17.20"]}' \ https://registry.npmjs.org/-/npm/v1/security/advisories/bulk | xxd | head -1 ``` Or, in any project with a non-trivial lockfile: `npm audit`. ### Environment - npm: reproduced on **12.0.1 (current latest)** and on 11.8.0; also reported by others on 11.9.0, 11.12.1, 11.16.0, 11.17.0 - Node.js: v24.13.1 - OS: macOS 15 (Darwin 25.5.0) — **also reproduces on GitHub Actions `ubuntu-latest`**, i.e. a different network, machine and region - Platform: observed via Cloudflare edge `DFW` - Onset: approximately 2026-07-26 04:15 UTC; still failing continuously as of 06:20 UTC
4 条评论