No credit card data stored in application database
Why it matters
Storing raw card numbers, CVV, or full PANs in your database is a direct violation of PCI DSS Req 3.3.1 and Req 3.5.1 (pci-dss:4.0), classified under OWASP A02 (Cryptographic Failures) and CWE-312 (Cleartext Storage of Sensitive Information). A single database breach exposes every stored card to attackers. Payment providers deliberately design their systems so your application never needs to hold this data — storing it anyway buys you nothing except unlimited liability.
Severity rationale
Critical because any stored raw card data is immediately exploitable in a breach and constitutes a per-se PCI DSS violation regardless of encryption.
Remediation
Remove all raw card columns from your schema and replace them with provider-issued opaque identifiers. If you need to display card details to users (last 4 digits, brand), fetch that information from the provider's API at render time.
// prisma/schema.prisma — replace card_number / cvv columns with:
model User {
stripe_customer_id String? @unique
stripe_default_payment_method String?
// card_number String <-- delete this
// cvv String <-- delete this
}
To show "Visa ending in 4242" on the billing page, call stripe.paymentMethods.retrieve(pm.id) and read pm.card.last4 and pm.card.brand — never cache those values to your own database.
Detection
-
ID:
no-cc-data-stored -
Severity:
critical -
What to look for: Search database schema files and migration files for fields that could contain raw card data. Look for column names like
card_number,card_num,cc_number,cvv,cvc,card_cvv,expiry,expiration_date,pan,full_card. Also examine model definitions and any ORM query code that might write raw card strings to the database. Check API route handlers that process payment-related POST requests for anydb.insertor similar calls that include card-like data. Examine any logging code near payment flows for accidental card data capture. -
Pass criteria: Enumerate all database schema columns related to payments — at least 0 PCI-sensitive columns expected. 0 columns contain raw card data. The schema may legitimately store provider-side tokens (
stripe_customer_id,stripe_payment_method_id,stripe_subscription_id) — these are fine. Only raw card numbers, CVV, or magnetic stripe data fail this check. -
Fail criteria: Database schema or migration files contain columns for raw card data, OR API route code stores raw card-like strings to the database.
-
Skip (N/A) when: No database detected (no ORM or database dependency in
package.json, no schema files). -
Detail on fail:
"prisma/schema.prisma contains a 'card_number' field on the PaymentMethod model"or"API route stores raw card data to users table: found INSERT with 'card_number' field" -
Remediation: Storing raw card data is a PCI DSS violation and creates catastrophic breach liability. Use your payment provider's customer and payment method objects instead:
// Store only the provider's opaque identifiers await db.user.update({ where: { id: userId }, data: { stripe_customer_id: customer.id, // OK stripe_default_payment_method: pm.id, // OK // card_number: '4111...' — NEVER do this } })If you need to display partial card info to users (last 4 digits, card brand), retrieve it from the provider's API at display time rather than storing it yourself.
External references
- pci-dss:4.0 · Req 3.3.1 — SAD must not be retained after authorization
- pci-dss:4.0 · Req 3.5.1 — PAN must be rendered unreadable anywhere it is stored
- cwe · CWE-312 — Cleartext Storage of Sensitive Information
- owasp:2021 · A02 — Cryptographic Failures
Taxons
History
- 2026-04-18·v1.0.0·Initial import from saas-billing·automated