Response Linter (dev)#
Catch response anti-patterns during development before they leak into production. This middleware inspects outbound responses and logs/warns (or throws in strict mode) when it detects common mistakes.
Enable only in dev/staging. In production, use warn mode at most.
What it checks (typical)#
Content-Type sanity
Missing or wrong
Content-Type(e.g., JSON body withoutapplication/json; charset=UTF-8)HTML/XML without charset
Content-Length / streaming
Content-Lengthpresent on a streaming response (illegal)Negative/invalid lengths
Compression & validators
Content-Encodingset but body appears uncompressed (double-compress risk upstream)ETagpresent + Compression active with a mismatched strategy (e.g., strong ETag on pre-encoding bytes when strategy is recompute-strong)
Caching headers
Conflicting
Cache-Controldirectives (no-storewithmax-age)Missing
Varywhen body changes byAcceptorAccept-Encoding
Status/body coherence
204/304 responses that still carry a body
1xx/204/304 responses with entity headers that must be stripped
Cookies
Set-Cookieon responses withCache-Control: public(possible cache leak)Cookies without
HttpOnly/Secure(warning)
CORS/security
CORS preflight responses missing required
Access-Control-*Obvious security header omissions in HTML (CSP, nosniff, frame options) – advisory
Misc
Duplicate headers with conflicting values
Non-ASCII in header names/values
(Exact rules depend on your implementation; adjust list to match your checks.)
Wiring#
Place it last in post-global so it sees the final response:
$postGlobal = [
\Infocyph\Webrick\Middleware\CompressionMiddleware::class,
\Infocyph\Webrick\Middleware\CorsAndPoliciesMiddleware::class,
\Infocyph\Webrick\Middleware\VaryAccumulatorMiddleware::class,
new \Infocyph\Webrick\Middleware\ResponseLinterMiddleware(
mode: 'warn', // 'warn' | 'throw'
logLevel: 'warning'
),
];
Use 'throw' only in local dev to force corrections.
Output & developer UX#
On violation, the linter can:
Log a structured entry (rule id, message, header snapshot, route name)
Annotate the response with a
X-Response-Lintheader (dev only)Throw an exception (in strict mode) with a clear remediation message
Example log:
{
"rule": "no-body-with-204",
"route": "api.users.update",
"status": 204,
"hint": "A 204 response must not include a message body."
}
Recommended rules (cheatsheet)#
Rule ID |
Description |
Fix |
|---|---|---|
|
JSON-looking body but no |
Use |
|
Body present on 204/304 |
Return empty body; strip entity headers |
|
Streaming response has |
Remove length; ensure chunked/implicit |
|
Negotiated body but |
Add/make Vary Accumulator add it |
|
|
Pick one (usually |
|
|
Use |
|
Weak ETag + encode strategy mismatch |
Align with compression strategy |
|
Preflight missing allow headers |
Add required |
Examples#
1) JSON route missing content type#
Route::get('/data', fn() => Response::create(json_encode(['ok'=>true])));
Lint: content-type-json
Fix: Response::json(['ok'=>true])
2) Stream with Content-Length#
return Response::stream(function(){ yield "..." ; })
->withHeader('Content-Length','1234');
Lint: stream-no-length
Fix: remove Content-Length (streaming responses should not declare a fixed length).
3) 204 with body#
return Response::json(['ok'=>true], 204);
Lint: no-body-with-204
Fix: return 200, or keep 204 and no body.
Tuning#
Constructor/options you might expose:
mode:'warn' | 'throw'rules: enable/disable specific checks (e.g.,['cookie-public-cache' => false])treatAsJson: heuristic (status + body prefix) threshold to flag JSON without correct content typesecurityProfile:'api' | 'web'– nudges which advisory rules to applymaxHeaderCount/maxHeaderBytes: warn when nearing platform limits
Troubleshooting#
Symptom |
Likely cause |
Fix |
|---|---|---|
Legitimate binary flagged as JSON |
Aggressive heuristic |
Loosen |
Fails only in prod |
Linter enabled in prod |
Disable or set |
Duplicate |
Manual |
Let accumulator manage; avoid manual concatenation |
Checklist#
Run Response Linter last in post-global (dev/staging)
Start with
mode: 'warn'; use'throw'locally to catch mistakes earlyEnable rules that match your project (API vs HTML site)
Fix violations at the source (helpers, middleware order, headers)