Safe ICAP

Bounded RFC 3507 Internet Content Adaptation Protocol (ICAP) response parser. ICAP wraps HTTP-shaped request/response objects (REQMOD / RESPMOD / OPTIONS) inside a protocol that shares HTTP's header syntax but adds the Encapsulated header to describe a compound body of req-hdr, req-body, res-hdr, res-body, opt-body, or null-body sections at byte offsets.

Substrate for b.mail.scan (v0.9.x) — every consumer that talks to ClamAV-via-c-icap, Sophos / Trend Micro / Symantec ICAP daemons, or any RFC 3507 server hands raw response bytes through this parser before trusting any field.

## Wire-protocol caps (every dimension an attacker can grow)

## Refusals

- **Bare-CR / bare-LF / NUL inside headers** — RFC 3507 §4.3.1 inherits RFC 7230's CRLF-only rule. Bare-LF terminators are the canonical ICAP-response-injection vector (a hostile upstream smuggles a second response by terminating with \n instead of \r\n; intermediaries that accept bare-LF then desync against this parser). - **Status-code allowlist** — only 100 / 200 / 204 / 400 / 403 / 404 / 405 / 408 / 5xx are honored. RFC 3507 §4.3.3 enumerates these as the legal ICAP response codes; an unexpected 1xx continuation or 3xx redirect is refused because it's a classic header- injection class (attacker smuggles ICAP/1.0 100 X-Inject: through a permissive proxy). - **Encapsulated parse-failure** — header value must be a comma-separated list of = tokens where is one of the six legal section names and is a non-negative integer within the body region. - **Body cap** — res-body / opt-body body section length capped at profile's maxBodyBytes. Defends the parser-bomb class (RFC 3507 §3 imposes no body cap on the wire, so a hostile ICAP daemon can ship arbitrary bytes here).

## CVE / threat model

No CVE pool exists specifically for "ICAP-response-injection" because the protocol is operationally deployed inside trusted networks — that very assumption is the threat model. Operators tunnelling untrusted client byte streams through ICAP-mediated AV scanning need to refuse hostile ICAP responses just as aggressively as hostile HTTP responses. The same byte-level discipline that defends HTTP request-smuggling (CVE-2019-18801 / -18802 / -18803, CVE-2023-44487 HTTP/2 Rapid Reset) applies here — strict CRLF, strict status-code allowlist, bounded header / body / count dimensions, no continuation-line acceptance.

Parser is purely functional — no I/O, no async — operator owns the socket lifecycle (the b.mail.scan primitive composes the parser with its own ICAP socket).

b.safeIcap.parse(buf, opts?) #

stable0.9.81
{
  profile:  "strict" | "balanced" | "permissive",
  posture:  "hipaa" | "pci-dss" | "gdpr" | "soc2",
}

Parse an ICAP/1.0 response (RFC 3507 §4.3) from a byte buffer. Returns { statusCode, statusText, headers, encapsulated, headerByteLength, body, threatFound, threatName? } where:

- statusCode / statusText come from the status-line (e.g. ICAP/1.0 200 OK → 200 / "OK"). Status MUST be one of the RFC 3507 §4.3.3 codes (100 / 200 / 204 / 400 / 403 / 404 / 405 / 408 / 500-505). - headers is a lower-cased-key object. Duplicate header names collapse to an Array of values. - encapsulated is { "req-hdr": offset, "res-body": offset, ... } parsed from the Encapsulated header. null if the header is absent (legal for status 100 / 204 / 4xx / 5xx). - headerByteLength — the byte offset where the body region starts (after the terminating CRLF CRLF). - body — Buffer slice of the body region, length-capped by maxBodyBytes. Empty Buffer when the body region is absent or zero-length. - threatFound — boolean. true when the response signals an infected verdict via the well-known X-Infection-Found header (Symantec / ClamAV / Sophos all emit this on a hit) OR the status code is 403 (ICAP convention: 403 = blocked). - threatName — string when X-Infection-Found parses out a Threat= token; absent otherwise.

Throws SafeIcapError with codes: safe-icap/bad-input / oversize-header / oversize-body / oversize-header-count / oversize-header-value / bare-cr-or-lf / nul-in-header / bad-status-line / unexpected-status / bad-encapsulated / bad-profile.

var parsed = b.safeIcap.parse(wireBytes);
if (parsed.threatFound) refuseMessage(parsed.threatName);

b.safeIcap.compliancePosture(name) #

stable0.9.81hipaapci-dssgdprsoc2

Return the effective profile NAME for a compliance posture, or null for a name this parser does not map. Unlike the content-guard variant this returns the resolved profile string (every line-protocol parser composes gateContract.ALL_STRICT_POSTURES, so "hipaa" / "pci-dss" / "gdpr" / "soc2" all resolve to "strict") and never throws — the parser shape carries no overlay-clone, no buildProfile, and no loadRulePack. Wired by gateContract.defineParser.

b.safeIcap.compliancePosture("hipaa");                   // → "strict"
b.safeIcap.compliancePosture("not-a-regime");            // → null

Last updated 2026-08-08T16:39:49.652Z by seeder.