The browser should never decide what you charge
The concrete launch risk: your checkout takes a price from the page and uses that value to create the charge. A generated flow often reads the amount from a hidden field, a JSON request body, or a JavaScript variable, then sends it to your server, which forwards it straight into the payment intent. If the number that controls the charge comes from the browser, the customer controls the charge. They can open DevTools, change 4900 to 1, and pay one cent for a product priced at forty-nine dollars.
This is client side price manipulation, and it maps to the OWASP category of broken object-level and business-logic authorization combined with trusting client-supplied input (CWE-602, reliance on untrusted inputs in a security decision). The user is authenticated correctly. The failure is that the server treats a browser-supplied amount as authoritative instead of looking the price up itself.
Separate what is observable from what is only possible. What you can observe from outside: whether the checkout request carries an amount field that the client sets. That alone is a weak signal, not proof. What is only confirmed by reading the server code: whether the server ignores that field and recomputes the price from a trusted catalog. A request that sends an amount is suspicious; only the server logic tells you if it is exploitable.
Why AI-built checkouts land here
A prompt like "add a checkout that charges the cart total with Stripe" produces a working flow fast. The browser already knows the total it is displaying, so the quickest path the model takes is to send that total to the backend and pass it to the payment intent. The app works on the first try, the test card succeeds, and the flow looks finished.
The step that does not happen automatically is the trust boundary. Nothing in a green test run reveals that the amount is attacker-controlled, because the developer testing it never edits the request. The price displayed and the price charged match in every honest run.
- The displayed total and the request body agree, so the missing server lookup is invisible from the inside.
- The payment succeeds with a real test card, which makes the flow read as correct.
- Fast deploy means the public checkout exists before anyone asks where the authoritative price lives.
- Stripe will happily create a payment intent for whatever amount you pass; it does not know your catalog price, so it cannot catch the substitution for you.
Test it on your own checkout
You can confirm this on your own app without attacking anyone. Open your checkout, then open DevTools and the Network tab. Begin a purchase and watch the request that creates the payment, the one that hits Stripe or your own create-payment-intent endpoint.
Do this only against your own project in test mode with test keys, never against a live store or a third party. If you are unsure whether your keys are test or live, that is its own pre-launch issue worth checking first.
- Inspect the request body and any hidden form fields. Look for a field that carries the price, for example amount, price, total, or unit_amount, sent from the browser.
- If you see no client-supplied amount and the request only sends an item id or a cart id, that is the safer shape: the server must look the price up from those ids.
- If the request includes the amount, run the safe self-check on your own test order. In test mode, change the amount value to a smaller number using the browser's request-editing or a local intercept, and submit.
- Suspicious result: the payment proceeds and your order or Stripe dashboard records the lowered amount. That means the browser value reached the charge.
- Safer result: the server rejects the request, or it ignores your edited value and charges the correct catalog price anyway. That is the server recomputing from a trusted source.
Look the price up server-side
The fix is a trust boundary, not a tweak. The client may send what the user wants to buy; the server decides what it costs.
Send only identifiers from the browser, such as a product id, price id, or cart id. On the server, load the price for those ids from your own database or from your Stripe Price objects, and build the payment intent from that trusted number. Discard any amount the client sent. If you use Stripe Checkout Sessions with predefined Price objects rather than ad-hoc amounts, Stripe holds the authoritative price for you and the browser never names a figure.
State the limit plainly. Recomputing the price closes the amount-tampering gap, but it does not validate everything else in the order. A user can still request a quantity you did not intend, replay a stale price id, or manipulate currency or shipping if those are taken from the client. Each money-affecting field needs the same treatment: identifier in, authoritative value looked up server-side. And this is authorization and business-logic correctness, separate from confirming the request is authenticated at all.
- Treat product and price ids as the only money-related input you accept from the client.
- Compute totals, quantities, discounts, and tax on the server from trusted records, not from the request body.
- If a coupon or discount applies, validate it server-side too; a client-applied discount is the same gap in a different field.
Where VibeCodeGuard fits, and what it does not prove
VibeCodeGuard's Launch Check works from the public surface, so it can flag a checkout endpoint that accepts a client-supplied amount in its request as a weak signal worth investigating. Seeing an amount field travel from the browser tells you where to look. It does not tell you whether the server trusts that value.
What the scan cannot do is confirm the fix. Verifying that your server ignores the client amount and recomputes the price from a trusted catalog requires reading the server code, which is manual review, not a public-surface scan. The Launch Check does not run a test charge, does not edit your requests, and does not certify your payment flow.
Next step: open your own checkout in test mode, find the request that creates the payment, and confirm the authoritative price is looked up on the server from an id, not taken from the browser. Run a focused Launch Check against the public URL before sharing it, so an exposed client-amount field surfaces in your own review first.
A clean Launch Check does not prove your checkout recomputes prices safely. It reports what an outside visitor can see in the request shape; it cannot read the server logic that decides what you actually charge. Treat a client-supplied amount as a prompt to read the server code, not as a verdict either way.
> launch check
Scan the public surface before launch.
Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.