Skip to content

Add deterministic WAF-evasion payload mutation tool - #41

Merged
ankitsingh015 merged 3 commits into
mainfrom
claude/waf-payload-mutation
Aug 25, 2026
Merged

Add deterministic WAF-evasion payload mutation tool#41
ankitsingh015 merged 3 commits into
mainfrom
claude/waf-payload-mutation

Conversation

@ankitsingh015

Copy link
Copy Markdown
Owner

Summary

  • Adds mutate_payload() to waf-bypass-mcp: deterministic (non-LLM) WAF-evasion techniques — percent-encoding, double-percent-encoding, HTML entity encoding, overlong UTF-8, whitespace substitution, SQL keyword case-mixing, SQL comment splitting, JS hex-escaping.
  • No hardcoded model/vendor dependency, consistent with model_gateway.py's no-vendor-lock-in design.
  • Fixes found during audit: invalid JS escape sequence for non-BMP characters, and an XSS-marker substring-vs-word-boundary inconsistency in context detection.

Test plan

  • 138 tests pass (pytest tests/), including new regression tests for both audit fixes
  • python -m py_compile clean

New mutate_payload(payload, context) tool -- distinct from
attempt_bypass(): that handles a whole REQUEST getting blocked
regardless of payload content (Tiers 1-4, header/path/method/HTTP-
version), this handles a PAYLOAD itself getting pattern-matched by a
WAF/filter once the request already reaches the backend.

Operationalizes .claude/skills/waf-evasion-encoding-playbook/SKILL.md's
manual technique list as deterministic string transforms: percent
encoding, double percent encoding, HTML entity encoding (hex/decimal),
overlong UTF-8 for the well-documented characters (/ . < >), whitespace
substitution (tab/newline/vertical-tab/form-feed/NBSP/SQL comment), SQL
keyword case-mixing, SQL inline-comment splitting, and JS hex-escaping.
Auto-detects SQL vs XSS vs both vs generic context from the payload.

Deliberately NOT an LLM call to a named model -- these are fixed-formula
transforms with no benefit from non-determinism, and hardcoding a
specific provider would conflict with model_gateway.py's whole
no-lock-in design. Pure string transformation, no live request sent, so
not scope-gated or budget-counted (same reasoning as oob-mcp's
generate_payload_url()).

Caught and fixed a real bug before landing: the first draft matched SQL
keywords with re.escape(kw) and no \b word boundaries, so "OR" matched
inside "FORMULA" and "AND" inside "SANDBOX", corrupting unrelated text
instead of only mutating the actual keyword. Fixed with \b boundaries
and added regression tests for it.

19 new tests, all passing; full suite (136 tests) still green.
…alse positives

Independent fresh-eyes review of mutate_payload() found two real bugs:

1. _js_hex_escape emitted \uXXXXX (5 hex digits) for codepoints above
   0xFFFF (e.g. an emoji) via {:04x}'s minimum-width formatting -- not
   valid JS syntax, which requires exactly 4 hex digits per \u escape or
   the \u{...} codepoint form for astral characters. Fixed to use
   \u{...} above the BMP.

2. _detect_context's XSS-marker check used plain substring matching
   (marker.lower() in payload.lower()) while its SQL-keyword check used
   \b-bounded regex. Several _XSS_MARKERS entries ("onload", "onclick")
   are short common word fragments, so the substring check false-
   positived on SQL-only payloads containing them embedded in an
   unrelated word (e.g. "onload" inside "salonload", "onclick" inside
   "econclick") -- misclassifying pure-SQL payloads as context="both"
   and adding a spurious JS hex-escape variant to the output. Fixed with
   a boundary-aware matcher (_has_xss_marker) that adds \b only on a
   side where the marker itself starts/ends with a word character --
   punctuation-anchored markers like "<script"/"alert(" don't need it
   and a literal \b there wouldn't be meaningful.

Note: a LIKE '%onload%' style payload still detects as XSS-adjacent --
that's correct, not a bug, since '%' isn't a word character and "onload"
genuinely is a bounded standalone word there, not an embedded substring.

4 new regression tests (2 for each fix); full suite (138 tests) green.
@ankitsingh015
ankitsingh015 merged commit d7b5e9c into main Aug 25, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant