Methodology

The Firewall Nobody Documented

The managed host's origin firewall appears in none of its own documentation, and its rules match tokens, not intent. Sixteen disposable probes mapped it - and found the off switch.

A deploy failed against a firewall rule that no public documentation describes — not the host's, not ours. The runbook we trusted had been right once; the wall had moved since.

On 2026-06-04, a section deploy to a client site — a welding and fabrication shop — was refused by the managed host's origin firewall. The runbook in hand modeled that firewall narrowly: it blocked REST POST bodies containing HTML literals, bypassable through a custom endpoint or by form-encoding. All of that was stale. The live rule set was wider in some places and narrower in others, and the host publishes none of it.

The transferable finding is not the workaround. It is the method: when a security layer you do not administer refuses your work, stop reasoning about what it probably does and measure what it actually does, one construct per request. We call that Probe the Firewall, Don't Model It. The artifact it produces — a block/pass matrix — outlives the deploy that prompted it, because the passes are the workaround.

Step 1 — Identify the blocking layer from response headers, not from assumptions

A managed host, a CDN, a security plugin and an upstream proxy produce failures that look identical from a terminal, and each has a different remedy.

The refused requests on 2026-06-04 returned a waf-block: request header, an empty body, and — the load-bearing part — no CDN mitigation header of any kind. A CDN block announces itself: a challenge, a ray identifier, a mitigation reason. None was present. The block belonged to the host's own origin firewall, one layer beneath the CDN dashboard where an operator would spend the next hour.

Worked example of the inverse. Later in the same session a scripted request from Python's urllib was refused with a different signature — that one was the CDN, applying a browser-signature ban to the default user agent. Re-issued through curl, it passed. Two layers, two rule sets, two remedies, and only the response headers tell them apart.

Step 2 — Build a matrix of minimal disposable probes, one construct each

A probe that tests three things at once tells you nothing when it fails. The session ran roughly sixteen disposable snippets on 2026-06-04, each carrying a single construct, each deployed and observed in isolation against the site's managed host.

They were left in place rather than removed, because the snippet manager's own REST DELETE returns a 500 here — a consequence of the very rule set being mapped. The instrument for cleaning up the probes was blocked by the thing the probes measured.

Every row below is an observed result from that session, not an inference from documentation.

ConstructResultNote
Tag-start strings — <script, <style, <section, <svg, <img, <inputBlockedMatched as XSS tag-starts on the raw payload; the string alone is enough
Injection and obfuscation tokens — base64_decode, hex2bin, chr(, pack(, hexdecBlockedToken match against the source text
Anonymous closures — function (BlockedTreated as an obfuscation signature
Server-side-request-forgery fetch helpersBlockedAny helper containing a fetch function trips it
require / includeBlockedFile-inclusion rule
Binary media uploadBlockedRefused at the origin firewall, not the media library
HTTP DELETE verbBlockedWhy the probes could not be cleaned up over REST
GET requestsPassedEvery read path stayed open throughout
Named functionsPassedSemantically identical to the blocked closure form
Braces and control flowPassedNo structural PHP rule in evidence
Route registrationPassedCustom endpoints register normally
Heavy string concatenationPassedRoughly 150 concatenated segments in one expression
Arbitrary hex or base64 dataPassed80 KB and above passed cleanly
Small, clean PHPPassedThe baseline control for the matrix

Step 3 — Record the passes as carefully as the blocks

Most write-ups of a firewall encounter publish the blocks, because the blocks are the complaint. The passes are the deliverable, and two rows do all the work.

Anonymous closures were blocked; named functions passed. Those two constructs do the same thing. Nothing about a closure is more dangerous than a named function with the same body — the rule is not reasoning about behaviour, it is matching a shape that obfuscated payloads tend to have. The remedy is a rename, not a redesign.

The decoder tokens were blocked; the encoded data was not. base64_decode was refused while 80 KB of base64 payload passed without comment. That inverts the intuition everyone brings to a firewall, which is that the dangerous thing is the data. Here the data is inert and the short verb that would act on it is the trigger.

The workaround falls out of those two rows: carry content as data, assemble it with named functions and string concatenation, and never let a decoder token or a tag-start literal appear in the transmitted source. Not a clever bypass — the rule set, read back in the order it was written.

What the undocumented firewall blocks and what passes A two-column probe matrix. Blocked: XSS tag-starts, anonymous closures, fetch and SSRF helpers, PHP obfuscation tokens, require and include, binary upload and the DELETE verb. Passes: GETs, named functions, braces and control flow, heavy string concatenation, large plain data, small clean PHP. A footer notes the layer was identified from response headers as the host's origin firewall, and that the only relax lever is a WAF toggle documented in none of the host's own knowledge-base articles. THE PROBE MATRIX — 16 DISPOSABLE SNIPPETS, ONE CONSTRUCT EACHBLOCKEDXSS tag-starts (<script, <svg, <img …)Anonymous closures — function (Fetch / SSRF helpersPHP obfuscation (base64_decode, chr( …)require / includeBinary upload · the DELETE verbPASSESGETsNAMED functionsBraces, control flow, route registrationString concatenation (~150 segments)Hex / base64 DATA (≥80 KB fine)Small clean PHPIdentified from response headers (waf-block; no CDN mitigation header) — the HOST origin firewall.The off switch: a WAF toggle in the host dashboard's Security tab. In none of its KB articles.
Probed 2026-06-04 with about sixteen disposable snippets, one construct each; passes recorded as carefully as blocks, because the passes are the workaround.

The rule with no workaround

One capability could not be rebuilt. A helper endpoint that performs an outbound fetch trips the request-forgery rule by construction — the fetch function is the match, and there is no version of "a helper that fetches things" without one. Write that down, because an hour hunting for a route around a rule that has none is an hour gone.

Step 4 — Find the off switch and time-box the window

The host does expose a control. On its dashboard: Apps → the site → Manage → Security tab → scroll past the malware scanner → a Web Application Firewall toggle.

That path appears in none of the host's knowledge-base articles. It was found by walking the dashboard rather than searching the documentation, and it is the most useful sentence in this post for anyone on the same platform.

The deploy ran with the firewall off, over curl, on-domain, in about two minutes, and the firewall went back on immediately. Time-boxing is not a formality — an off switch you forget to flip back is a worse defect than the one you were working around.

Verification, and what it proved beyond the deploy

The post-deploy check ran logged out, on the canonical URL, with no cache-buster, because a cache-busted URL verifies that your file exists rather than that a visitor will receive it.

The response returned a cache HIT dated after the deploy — a two-for-one result. The new section was live for anonymous traffic, and both the CDN and the page cache had purged and re-cached with no manual purge issued. A cache HIT older than the deploy would have meant the opposite, and the difference is one timestamp.

What we did not verify

The source record carries status: draft, and two claims inside it are weaker than the rest. Mobile rendering and the reduced-motion variant were not re-tested live at deploy. That both behave correctly rests on the CSS for those paths being unchanged from an earlier verified state — a reasonable inference, and not a measurement. A regression there unrelated to this deploy would have gone unnoticed.

What would prove this wrong

  • The same probe set passing unchanged on a second site on the same host. The rule set would be per-site configuration rather than a platform default, and the matrix stops generalizing.
  • A helper containing a fetch function deploying successfully. "No workaround exists" is the strongest claim here and the easiest to overturn.
  • A waf-block header arriving alongside a CDN mitigation header. Then Step 1's layer identification was wrong and everything built on it is suspect.
  • urllib succeeding from a different network. That sinks the browser-signature explanation.
  • The host publishing a rule set that contradicts any row above. Documentation beats our probes, and we replace the table.

What the matrix is for

A firewall you do not administer is not a specification to reason about — it is a black box to characterize. Sixteen small probes, one construct each, turn every future deploy on that platform into a table lookup. If a security layer has ever eaten a deploy and the answer was "we worked around it somehow," the four steps above will leave a matrix the next deploy inherits instead of a story nobody can reuse. We will map one alongside a team that wants the artifact.

  • #blog
  • #waf
  • #wordpress
  • #managed-hosting
  • #deployment
  • #debugging
Share X LinkedIn
Build it yourself?

Get the kit, not just the theory.

We'll send the build checklist behind this post — and the next pillar when it ships. One email, no drip sequence. Unsubscribe in one click.

Want this built for you?

Book a discovery call. We'll walk your numbers.

20 minutes. Tell us what's broken, hear what we'd ship in the next 90 days. No pitch deck.