Back to scan results
What this check probes
- Credit card numbers — extracts every sequence of 13–19 digits (allowing spaces and dashes), then runs the Luhn mod-10 checksum. We additionally check the prefix to filter to known issuer ranges (Visa 4, Mastercard 5/2221–2720, Amex 34/37, Discover 6011/65, JCB, Diners). A Luhn-valid Visa number in your homepage HTML is essentially a guaranteed real card.
- Social Security Numbers — pattern
\d{3}-\d{2}-\d{4} with non-zero area number and group number, common false-positive filtering.
- Email harvesting risk — counts
mailto: links and bare user@domain patterns. Not a hard fail, but an advisory if you have many — these are scraped by spam bots.
- Phone numbers — counted, not failed. Informational only.
The check operates only on the response body of your homepage. We do not crawl or test other URLs.
Why this matters for PCI DSS
This is the most direct PCI compliance signal on the entire scanner. PCI DSS 4.0 Requirement 3.2.1 requires that storage of account data is kept to the minimum necessary. Requirement 3.4.1 requires that the primary account number (PAN) be rendered unreadable anywhere it is stored. Requirement 3.5.1 requires that PAN, when stored, be protected with strong cryptography.
A PAN visible in your homepage HTML is none of those things. It also massively expands your scope: a site that displays even one cardholder data element is in PCI scope as a "Store/Process" environment, requiring the much heavier SAQ D rather than the streamlined SAQ A available to outsourced-only sites.
SSN exposure doesn't fall under PCI directly but typically triggers state breach-notification statutes (e.g., California SB 1386, Massachusetts 201 CMR 17), HIPAA, and GDPR.
How to fix it
Never render full PANs. If you must display a card to a returning customer, show only the last four digits with the rest masked: **** **** **** 4242.
Use a hosted/iframe payment form so card numbers never touch your server. The major options:
- Stripe Elements / Stripe Checkout — renders inputs in a Stripe-hosted iframe; you only see a token.
- Square Web Payments SDK — same pattern, Square iframe.
- Braintree Hosted Fields — same.
- Adyen Drop-in / Components — same.
- PayPal Buttons — full redirect or popup; card never touches your domain.
This pattern reduces your PCI scope from SAQ D (200+ controls) to SAQ A (~20 controls) — a massive simplification.
If you have a card number in your homepage source, it almost certainly came from one of:
- Test data in a placeholder that didn't get sanitized.
- A copy-paste from a customer support ticket into a CMS.
- A debug log that got rendered into the page.
- A receipt template that wasn't scoped to logged-in users.
Find and remove the source. Then assume the number was harvested by web crawlers and tell the cardholder to consider their card compromised.
Email harvesting — replace mailto: links with a contact form. If you must publish an email, use an image, JS-obfuscated DOM insertion, or a bot-resistant form like Cloudflare Email Address Obfuscation.