parlov docs

Forcing 304 Not Modified

The If-None-Match header makes a request conditional, creating a 304 vs 404 existence oracle.

Implemented

Mechanism: The If-None-Match header makes a request conditional. Per RFC 9110 §13.1.2, when a GET request includes If-None-Match: *, the condition evaluates to false if any current representation exists — meaning the server responds 304 Not Modified. If the resource doesn't exist, no representation exists, and the server returns 404.

Isolated Variable: Only the If-None-Match header is added with a wildcard * value. Everything else matches the baseline request.

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

GET — Existing Resource

GET /api/users/1001 HTTP/1.1
Host: target.com
If-None-Match: *

HTTP/1.1 304 Not Modified
ETag: "a1b2c3d4"

GET — Non-Existing Resource

GET /api/users/9999 HTTP/1.1
Host: target.com
If-None-Match: *

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

{"error": "Not Found"}

💡 Note on If-None-Match with specific ETags: You can also use a fabricated ETag value (e.g., If-None-Match: "bogus") instead of the wildcard. Against an existing resource, the ETags won't match, so the server returns 200 with the full representation. Against a non-existing resource, you get 404. The wildcard * variant is cleaner because the 304 is a distinct status code that's unambiguous.

Mitigation: This oracle is inherent to HTTP conditional request semantics and is difficult to collapse without breaking caching. The most practical defense is to require authentication before evaluating conditional headers.

Mechanism: The If-None-Match header makes a request conditional. Per RFC 9110 §13.1.2, when a GET request includes If-None-Match: *, the condition evaluates to false if any current representation exists — meaning the server responds 304 Not Modified. If the resource doesn't exist, no representation exists, and the server returns 404.

Isolated Variable: Only the If-None-Match header is added with a wildcard * value. Everything else matches the baseline request.

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

GET — Existing Resource

GET /api/users/1001 HTTP/1.1
Host: target.com
If-None-Match: *

HTTP/1.1 304 Not Modified
ETag: "a1b2c3d4"

GET — Non-Existing Resource

GET /api/users/9999 HTTP/1.1
Host: target.com
If-None-Match: *

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

{"error": "Not Found"}

💡 Note on If-None-Match with specific ETags: You can also use a fabricated ETag value (e.g., If-None-Match: "bogus") instead of the wildcard. Against an existing resource, the ETags won't match, so the server returns 200 with the full representation. Against a non-existing resource, you get 404. The wildcard * variant is cleaner because the 304 is a distinct status code that's unambiguous.

Mitigation: This oracle is inherent to HTTP conditional request semantics and is difficult to collapse without breaking caching. The most practical defense is to require authentication before evaluating conditional headers.