[BUG] npm audit fails: advisories/bulk returns a gzip body with no Content-Encoding header
BugNeeds Triage
### 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 条评论