Minimum base64 length to even attempt a decode. A COST gate, not a security gate
(the security gate is decode-then-match): probe-measured floor = 12, the length
of the shortest tracked attack (base64("\r\nQUIT\r\n")). Below 12 only adds work.
Hand-rolled standard base64 decode (alphabet +/, = padding). Returns None
on any non-alphabet byte. No new dependency (mirrors the hand-rolled percent /
overlong decoders); property-tested in the differential suite.
Canonicalize a multipart field (name / filename / value). Since 10c folded the
recursive percent + overlong decode into canonicalize_value PIPELINE-WIDE,
this is just that canonical transform (multipart + is literal). Kept as a named
entry for the multipart call sites (10b-cont). Base64-derived variants from
multipart values are collected separately by the normalizer (base64_derived).
Canonicalize a single field value: recursive percent-decode + overlong-UTF8
collapse to a fixed point (Fase 10c — overlong is now folded into the canonical
surface PIPELINE-WIDE, not scoped to multipart), then NFKC normalization. The
single source of truth for value canonicalization shared by query, body, cookies
and multipart. Overlong collapse is a canonical transform (the same value, a
legal re-encoding decoded) — distinct from the derived base64 channel below.
Collapse overlong UTF-8 to ASCII and return a lossy string. Pub for §6 docs /
tests; the pipeline uses the byte-level [collapse_overlong] inside the fixpoint.
All derived inspection variants of one inspected value (§6): base64-decoded (10c) +
HTML-entity-decoded (evasion, §6-D1) + mid-token-tag-stripped (mutation, §6-D2) +
mid-token-control-stripped (§6-D2b). All are decode-then-match-then-discard. Single
entry the normalizer calls per inspected value.
Base64-DERIVED variants of value, sharing budget with the overlong stage.
If value is a confident base64 candidate that decodes to mostly-printable text,
canonicalize the decode (so a base64 wrapping a percent/overlong payload still
resolves) and push it; recurse for nested base64. Each derived string is
inspection-only (“discard if it matches nothing”). Caller pre-applies the header
per-name exclusion (this fn is alphabet-only and surface-agnostic).
HTML-entity decode for EVASION only (§6-D1): named (table above) + numeric
(&#NN; / &#xHH;), but NEVER the 5 structural chars < > & " '. Returns Some
only when at least one entity was decoded (else the value is unchanged → no point
adding it to the derived channel). decode-then-match-then-discard: this output is an
inspection-only variant, the stored value is untouched. Linear single pass.
Base64 CANDIDACY (cost gate): strict alphabet [A-Za-z0-9+/] + = padding,
length a multiple of 4, and >= len_min. NOT a security gate — a benign token
that passes still cannot cause an FP because the decoded value only contributes
if it matches a module rule (decode-then-match-then-discard). Probe-verified
benign_FP=[] at every threshold.
MID-TOKEN control-character strip (§6-D2b): drop a run of C0 control bytes injected
INSIDE an identifier to break a keyword (<<scr\0ipt> → <<script>, the gotestwaf
NUL-split mutation). Like strip_midtoken_tags, it fires ONLY when the control run
sits between word chars on BOTH sides — benign content never carries a NUL mid-word.
\t/\n/\r are EXCLUDED: those are structural whitespace handled elsewhere, and
collapsing intra-token WHITESPACE (scr ipt) is the high-FP D2b-2 variant, still
deferred. Returns Some only when a control run was dropped. Linear single pass.
MID-TOKEN tag-strip (§6-D2): drop a <…> tag ONLY when immediately surrounded by word
chars on BOTH sides (\w<…>\w) — i.e. injected INSIDE an identifier to break a token
(o<x>nfocus → onfocus, autof<x>ocus → autofocus), the gotestwaf mutation-XSS
evasion. WRAPPING tags (<code>onerror</code>, HTML tables/<b>/<a href>) are left
INTACT, so benign HTML-bearing content gains NO spurious onerror= adjacency
(probe-measured: zero new FP). Returns Some only when a tag was dropped. The tag span
is capped at 24 chars (a real mutation tag is tiny — <x>, <y>).
VBScript string-concat de-obfuscation (§6-D3): collapse the "…&…" joints VBScript
uses to split keywords across string literals — "Ex"&"e"&"cute → "Execute,
M"&"i"&"d → Mid, c"&"h"&"r → chr (gotestwaf rce-urlparam webshell). Matches a
close-quote, optional ws, &, optional ws, open-quote and drops all of it, joining the
adjacent literals. & is VBScript’s concat operator (JS uses +), so "x"&"y" in a
request value is itself a strong VBScript tell → low FP, and decode-then-match-then-
discard means it only counts if it reconstructs an RCE keyword. Returns Some only when
a joint was removed. Linear single pass.