woocommerce Case study
Searchwiz Allowlist Firewall
A search integration partner crawls the catalog from a known set of IPs. Crawling is fine; a crawler wandering into add-to-cart, checkout or the account area is not, because those routes are expensive, stateful and were never meant for automated traffic. This gate draws that line explicitly.
The business problem
Giving an external crawler normal access means it eventually finds the routes that mutate state or bypass the cache. Cart and checkout requests cannot be served from the full-page cache, so a crawler hitting them generates uncached PHP work at whatever rate it likes. Rate limiting is not the answer when the request should not be served at all.
What I delivered
- a2-searchwiz-cart-block.php, an MU plugin that applies only to requests arriving from the partner's IPs and is completely inert for everyone else.
- A hard deny list evaluated before any allow rule, covering /cart, /checkout and /my-account together with their subpaths.
- Deny rules for the equivalent AJAX surfaces, since blocking the page path alone leaves wc-ajax and admin-ajax actions reachable.
- An explicit allow for the partner's own REST namespace, which is the route the integration is actually supposed to use.
- An unconditional block on wp-admin, wp-login.php and xmlrpc.php for these IPs, regardless of anything else.
- A default-deny tail: GET and HEAD are allowed for ordinary crawling, and anything that reaches the end of the rules is refused.
- A documented kill switch in the shared runtime flags file, so the partner's engineers can be given full access temporarily during a review without editing the firewall.
Technical approach
- Deny rules run before allow rules. The reverse order is how allowlists develop holes, because a generic allow written later silently re-opens a route someone blocked earlier.
- Path matching is prefix-aware and anchored, so /cart matches /cart and /cart/anything but not a path that merely contains the word.
- The allowed IP list is an option merged with the partner's documented crawler addresses, which means it can be updated without a deploy.
- Blocks return 403 with cache headers suppressed, so a denial never gets stored and replayed by the cache layer.
- The kill switch is a constant in a runtime flags file with a comment stating it must be set back, because a temporary bypass with no note attached becomes permanent.
Result and evidence
The partner crawls the catalog and the REST namespace it needs, and the cart, checkout, account and admin surfaces are unreachable from those addresses. The firewall is scoped tightly enough that it cannot affect a real customer, because it exits immediately for any IP not on the list.
Commercial value
Integrations are a standing risk to a commerce site precisely because they are trusted. Writing the boundary down as code makes the agreement enforceable rather than a matter of the partner's good behaviour.
Readable implementation brief
implementation_brief {
project: "Searchwiz Allowlist Firewall"
file: "mu-plugins/a2-searchwiz-cart-block.php (v1.1.0)"
scope: "only requests from the partner's IPs; inert otherwise"
order: "hard deny -> explicit allow -> default deny"
denied: "/cart /checkout /my-account (+ subpaths),
matching wc-ajax and admin-ajax actions,
/wp-admin /wp-login.php /xmlrpc.php"
allowed: "partner REST namespace; GET/HEAD crawling"
response: "403 with nocache headers, never cached"
killswitch: "flag in 000-a2-runtime-flags.php, documented"
}What this project shows
The ordering of the rules is the security content of this project. Deny-before-allow with a default-deny tail is the only structure that stays correct as rules get added by someone who was not there originally.
The kill switch is there because I have seen the alternative: an engineer disables a whole security layer to run one test, and it stays disabled. A named flag with a comment is harder to forget.