{"slug": "8096-ips-one-woocommerce-cart-attack-how-we-protected-the-cart-without-blocking", "title": "8,096 IPs, One WooCommerce Cart Attack: How We Protected the Cart Without Blocking AI Commerce", "summary": "A team operating a WooCommerce store traced a cart-abuse campaign to 8,096 unique IP addresses rotating through browser-like Chrome/150 user agents, with most addresses appearing only once or twice. Rather than block IPs or user agents, they moved bot enforcement to the wp_loaded hook at priority 1 so requests are classified before WooCommerce processes add-to-cart mutations, returning 403s to crawlers like MJ12bot and Reflectionbot while keeping legitimate AI shopping agents and normal browsers on the standard cart flow.", "body_md": "A real-world WooCommerce incident involving rotating IPs, browser-like bot traffic, WordPress lifecycle ordering, signed cart proofs, Redis, and keeping UCP/MCP agent commerce online.\n\nA WooCommerce store we operate started showing a strange traffic pattern.\n\nAt first glance, it looked like growth.\n\nTraffic increased sharply. The basket page was receiving unusual attention. Google Analytics showed thousands of new sessions.\n\nThen we looked closer.\n\nEngagement was almost zero.\n\nRevenue from the traffic was zero.\n\nAnd thousands of requests were hitting WooCommerce URLs containing:\n\n```\n?add-to-cart=...\n```\n\nThis turned into an interesting engineering problem because the obvious solutions were also the wrong solutions.\n\nWe could not simply block the IP addresses.\n\nWe could not block the browser User-Agent.\n\nWe did not want to block legitimate crawlers indiscriminately.\n\nAnd most importantly, we are actively building infrastructure that allows legitimate AI shopping agents to interact with WooCommerce.\n\nWe needed to stop abusive automation **without breaking agentic commerce**.\n\nThis is how we approached it.\n\nThe traffic initially looked impressive.\n\nThousands of sessions appeared, with large increases in active and new users.\n\nBut almost none of those sessions behaved like customers.\n\nWe saw extremely low engagement and a disproportionate amount of traffic around the WooCommerce basket.\n\nServer logs provided the real picture.\n\nRequests repeatedly looked like this:\n\n```\nGET /shop/example-product/?add-to-cart=1234\nGET /basket/\n```\n\nSometimes the second request arrived only one or two seconds after the first.\n\nThe User-Agent often looked completely ordinary:\n\n```\nMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)\nAppleWebKit/537.36 (KHTML, like Gecko)\nChrome/150.0.0.0 Safari/537.36\n```\n\nBlocking that User-Agent would have been a bad security decision.\n\nChrome 150 is not an identity.\n\nIt is just a string.\n\nAcross the available logs, we extracted unique IP addresses that used that Chrome/150 signature while requesting `add-to-cart`.\n\nThe result:\n\n```\n8,096 unique IP addresses\n```\n\nThis immediately changed the problem.\n\nTraditional per-IP rate limiting or Fail2ban-style blocking could still remove individual offenders, but it was no longer an effective primary defense.\n\nMost addresses appeared only once or twice.\n\nThe network was rotating faster than an IP reputation system could meaningfully respond.\n\nASN sampling also showed significant hosting and proxy infrastructure, including traffic associated with several hosting networks.\n\nBut ASN blocking was not the answer either.\n\nThe fundamental problem was not:\n\nWhich IP is malicious?\n\nThe better question was:\n\nShould this request be allowed to perform an expensive state-changing WooCommerce operation?\n\nThat distinction became the foundation of the solution.\n\nNot all of the traffic was pretending to be a browser.\n\nWe also observed recognizable crawlers hitting cart operations, including identifiers such as:\n\n```\nSERankingBacklinksBot\nMJ12bot\nReflectionbot\n```\n\nThose could be handled through explicit bot detection.\n\nBut during testing we discovered another important issue.\n\nCorrectly detecting a bot does not help if enforcement happens too late in the WordPress lifecycle.\n\nWooCommerce processes legacy add-to-cart requests during `wp_loaded`.\n\nOur enforcement path was initially occurring later.\n\nThat meant a request could be correctly identified as a bot while WooCommerce had already been given an opportunity to process the cart operation.\n\nThe fix was conceptually simple:\n\n```\nadd_action(\n    'wp_loaded',\n    array( __CLASS__, 'observe_request' ),\n    1\n);\n```\n\nPriority matters.\n\nWooCommerce's cart processing happens later.\n\nSecurity enforcement must happen **before the mutation**, not after it.\n\nAfter correcting the lifecycle ordering, controlled tests produced the expected result:\n\n``` php\nMJ12bot      -> 403\nReflectionbot -> 403\nSERanking    -> 403\nNormal browser -> normal WooCommerce flow\n```\n\nThat solved recognizable bots.\n\nIt did not solve the distributed browser-like traffic.\n\nIt was tempting.\n\nThe suspicious traffic had a remarkably consistent Chrome/150 signature.\n\nBut blocking it globally would have violated one of the basic principles we wanted the system to follow:\n\nA User-Agent is a signal, not authentication.\n\nThe same applied to several other tempting rules.\n\nWe did not want to block requests merely because they had:\n\n`/basket/` URL\nEach might be useful as a signal.\n\nNone was strong enough to become the security boundary.\n\nWe built a separate WooCommerce protection layer around the legacy GET add-to-cart flow.\n\nThe idea is straightforward.\n\nA client should not be able to arrive from nowhere and immediately perform a legacy cart mutation.\n\nA normal storefront visitor first establishes first-party context.\n\nThe cart request then carries short-lived proof that this context exists.\n\nThe proof is signed using HMAC:\n\n``` php\n$issued_at = time();\n$nonce     = wp_generate_password( 20, false, false );\n\n$payload = 'v1.' . $issued_at . '.' . $nonce;\n\n$signature = hash_hmac(\n    'sha256',\n    $payload,\n    wp_salt( 'auth' )\n);\n```\n\nThe resulting proof is stored in a secure first-party cookie with properties including:\n\n```\nHttpOnly\nSecure\nSameSite=Lax\n```\n\nThe proof expires after five minutes.\n\nBefore WooCommerce processes a legacy add-to-cart operation, the guard verifies the signature and age.\n\nConceptually:\n\n```\nLegacy ?add-to-cart request\n        |\n        v\nValid short-lived proof?\n      /   \\\n    no     yes\n    |       |\n   403      v\n        WooCommerce\n```\n\nThis does not attempt to determine whether the client is human.\n\nThat is an important distinction.\n\nA sufficiently capable automation system can obtain the proof.\n\nThe mechanism instead changes the economics and state requirements of the attack while preventing blind, stateless cart mutations.\n\nThere was a practical WordPress problem.\n\nA heavily cached storefront page may be served without executing the PHP code that would normally issue a proof cookie.\n\nSo proof issuance cannot depend solely on uncached page execution.\n\nWe exposed a cache-safe proof endpoint:\n\n```\n/wp-json/wccg/v1/proof\n```\n\nA request to that endpoint returns the signed first-party cookie and explicitly disables caching.\n\nExample:\n\n```\n{\"issued\":true}\n```\n\nThe storefront can establish the proof while full-page caching remains enabled.\n\nAgain, this is not CAPTCHA.\n\nIt is not proof of humanity.\n\nIt is proof of state.\n\nThe store already uses Redis as its WordPress external object cache.\n\nRather than creating another Redis connection, the protection layer can use the normal WordPress object-cache API.\n\nThat gives us inexpensive counters for signals such as:\n\n```\nglobal cart mutations per minute\nrequests with valid proof\nrequests without proof\nrejected cart mutations\nproof issuance velocity\n```\n\nThat becomes useful if the automation adapts.\n\nFor example, if the bot eventually learns:\n\n```\nGET /wp-json/wccg/v1/proof\nGET /?add-to-cart=1234\n```\n\nthen possession of a proof is no longer sufficiently discriminating.\n\nAt that point Redis-backed behavioral and velocity signals can become the next layer.\n\nSecurity controls should evolve with the attacker instead of pretending one rule will remain perfect forever.\n\nThis is where the project became more interesting.\n\nAt Zologic, we are also building **UCPReady**, our WooCommerce implementation of the Universal Commerce Protocol.\n\nThe same production store under attack is intentionally exposed to legitimate AI commerce systems.\n\nIts UCP manifest is available at:\n\n```\n/.well-known/ucp\n```\n\nand it exposes agent-commerce transports including:\n\n```\nREST\nMCP\nEmbedded\n```\n\nThat means a simplistic security solution could have \"fixed\" the bot problem by breaking the future commerce infrastructure we actually wanted to preserve.\n\nSo the cart protection explicitly exempts the UCP and REST infrastructure.\n\nThe result is two separate trust paths.\n\n```\nBrowser\n   |\n   v\nfirst-party proof\n   |\n   v\nlegacy ?add-to-cart\n   |\n   v\nWooCommerce\nphp\nAI agent\n   |\n   v\n/.well-known/ucp\n   |\n   +--> REST\n   |\n   +--> MCP\n   |\n   +--> Embedded checkout\n```\n\nThey do not need to pretend to be the same thing.\n\nThat separation is important.\n\nWe did not want to rely only on our own testing.\n\nUCP Checker independently evaluates UCP storefronts and runs live checks against manifests and declared transports.\n\nHouse of Parfum currently receives:\n\n```\nUCP Status: Verified\nGrade: A\nScore: 100/100\n\nAgent Discovery:      100\nUCP Conformance:      100\nCapability Coverage:  100\n\nTransports:\n- REST\n- MCP\n- Embedded\n```\n\nThe store is also currently listed among the highest-scoring stores on the UCP Checker leaderboard.\n\nEarlier testing by UCP Checker was particularly valuable to us because their UCP Playground exposed real interoperability problems that controlled development environments had not.\n\nThat is exactly what standards-based engineering needs: independent implementations testing each other's assumptions.\n\nThere is an emerging mistake I expect many commerce sites to make.\n\nThey will see AI bots, scrapers and abusive automation and respond with increasingly broad blocking rules.\n\nEventually legitimate agents will be blocked too.\n\nThe opposite mistake is equally dangerous: exposing everything because \"AI agents need access.\"\n\nThe correct model is more nuanced.\n\nAn AI crawler fetching public discovery metadata is not the same operation as an anonymous client mutating a shopping cart.\n\nA UCP agent using a defined commerce protocol is not the same thing as a scraper hammering legacy WooCommerce URLs.\n\nSecurity policy should reflect those differences.\n\nOur architecture now looks roughly like this:\n\n```\n                    Internet\n                       |\n          +------------+-------------+\n          |                          |\n   Recognized bots              Commerce clients\n          |                          |\n          v                          v\n    Bot policy layer          +------+------+\n                              |             |\n                         Legacy Web       UCP/API\n                              |             |\n                              v             v\n                       Cart proof       Protocol\n                         guard           endpoints\n                              |             |\n                         WooCommerce <------+\n```\n\nDifferent clients.\n\nDifferent capabilities.\n\nDifferent trust boundaries.\n\nAfter enforcement became active, we measured the real traffic again.\n\nFor the post-enforcement sample:\n\n```\nAll add-to-cart requests: 36\n\n403 blocked: 35\n302 allowed: 1\n```\n\nThen we isolated the distributed Chrome/150 pattern:\n\n```\nChrome/150 add-to-cart requests: 30\n\n403: 30\n302: 0\n```\n\n**30 out of 30 were rejected.**\n\nThe rotating IP addresses continued.\n\nThe User-Agent continued.\n\nThe requests continued.\n\nBut changing IP addresses no longer helped because IP identity was no longer the security boundary.\n\nAt the same time, we verified that legitimate UCP infrastructure remained online:\n\n```\n200 /.well-known/ucp\n\n200 /wp-json/ucpready/v1/openapi.json\n\n200 /wp-json/ucpready/v1/mcp-tools.json\n```\n\nThat was the outcome we wanted.\n\nNot:\n\nBlock more bots.\n\nBut:\n\nStop unauthorized state mutation while preserving legitimate machine commerce.\n\nThe most useful lesson from this incident was not a particular WordPress hook or HMAC function.\n\nIt was changing the question.\n\nWhen an attacker can rotate through thousands of addresses, asking:\n\nIs this IP a bot?\n\nhas diminishing value.\n\nInstead ask:\n\nHas this client established the state and authority required to perform this operation?\n\nThat principle extends well beyond WooCommerce.\n\nProtect the operation.\n\nDefine the trust boundary.\n\nMake expensive state transitions require context.\n\nKeep machine-readable public interfaces intentionally accessible.\n\nAnd treat legitimate agents as first-class clients instead of forcing them to masquerade as browsers.\n\nWe are continuing to develop these ideas at **Zologic**, including WooCommerce bot protection, agent-aware access controls, UCPReady, MCP/REST commerce integrations, and security architectures that distinguish legitimate AI agents from abusive automation.\n\nIf your WooCommerce store is dealing with:\n\n`add-to-cart` abuse\nwe are interested in looking at the problem.\n\nThe solution should not be another giant IP blacklist.\n\nIt should fit the way your store actually works.\n\n**If you're seeing similar traffic, contact [Zologic](https://zologic.nl) and send us a sample of the pattern you're dealing with. We can help investigate it and design the appropriate protection or agent-commerce integration.**\n\n*This article describes a real production incident. IP addresses and infrastructure observations are treated as network evidence only; they do not establish the identity or intent of the underlying operator.*", "url": "https://wpnews.pro/news/8096-ips-one-woocommerce-cart-attack-how-we-protected-the-cart-without-blocking", "canonical_source": "https://dev.to/zologic/8096-ips-one-woocommerce-cart-attack-how-we-protected-the-cart-without-blocking-ai-commerce-k47", "published_at": "2026-10-05 22:35:03+00:00", "updated_at": "2026-10-05 22:47:31.648247+00:00", "lang": "en", "topics": ["ai-agents", "ai-crawlers", "agent-protocols", "ai-tools"], "entities": ["WooCommerce", "WordPress", "Redis", "Google Analytics", "MJ12bot", "SERankingBacklinksBot", "Reflectionbot", "Chrome"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/8096-ips-one-woocommerce-cart-attack-how-we-protected-the-cart-without-blocking", "markdown": "https://wpnews.pro/news/8096-ips-one-woocommerce-cart-attack-how-we-protected-the-cart-without-blocking.md", "text": "https://wpnews.pro/news/8096-ips-one-woocommerce-cart-attack-how-we-protected-the-cart-without-blocking.txt", "jsonld": "https://wpnews.pro/news/8096-ips-one-woocommerce-cart-attack-how-we-protected-the-cart-without-blocking.jsonld"}}