Auditing payment state machine and idempotency: value must not move faster than settlement
A checkout is a state machine, and money moves through it in a strict order: an order is placed, a charge is
authorized, the charge settles, and only then are goods fulfilled; refunds and cancels run the sequence
backward. The bugs are transitions that break the order or repeat for value. An order marked paid before the
charge is actually confirmed lets fulfillment run against a payment that never settles. A step that can be
skipped, so fulfillment triggers without a completed charge, or repeated, so one payment yields multiple
fulfillments, releases value the sequence did not authorize. A charge or fulfillment endpoint that is not
idempotent double-processes when a request is retried or deliberately replayed, so a network retry or an
attacker's repeat becomes two charges or two shipments. And a refund or cancel that returns value while the
charge stays captured pays out twice. The audit walks the state machine and tries to move value ahead of, or
more than once per, settlement. You audit this by driving transitions out of order and replaying the ones that
release value.
When to use
- A purchase moves through ordered payment states (placed, authorized, settled, fulfilled, refunded).
- Fulfillment may trigger before a charge is confirmed, or a paid state may be set ahead of settlement.
- Charge, fulfillment, or refund endpoints may not be idempotent under retry or replay.
Scope check
Test payment flows only against systems you own or are authorized to assess, on non-production or sandbox
payment paths. Driving transitions and replays moves real value on a live system, so use sandbox payment
credentials and test accounts and never trigger a real charge, refund, or fulfillment that is not yours. If you
can't name the authorization, stop.
The loop
Establish the intended state machine first. Map the legal states and the only transitions allowed between
them, and which transition each value-releasing action (fulfillment, refund, payout) requires as its
precondition. This is the false-positive killer: a flow where fulfillment requires a confirmed settled charge,
every transition validates its precondition server-side, and every value-releasing handler is idempotent is
correct. Name the intended sequence, then test deviations.
Check the paid-state precondition. Confirm the state that gates fulfillment is set only after the charge
is confirmed settled by the payment provider, not on client assertion or on authorization alone. An order
marked paid before settlement, or on a client-supplied status, lets goods ship against a payment that may
never complete.
Test step skipping and out-of-order transitions. Attempt to reach a value-releasing state without the
transitions that should precede it: trigger fulfillment without a completed charge, jump past a verification
step, or set a later state directly. Confirm each transition validates its precondition on the server and
refuses an out-of-order jump. A state reachable without its prerequisites is a skip.
Test replay and idempotency of value-releasing handlers. Replay the charge, fulfillment, and payout
requests, and retry them with and without any idempotency key. Confirm each is idempotent: a repeated request
for the same logical operation processes once, not once per delivery. A non-idempotent handler turns a retry
or a deliberate replay into a double charge or double fulfillment.
Check refund and cancel symmetry. Confirm a refund or cancel only returns value that was actually
captured, moves the state consistently, and cannot be repeated to pay out more than once or run while the
charge remains captured. A refund that credits without reversing the charge, or that replays, is a payout
primitive.
Confirm and record. Confirm by releasing value without settlement, a fulfillment on an unconfirmed
charge, a skipped step reaching a paid state, a replayed handler double-processing, or a repeated refund, all
on sandbox payment paths and without moving real money. Kill the lead if every value-releasing action requires
its settled precondition validated server-side and every such handler is idempotent under retry and replay.
Record the out-of-order or replayed transition, the value-released sink, and the skippable step or
non-idempotent handler.
Where payment state trust leaks
- Paid set before settlement. Marking an order paid on authorization alone or on client-supplied status
ships goods against a charge that may never settle.
- A skippable step reaches fulfillment. A value-releasing state reachable without its preceding transitions
lets fulfillment run without a completed charge.
- A non-idempotent handler double-processes. A charge, fulfillment, or payout endpoint that acts once per
request rather than once per operation doubles on retry or replay.
- A repeatable refund pays out twice. A refund or cancel that can be replayed, or that credits without
reversing the capture, returns more value than was taken.
- Client-driven state transitions. Trusting a client to assert the next state, rather than deriving it from
server-confirmed events, lets an attacker set the state that releases value.
Worked example (a confirm and a kill)
Confirm. A checkout marks the order paid and triggers fulfillment when the browser posts back a success
status after redirect from the payment page, before the server confirms settlement with the provider. Posting
the success callback directly, without any real payment, drives the order to paid and ships the goods.
Separately, replaying the fulfillment request produces a second shipment for one order. Confirmed payment
state bypass via client-asserted paid state and non-idempotent fulfillment, critical, remediation = set the
paid state only from a server-side confirmation of settlement with the provider, require the settled
precondition before fulfillment, and make fulfillment idempotent per order.
Kill. The paid state is set only after the server confirms settlement with the payment provider out of
band; fulfillment, charge, and payout each validate their settled precondition server-side and refuse an
out-of-order jump; every value-releasing handler is idempotent per logical operation under retry and replay;
and refunds return only captured value and cannot repeat. A skipped step or replayed request releases nothing
extra. Killed, kill_reason = "value-releasing transitions gated on server-confirmed settlement and
idempotent under replay; no out-of-order or repeated transition releases value."
Rationalizations to reject
- "The payment page redirected back with success." → A client-delivered success is spoofable; confirm
settlement server-side with the provider before setting paid.
- "The charge was authorized." → Authorization is not settlement; gate fulfillment on the confirmed settled
state, not on a hold that can be released.
- "Retries are just network noise." → A non-idempotent handler turns any retry, benign or malicious, into a
double charge or double shipment; make it idempotent per operation.
- "Refunds go through our finance team." → If the refund endpoint can be replayed or credits without reversing
the capture, it is a payout primitive regardless of who normally calls it.
- "The client tracks the order state." → Client-asserted state lets an attacker set the value-releasing state;
derive every transition from server-confirmed events.
Executing this in practice
You need the legal states and transitions, which server-confirmed event gates each value-releasing action, and
whether each such handler is idempotent. On sandbox payment paths, try to mark paid without settlement, skip to
fulfillment, replay the charge and fulfillment and refund handlers, and repeat a refund. Reading the state-
transition code shows the intended order; a value release without settlement or a doubled handler shows whether
it holds.
Related
auditing-payment-callback-and-amount-integrity - the provider callback that should set the paid state; its
authenticity and amount checks are the trusted signal this state machine must wait for.
hunting-price-and-coupon-manipulation - manipulating the amount before this state machine runs is the paired
attack; together they cover paying less and paying nothing.
auditing-webhook-authenticity-and-callback-trust - settlement usually arrives as a webhook; that skill
verifies the event this flow depends on is genuine.
hunting-broken-object-level-authorization - driving another user's order through these transitions is an
object-authorization failure on the order; the two meet at the order identifier.
- FINDING-SCHEMA.md - source = the out-of-order or replayed transition, sink = the
value released without settlement, evidence = the skippable step or non-idempotent handler.
1---2name: auditing-payment-state-machine-and-idempotency3description: Audit payment and checkout state machines for transitions an attacker can drive out of order or replay for value: an order marked paid before the charge is confirmed, a step that can be skipped or repeated so goods ship without settlement, a non-idempotent charge or fulfillment endpoint that double-processes on a retried or replayed request, and a refund or cancel that returns value while the underlying charge stays captured. Covers checkout, charge, fulfillment, and refund flows where money and goods change hands across a sequence of state transitions. Use when a purchase moves through ordered payment states and the transitions and their idempotency are the boundary. The out-of-order or replayed transition is the source, the value released without settlement is the sink, and the skippable step or non-idempotent handler that allows it is the bug.4license: MIT5---67# Auditing payment state machine and idempotency: value must not move faster than settlement89A checkout is a state machine, and money moves through it in a strict order: an order is placed, a charge is10authorized, the charge settles, and only then are goods fulfilled; refunds and cancels run the sequence11backward. The bugs are transitions that break the order or repeat for value. An order marked paid before the12charge is actually confirmed lets fulfillment run against a payment that never settles. A step that can be13skipped, so fulfillment triggers without a completed charge, or repeated, so one payment yields multiple14fulfillments, releases value the sequence did not authorize. A charge or fulfillment endpoint that is not15idempotent double-processes when a request is retried or deliberately replayed, so a network retry or an16attacker's repeat becomes two charges or two shipments. And a refund or cancel that returns value while the17charge stays captured pays out twice. The audit walks the state machine and tries to move value ahead of, or18more than once per, settlement. You audit this by driving transitions out of order and replaying the ones that19release value.2021## When to use2223- A purchase moves through ordered payment states (placed, authorized, settled, fulfilled, refunded).24- Fulfillment may trigger before a charge is confirmed, or a paid state may be set ahead of settlement.25- Charge, fulfillment, or refund endpoints may not be idempotent under retry or replay.2627## Scope check2829Test payment flows only against systems you own or are authorized to assess, on non-production or sandbox30payment paths. Driving transitions and replays moves real value on a live system, so use sandbox payment31credentials and test accounts and never trigger a real charge, refund, or fulfillment that is not yours. If you32can't name the authorization, stop.3334## The loop35361. **Establish the intended state machine first.** Map the legal states and the only transitions allowed between37 them, and which transition each value-releasing action (fulfillment, refund, payout) requires as its38 precondition. This is the false-positive killer: a flow where fulfillment requires a confirmed settled charge,39 every transition validates its precondition server-side, and every value-releasing handler is idempotent is40 correct. Name the intended sequence, then test deviations.41422. **Check the paid-state precondition.** Confirm the state that gates fulfillment is set only after the charge43 is confirmed settled by the payment provider, not on client assertion or on authorization alone. An order44 marked paid before settlement, or on a client-supplied status, lets goods ship against a payment that may45 never complete.46473. **Test step skipping and out-of-order transitions.** Attempt to reach a value-releasing state without the48 transitions that should precede it: trigger fulfillment without a completed charge, jump past a verification49 step, or set a later state directly. Confirm each transition validates its precondition on the server and50 refuses an out-of-order jump. A state reachable without its prerequisites is a skip.51524. **Test replay and idempotency of value-releasing handlers.** Replay the charge, fulfillment, and payout53 requests, and retry them with and without any idempotency key. Confirm each is idempotent: a repeated request54 for the same logical operation processes once, not once per delivery. A non-idempotent handler turns a retry55 or a deliberate replay into a double charge or double fulfillment.56575. **Check refund and cancel symmetry.** Confirm a refund or cancel only returns value that was actually58 captured, moves the state consistently, and cannot be repeated to pay out more than once or run while the59 charge remains captured. A refund that credits without reversing the charge, or that replays, is a payout60 primitive.61626. **Confirm and record.** Confirm by releasing value without settlement, a fulfillment on an unconfirmed63 charge, a skipped step reaching a paid state, a replayed handler double-processing, or a repeated refund, all64 on sandbox payment paths and without moving real money. Kill the lead if every value-releasing action requires65 its settled precondition validated server-side and every such handler is idempotent under retry and replay.66 Record the out-of-order or replayed transition, the value-released sink, and the skippable step or67 non-idempotent handler.6869## Where payment state trust leaks7071- **Paid set before settlement.** Marking an order paid on authorization alone or on client-supplied status72 ships goods against a charge that may never settle.73- **A skippable step reaches fulfillment.** A value-releasing state reachable without its preceding transitions74 lets fulfillment run without a completed charge.75- **A non-idempotent handler double-processes.** A charge, fulfillment, or payout endpoint that acts once per76 request rather than once per operation doubles on retry or replay.77- **A repeatable refund pays out twice.** A refund or cancel that can be replayed, or that credits without78 reversing the capture, returns more value than was taken.79- **Client-driven state transitions.** Trusting a client to assert the next state, rather than deriving it from80 server-confirmed events, lets an attacker set the state that releases value.8182## Worked example (a confirm and a kill)8384> **Confirm.** A checkout marks the order paid and triggers fulfillment when the browser posts back a success85> status after redirect from the payment page, before the server confirms settlement with the provider. Posting86> the success callback directly, without any real payment, drives the order to paid and ships the goods.87> Separately, replaying the fulfillment request produces a second shipment for one order. **Confirmed** payment88> state bypass via client-asserted paid state and non-idempotent fulfillment, `critical`, remediation = set the89> paid state only from a server-side confirmation of settlement with the provider, require the settled90> precondition before fulfillment, and make fulfillment idempotent per order.91>92> **Kill.** The paid state is set only after the server confirms settlement with the payment provider out of93> band; fulfillment, charge, and payout each validate their settled precondition server-side and refuse an94> out-of-order jump; every value-releasing handler is idempotent per logical operation under retry and replay;95> and refunds return only captured value and cannot repeat. A skipped step or replayed request releases nothing96> extra. **Killed**, `kill_reason` = "value-releasing transitions gated on server-confirmed settlement and97> idempotent under replay; no out-of-order or repeated transition releases value."9899## Rationalizations to reject100101- *"The payment page redirected back with success."* → A client-delivered success is spoofable; confirm102 settlement server-side with the provider before setting paid.103- *"The charge was authorized."* → Authorization is not settlement; gate fulfillment on the confirmed settled104 state, not on a hold that can be released.105- *"Retries are just network noise."* → A non-idempotent handler turns any retry, benign or malicious, into a106 double charge or double shipment; make it idempotent per operation.107- *"Refunds go through our finance team."* → If the refund endpoint can be replayed or credits without reversing108 the capture, it is a payout primitive regardless of who normally calls it.109- *"The client tracks the order state."* → Client-asserted state lets an attacker set the value-releasing state;110 derive every transition from server-confirmed events.111112## Executing this in practice113114You need the legal states and transitions, which server-confirmed event gates each value-releasing action, and115whether each such handler is idempotent. On sandbox payment paths, try to mark paid without settlement, skip to116fulfillment, replay the charge and fulfillment and refund handlers, and repeat a refund. Reading the state-117transition code shows the intended order; a value release without settlement or a doubled handler shows whether118it holds.119120## Related121122- `auditing-payment-callback-and-amount-integrity` - the provider callback that should set the paid state; its123 authenticity and amount checks are the trusted signal this state machine must wait for.124- `hunting-price-and-coupon-manipulation` - manipulating the amount before this state machine runs is the paired125 attack; together they cover paying less and paying nothing.126- `auditing-webhook-authenticity-and-callback-trust` - settlement usually arrives as a webhook; that skill127 verifies the event this flow depends on is genuine.128- `hunting-broken-object-level-authorization` - driving another user's order through these transitions is an129 object-authorization failure on the order; the two meet at the order identifier.130- [FINDING-SCHEMA.md](../../FINDING-SCHEMA.md) - source = the out-of-order or replayed transition, sink = the131 value released without settlement, evidence = the skippable step or non-idempotent handler.