This weakness occurs when software performs the same operation on a resource multiple times when it should only happen once. Common examples include charging a…
This weakness occurs when software performs the same operation on a resource multiple times when it should only happen once. Common examples include charging a payment twice, applying a discount code twice, or incrementing a counter multiple times for a single user action. The result is data corruption, financial loss, or inconsistent application state.
02How It Happens
Duplicate operations typically arise from missing or inadequate idempotency controls. When a user action (like submitting a form or clicking a button) triggers a backend operation, the software may lack a mechanism to detect and prevent re-execution if the request is retried, resubmitted, or processed by multiple threads simultaneously. This is especially common in asynchronous workflows, distributed systems, or when network timeouts cause clients to retry requests without the server knowing whether the original operation completed.
The root cause is usually one of three patterns: no unique identifier tracking which operations have already been performed, no locking or synchronization to prevent concurrent execution of the same operation, or no validation that the operation hasn't already been applied to the target resource.
03Real-World Impact
Duplicate operations can have serious consequences depending on the resource involved. In payment processing, a user might be charged twice for a single purchase. In e-commerce, a coupon or discount code might be applied multiple times, inflating refunds or reducing revenue. In account management, duplicate operations could create multiple records, corrupt balances, or trigger unintended side effects like sending duplicate notifications or creating duplicate audit log entries. The financial and reputational damage depends on the operation's nature and how long the duplication goes undetected.
04Vulnerable & Fixed Patterns
Vulnerable pattern
def apply_discount(user_id, coupon_code, amount):
user = get_user(user_id)
discount_value = validate_coupon(coupon_code)
# No check for whether this coupon was already applied
user.balance -= discount_value
user.save()
return {"status": "success", "new_balance": user.balance}
Why it's vulnerable: If the same request is retried (due to network timeout or user double-click), the discount is applied again without checking whether it was already applied to this user.
Fixed pattern
def apply_discount(user_id, coupon_code, amount, request_id):
user = get_user(user_id)
# Check if this operation was already performed using a unique request ID
if Operation.objects.filter(user_id=user_id, request_id=request_id).exists():
return {"status": "already_applied", "new_balance": user.balance}
discount_value = validate_coupon(coupon_code)
user.balance -= discount_value
user.save()
# Record that this operation was performed
Operation.objects.create(user_id=user_id, request_id=request_id, operation_type="discount")
return {"status": "success", "new_balance": user.balance}
Vulnerable pattern
function process_payment($user_id, $amount) {
$user = get_user($user_id);
// No idempotency check
$result = charge_payment_gateway($user->payment_method, $amount);
if ($result['success']) {
$user->balance -= $amount;
update_user($user);
}
return $result;
}
Why it's vulnerable: If the payment gateway confirms the charge but the response is lost due to a network error, a retry will charge the user again.
Fixed pattern
function process_payment($user_id, $amount, $request_id) {
$user = get_user($user_id);
// Check if this payment was already processed
$existing = get_payment_record($user_id, $request_id);
if ($existing) {
return ['success' => true, 'message' => 'Payment already processed', 'transaction_id' => $existing['transaction_id']];
}
$result = charge_payment_gateway($user->payment_method, $amount);
if ($result['success']) {
$user->balance -= $amount;
update_user($user);
// Record this payment with the unique request ID
record_payment($user_id, $request_id, $result['transaction_id']);
}
return $result;
}
05Prevention Checklist
Implement idempotency keys: Require clients to provide a unique identifier (UUID or request ID) for each operation; check whether that ID has already been processed before executing the operation.
Use database constraints: Add unique constraints on combinations of user ID and operation identifier to prevent duplicate inserts at the database level.
Add state tracking: Maintain a record of completed operations (transaction logs, audit tables) so you can detect and reject retries.
Implement locking: Use database locks or distributed locks (Redis, etc.) to ensure only one instance of an operation executes at a time for a given resource.
Design for idempotency: Structure operations so that re-executing them with the same inputs produces the same result without side effects (e.g., "set balance to X" instead of "subtract X from balance").
Test retry scenarios: Simulate network failures and client retries in your test suite to catch duplicate-operation bugs before production.
06Signs You May Already Be Affected
Review transaction logs and audit trails for duplicate entries with identical timestamps or request parameters. Check payment records for duplicate charges on the same account within short time windows. Look for users reporting unexpected balance changes, duplicate charges, or coupons applied multiple times. Monitor application logs for repeated operations with the same input data occurring in quick succession.