Guide 122 worked guide

How to find business-logic abuse paths in a checkout or credit flow

Find business-logic abuse in checkout and credit flows by tracing eligibility, competing writers, repeated actions, and the boundary between support and fresh grants.

To review a checkout or credit flow, write down the rules that must stay true even when a user changes request order, retries, opens two sessions or sends values the interface would never offer. Trace the entire server-side state transition, including payment callbacks, credits and fulfillment. For each transition ask: who may cause it, which trusted record supplies the price or balance, what state must already exist, and what stops the effect happening twice? A suspicious request shape alone is not a vulnerability; the evidence must connect an allowed action to an unintended business outcome. If the payment provider, concurrency behavior or deployed worker is unknown, record the missing proof. OWASP's Business Logic Security guidance emphasizes server-side invariants and state rather than assuming a clean generic scan covers the business rules.

Separate the promised flow from the actual state changes

Illustrative case only; no real checkout, payment or customer data was used. Birch Market sells prepaid workshop seats. A customer may apply a single-use $20 account credit to a $60 order and then pay the $40 balance. A successful payment confirmation makes the order eligible for fulfillment. Customer A's credit must never pay Customer B's order. The credit may be consumed only once, and a failed payment must not create a fulfilled order. These are product rules, not scanner signatures.

Write them as invariants before opening the code:

  1. The server calculates the order total from its current catalog, eligibility and discount rules; it does not trust a submitted final price.
  2. The credit belongs to the same customer account as the order, is eligible for this order, and is consumed at most once.
  3. An order can move to paid only after an authoritative payment result for the expected amount, currency, provider account and bound order.
  4. Fulfillment is triggered at most once for that paid order, even if callbacks or jobs repeat.
  5. Cancellation, refund and expiry have explicit paths that do not silently restore spendable credit twice.

The OWASP WSTG business-logic data-validation guide tells reviewers to check values constrained only by the interface and to follow values across system handoffs. Its payment-functionality guide discusses the danger of treating a browser return to a success page as proof of payment. For Birch, neither a client-supplied total nor a redirect is authoritative evidence of settlement.

Draw the state machine and handoffs

A checkout diagram need not be elaborate. It must distinguish commands from verified facts:

Current state Allowed event Required server check Next state / durable effect
Draft order Apply credit Credit belongs to order account, is eligible and unconsumed; price recomputed Credit reserved for this order
Credit reserved Start payment Amount equals server-computed remainder; payment attempt bound to order Payment pending
Payment pending Verified payment success Trusted provider event refers to this order and amount; event not already applied Paid, one fulfillment intent
Payment pending Terminal failure or expiry reconciliation Verify provider state and handle any outstanding/late payment under policy before releasing once Failed, expired or reconciliation pending
Paid Duplicate success event or page refresh Order already paid No second credit debit or fulfillment

This is the decision artifact for the review. The actual system may use different states and a more complex ledger. Map those exact states rather than forcing this table onto the code. Include the edge that creates the provider attempt, the webhook or polling consumer, the ledger write and the fulfillment queue. A controller that looks safe in isolation may delegate the decisive write to another process.

MITRE's CWE-841 describes software that fails to enforce required workflow order. The useful question is not “Can the browser show a later page?” but “Can a server-side transition or value transfer occur without the required preceding state?” A frontend step indicator cannot enforce an order if the API accepts the transition directly.

Find the rule that the code never promised to enforce

The useful disagreement in Birch is not a malformed price. It is what single-use credit means. The following evidence packet is synthetic; the code and results are a worked design example, not observations from a real checkout.

Evidence What it says What it does not settle
Offer page “One welcome credit per account” Which durable record enforces eligibility
Product specification New accounts may receive a $20 welcome benefit once Whether a failed order consumes the benefit
Reservation operation A credit record can be reserved for one order only Whether the account can obtain another welcome credit
Issuance query Create welcome credit if this order has no welcome credit Whether the account already used the offer on another order

The reviewer asks product to resolve the ambiguity. In the scenario, product confirms the intended rule: a completed use exhausts the account's welcome benefit; cancellation before payment releases an unused reservation, but completing another order does not earn a new welcome credit. That distinction belongs in the invariant. “Single-use token” was too small a rule.

The race hypothesis loses; the eligibility hypothesis survives

Inspect the authoritative reservation boundary first. Birch uses a conditional transition that binds credit C1 from available to reserved_for=O1. A competing reservation of C1 for O2 cannot commit after that transition. The order and credit account relationships are checked inside the operation. Under those stated transaction assumptions, two workers cannot spend that same credit twice. Preserve this counterevidence.

Now run the sequence on paper without concurrency:

Step Trusted account/order facts Credit state Order amount
1 Account A creates O1, catalogue price $60 Issuer creates C1 for O1 $60 before credit
2 O1 reserves and consumes C1 after qualifying payment C1 consumed; account has used the welcome offer $40 paid plus $20 credit
3 Account A later creates O2, catalogue price $60 Issuer checks O2 only and creates C2 $60 before credit
4 O2 reserves and consumes C2 C2 consumed; no duplicate use of C1 $40 paid plus another $20 credit

Every individual credit is used once. Every price is recomputed by the server. The policy still fails because eligibility is scoped to an order instead of the account's lifetime offer use. The second $20 benefit is outside the agreed policy. The table demonstrates a synthetic outcome of the supplied rules; it does not establish production loss or that an actual customer used this path.

This is why a review that stops at “use a transaction” can miss a business-logic defect. Atomicity protects the invariant you actually encode. It does not choose the right business identity for that invariant.

Put the evidence beside the invariant

Invariant Decisive evidence in this case Disposition
Customer cannot choose the catalogue price Server recomputes $60 and verifies the expected remainder Reject client-price claim for the traced path
C1 cannot discount two orders Conditional reservation binds one order and account Reject the proposed C1 race under the stated storage semantics
A completed welcome benefit cannot be issued again to account A Issuance checks order ID, while policy is account-scoped Source-supported eligibility candidate
Browser return cannot mark an order paid Provider callback route is outside this supplied evidence packet Separate follow-up; no claim made

A single broad “checkout insecure” label would lose the value of all four rows. The staff engineer should carry forward the rejected claims as well as the surviving one so the next reviewer does not repeat the same work.

Repair eligibility without breaking failed checkout recovery

A proposed design gives the welcome offer its own durable identity, (account, offer), and explicit states such as eligible, reserved for order, and consumed. Issuance and reservation must enforce that identity across every entry point, including support adjustments. The design must still decide how an abandoned or failed order releases a reservation. Simply marking the account consumed when checkout starts could deny legitimate customers their benefit after a failed payment.

The acceptance sequence therefore includes three branches. A completed O1 prevents a second welcome benefit on O2. A terminally failed O1 releases its unconsumed reservation according to policy so a later eligible order can proceed. A local timeout alone is not terminal payment failure: reconcile the outstanding provider attempt before making the same benefit spendable elsewhere. Two concurrent orders cannot each reserve the same account-level entitlement. The product owner supplies the cancellation rule; the storage owner identifies the transaction or uniqueness boundary that enforces it. Do not copy these state names without checking the actual ledger model.

The resulting handoff is narrow:

At illustrative BIRCH-19, welcome-credit issuance is unique per order, while the approved offer is usable once per account. A sequential second order can receive a second credit even though reservation of each individual credit is atomic. Review the deployment and every issuance path, then enforce account/offer eligibility while preserving the documented failed-payment recovery path.

There is no invented annual loss estimate. The supported effect is one additional benefit in the specified sequence. Scale, reachability and deployment remain evidence to collect. Crash recovery and late payment events belong in payment-state reconciliation; this review's contribution is finding the business rule that the existing synchronization does not protect.

Review support adjustments against the same offer identity

Birch's normal checkout can enforce (account, offer) correctly while a support tool issues replacement credits under a different rule. Inventory every operation that creates, restores, transfers, or cancels the welcome benefit. Each may have legitimate business authority, but it must be explicit whether it can override the once-per-account policy.

For example, support may compensate a customer for a failed service experience with a separate goodwill credit. That is different from resetting the welcome-offer state to eligible. Give the adjustment its own purpose and authorization so the account's history remains interpretable. Otherwise a repair that blocks ordinary duplicate issuance can be bypassed through an ambiguous administrative convenience.

Also distinguish a refund of paid money from restoration of promotional eligibility. The product may return the customer's cash while keeping the welcome benefit consumed, or may deliberately restore it under specified conditions. The ledger must represent the chosen rule. A generic “undo order” function should not decide it accidentally by reversing every row without considering the offer policy.

The review artifact can remain small: list the normal issuance path, cancellation release, payment reconciliation, refund, and support adjustment, then record the effect each has on the account/offer state. A missing administrative path becomes a bounded follow-up rather than a reason to call the entire checkout unreviewed.

At closure, verify that the surviving eligibility finding is repaired across these relevant writers. Preserve the earlier counterevidence that each individual credit reservation was already atomic. The fix adds the correct business identity; it should not be described as solving a race that the evidence had rejected.

There is a related but different question: can someone automate an otherwise permitted sensitive flow so heavily that it harms the business? OWASP's API6:2023 covers that pattern. It does not mean every high-volume purchase is a flaw; the business owner must define the harmful threshold. For budget and frequency controls on expensive operations, continue with API rate-limit review. For a closer look at single-use decisions and retries, use replay protection on one-time actions.