/** * @module @arcis/node/middleware/response-splitting * * HTTP response splitting prevention (sdk-vectors.md tier 1 #27). * * Response splitting is the *output* counterpart to header injection: app * code passes user input into `res.setHeader`, `res.writeHead`, or * `res.appendHeader` (Node 17+) without stripping CR/LF, and an attacker * uses the embedded newline to break out of the header block and forge a * second response. Most often weaponised against `Location:` after a * redirect that reflects user input (`/redirect?to=...`). * * `sanitizeHeaderValue` already covers the byte-level fix on the way in; * this middleware wraps the response object so every header that leaves * the app gets sanitised on the way out — even when the app forgets. * * ```ts * import { responseSplittingGuard } from '@arcis/node/middleware/response-splitting'; * * app.use(responseSplittingGuard()); * * // Later — even this passes through clean: * app.get('/r', (req, res) => res.redirect(req.query.to as string)); * ``` * * Pair with `validateRedirect` for full coverage: this middleware blocks * the response-splitting payload, `validateRedirect` blocks the * open-redirect payload. */ import type { RequestHandler } from 'express'; import { detectHeaderInjection, sanitizeHeaderValue } from '../sanitizers/headers'; export interface ResponseSplittingGuardOptions { /** * What to do when an outgoing header value contains CR / LF / NUL. * * - `'strip'` (default) — silently sanitise the value before it reaches * the wire. Preserves availability; existing routes don't break. * - `'reject'` — throw a `ResponseSplittingError`. Use in apps that * would rather fail-closed than emit a partial response. * * Both modes invoke `onDetect` if provided. */ mode?: 'strip' | 'reject'; /** * Per-detection callback. Fires before strip/reject. Useful for * logging or alerting when an attempted split slips through into the * response builder. */ onDetect?: (header: string, originalValue: string) => void; } /** * Thrown by `responseSplittingGuard({ mode: 'reject' })` when an * outgoing header value contains CR / LF / NUL. The header name is in * `header`; the originally attempted value is in `value` so it can be * logged or surfaced in an error handler. */ export declare class ResponseSplittingError extends Error { readonly header: string; readonly value: string; constructor(header: string, value: string); } /** * Re-export under the response-splitting name. Same byte pattern as * header injection (CR / LF / NUL) — different threat model: input * boundary vs output boundary. */ export declare const detectResponseSplitting: typeof detectHeaderInjection; /** * Re-export under the response-splitting name. Strips CR / LF / NUL. */ export declare const sanitizeResponseHeader: typeof sanitizeHeaderValue; /** * Build the response-splitting guard middleware. Wraps `res.setHeader`, * `res.writeHead`, and `res.appendHeader` (when present) on each * incoming request so every header that leaves the app gets the same * CRLF / NUL treatment regardless of which code path emitted it. * * Wrapping happens per-request (not on the prototype) so multiple * mounts with different options don't trample each other. */ export declare function responseSplittingGuard(options?: ResponseSplittingGuardOptions): RequestHandler; export default responseSplittingGuard; //# sourceMappingURL=response-splitting.d.ts.map