parlov docs

Forcing 406 Not Acceptable

The Accept header declares which media types the client will tolerate, creating a 406 vs 404 existence oracle.

Implemented

Mechanism: The Accept header declares which media types the client will tolerate. Per RFC 9110 §12.5.6, a server performing proactive content negotiation may send 406 when it cannot produce a representation matching the Accept value. The server must first locate the resource and inspect its available representations — so a 406 implies the resource was found.

Isolated Variable: Only the Accept header changes. The rest of the request remains identical to the baseline.

Oracle Signal: 406 (exists) vs 404 (does not exist).

GET — Existing Resource

GET /api/users/1001 HTTP/1.1
Host: target.com
Accept: application/x-nonexistent

HTTP/1.1 406 Not Acceptable
Content-Type: application/json

{"error": "Not Acceptable", "detail": "Cannot produce application/x-nonexistent"}

GET — Non-Existing Resource

GET /api/users/9999 HTTP/1.1
Host: target.com
Accept: application/x-nonexistent

HTTP/1.1 404 Not Found
Content-Type: application/json

{"error": "Not Found"}

💡 Note: Many servers ignore Accept entirely and serve their default representation regardless, returning 200 for existing resources instead of 406. In that case the oracle still exists — the differential is 200 vs 404 — but it's the same signal as the baseline probe. This technique is most useful when the server does implement strict content negotiation.

Mitigation: Return 404 for all requests targeting a non-existent resource before evaluating content negotiation headers. Alternatively, collapse 406 into 404 for resource-scoped endpoints entirely.