Payment processing uses a PCI-compliant provider
Why it matters
Collecting raw card numbers, CVV, or expiry dates in your own form and submitting them to your server puts you directly in scope for PCI DSS compliance (pci-dss:4.0 Req 4.2.1, Req 6.4.3). That compliance burden means annual audits, penetration tests, and strict infrastructure controls — or, if you skip it, catastrophic liability when a breach occurs. OWASP A05 (Security Misconfiguration) covers exactly this failure mode: trusting that a payment provider SDK is installed is not the same as using it. Every payment flow that touches raw card data on your server is an exposure vector.
Severity rationale
Critical because raw card data transiting your server creates direct PCI DSS scope and breach liability with no mitigating controls.
Remediation
Replace any custom card input fields with your payment provider's hosted element. Card data must go directly to the provider's domain — your server should only ever receive opaque tokens or session IDs.
For Stripe, swap the custom form for PaymentElement:
import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js'
// Card data is captured by Stripe's iframe — never touches your server
// Your API receives only the paymentIntentId returned by stripe.confirmPayment()
Verify the fix by opening your browser's network tab during checkout: card field submissions should go to api.stripe.com, not your own domain.
Detection
-
ID:
pci-compliant-provider -
Severity:
critical -
What to look for: Examine how payment information is collected and processed. Look for Stripe.js / Stripe Elements / Stripe Checkout, PaymentElement, Paddle.js, LemonSqueezy embedded checkout, Braintree hosted fields, or similar SDK-based payment collection. The critical signal is whether raw card data ever passes through your server. Check all form components, API routes, and checkout flows for direct card number fields (
type="tel"ortype="number"inputs namedcard,cardNumber,cvv,cvc,expiry) that are submitted to your own backend. Beyond package presence, verify that Stripe Elements or Checkout is actually imported and used: look for imports ofElements,CardElement, orPaymentElementfrom@stripe/react-stripe-js, or server-side calls tostripe.checkout.sessions.create()/redirectToCheckoutin API routes. -
Pass criteria: Count every payment form and checkout flow in the codebase. Payment collection uses an SDK-hosted element (Stripe Elements, Stripe Checkout, Paddle overlay, LemonSqueezy checkout) that handles card data entirely on the provider's side, AND there is evidence of actual usage — imports of
Elements,CardElement, orPaymentElementfrom@stripe/react-stripe-js, or server-side calls tostripe.checkout.sessions.create()orredirectToCheckoutin API routes. Your server never receives raw card data — only payment intent IDs, session IDs, or tokens. At least 1 import of Elements, CardElement, or PaymentElement must be present. Having@stripe/stripe-jsinpackage.jsonalone is not sufficient to pass. -
Fail criteria: Your application collects raw card numbers, CVV, or expiry dates in a form that submits to your own server, even if you then forward to a payment provider. Any server-side handling of raw card data fails this check. Do NOT pass if a hosted element is present alongside a custom card form — the custom form is the vulnerability.
-
Skip (N/A) when: No payment functionality detected in this project. Signal: no payment provider SDK in
package.jsonand no billing-related API routes. -
Cross-reference: For API security beyond payment handling, the API Security audit covers authentication, authorization, and input validation across all endpoints.
-
Detail on fail: Describe what was found. Example:
"Custom payment form in components/checkout/CardForm.tsx collects card number and CVV fields submitted to /api/billing/charge — raw card data passes through application server" -
Remediation: Handling raw card data yourself requires PCI DSS compliance, which is a significant ongoing audit burden. Use your payment provider's hosted elements instead:
For Stripe, replace custom card inputs with Stripe Elements or Stripe Checkout:
// Use Stripe's hosted PaymentElement — card data never touches your server import { PaymentElement } from '@stripe/react-stripe-js' // Your server only receives a paymentIntentId, never card dataFor other providers, use their equivalent hosted checkout or embedded payment widgets. Verify by checking your network tab during checkout — you should see card data going directly to the provider's domain (stripe.com, paddle.com, etc.), not to your own API.
External references
- pci-dss:4.0 · Req 4.2.1 — Strong cryptography used to safeguard PAN during transmission
- pci-dss:4.0 · Req 6.4.3 — All payment page scripts managed and authorized
- owasp:2021 · A05 — Security Misconfiguration
- cwe · CWE-311 — Missing Encryption of Sensitive Data
Taxons
History
- 2026-04-18·v1.0.0·Initial import from saas-billing·automated