VNX-837: Improper Enforcement of a Single, Unique Action
Detects Stripe calls that move money (charges, payment intents, refunds, transfers, payouts, top-ups) made without an idempotency key, so a retried or double-submitted request performs the action twice. Python and JavaScript/TypeScript.
| Field | Value |
|---|---|
| Rule ID | VNX-837 |
| Severity | Low |
| SARIF level | note |
| Kind | sast |
| Languages | JavaScript / TypeScript, Python |
| CWE | CWE-837 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:L/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:L/DI:H/EX:L/EC:N/P:L |
| Tags | improper-enforcement-of-single-unique-action idempotency payments cwe-837 |
| Source | internal/sast/rules/vnx-837.rego |
Overview
VNX-837 maps to CWE-837: Improper Enforcement of a Single, Unique Action. A payment must happen exactly once per order, but HTTP requests are retried by clients, proxies, job queues and impatient users clicking “Pay” twice. Stripe’s API makes a create call happen once only when it carries an idempotency key; without one, every retry is a new charge, refund or payout.
The rule reports stripe.Charge/PaymentIntent/Refund/Transfer/Payout/Topup.create(...) and the StripeClient charges/payment_intents/refunds/transfers/payouts services in Python, and stripe.charges/paymentIntents/refunds/transfers/payouts/topups.create(...) in JavaScript/TypeScript, when neither the call’s arguments nor the ten lines above it name an idempotency key.
What Gets Flagged
return stripe.Charge.create(amount=order.total_cents, currency="usd", source=token) # FLAGGED
const intent = await stripe.paymentIntents.create({ amount: order.total, currency: 'eur' }); // FLAGGED
What Is Not Flagged
stripe.Charge.create(..., idempotency_key=f"order-{order.id}")andstripe.paymentIntents.create(params, { idempotencyKey }), including an options object built just above the call.- Read calls (
retrieve,list) and creates that do not move money (customers,products). - Test code, and the call in comments or string literals.
Other single-use actions (coupon redemption, voting) have no recognisable API and are not judged by this rule.
Remediation
Derive a key from the business identity of the action (the order, refund request or payout id) and pass it on every money-moving call, so a retry of the same action is recognised and returns the original result:
stripe.Charge.create(amount=order.total_cents, currency="usd", source=token,
idempotency_key=f"order-{order.id}")
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-837
// nosec vnx-837
Or record an ignore rule: vulnetix ignore add --rule vnx-837 --reason "...". See Suppressing findings.