Primary account numbers do not stay where you put them. Learn how to define CHD and SAD precisely, find stray PANs in logs, tickets, and databases, shrink your PCI scope with detection-plus-masking, and operationalize the quarterly discovery scans assessors expect.
Explore the GuidePCI DSS — now at version 4.0.1, mandatory since March 2024 with the remaining future-dated requirements enforceable from March 31, 2025 — protects a narrowly defined asset: account data. Account data splits into two classes with very different rules. Cardholder data (CHD) is the primary account number (PAN) alone, or the PAN together with cardholder name, expiration date, and service code. Sensitive authentication data (SAD) is the security-critical material used to authorize transactions: full track data from the magnetic stripe or chip, the card verification code (CVV2/CVC2/CID), and PINs or PIN blocks.
The distinction drives everything. CHD may be stored if there is a documented business need, but the PAN must be rendered unreadable at rest — via strong cryptography, truncation, tokenization, or one-way hashing — and masked when displayed, showing at most the first six (or eight, under 4.0's expanded BIN rules) and last four digits. SAD, by contrast, must never be stored after authorization, even encrypted, with the sole exception of issuers with a documented justification. A single CVV sitting in a database column after authorization completes is an automatic compliance failure, no matter how well encrypted the column is.
Note what makes discovery hard: the PAN is the pivot element. Cardholder name or expiration date stored without the PAN is not cardholder data at all under the standard. The same name stored next to a PAN becomes CHD requiring full protection. So a discovery program cannot simply grep for names; it must find PANs first, then evaluate what travels with them. That is a contextual judgment — exactly the kind of judgment a transformer-based detection model makes and a regex does not.
Our PII Detection API detects CREDIT_CARD_NUMBER (with Luhn validation and issuer-range awareness to suppress false positives from order IDs and timestamps), CREDIT_CARD_EXPIRATION_DATE, CVV_NUMBER, and PERSON_NAME as distinct entity types, each with character offsets and confidence scores — the raw material for both discovery reports and automated remediation.
This table condenses the PCI DSS account-data matrix and maps each element to the API entity type that finds it. Use the entity list as your entities parameter for payment-focused scans; the full catalog is on the entities page.
| Data element | Class | Storage permitted? | Protection required | API entity type |
|---|---|---|---|---|
| Primary Account Number (PAN) | CHD | Yes, with business need | Render unreadable at rest (Req 3.5); mask on display (Req 3.4) | CREDIT_CARD_NUMBER |
| Cardholder name | CHD (with PAN) | Yes | Protect when stored with PAN | PERSON_NAME |
| Expiration date | CHD (with PAN) | Yes | Protect when stored with PAN | CREDIT_CARD_EXPIRATION_DATE |
| Service code | CHD (with PAN) | Yes | Protect when stored with PAN | Detected in track-data context |
| Full track data (magstripe / chip equivalent) | SAD | No (post-authorization) | Must not be retained; secure deletion on discovery | CREDIT_CARD_NUMBER + adjacent track markers |
| CVV2 / CVC2 / CID | SAD | No (post-authorization) | Must not be retained, even encrypted | CVV_NUMBER |
| PIN / PIN block | SAD | No (post-authorization) | Must not be retained | PASSWORD in PIN context |
| Bank account / IBAN (non-card payments) | Out of PCI scope, in privacy scope | Regulated by GDPR/CCPA/GLBA | Detect and protect under privacy program | FINANCIAL_ACCOUNT_NUMBER, IBAN_CODE, ROUTING_NUMBER |
One subtlety worth internalizing: truncated PANs (first six/eight plus last four) and properly tokenized values are not cardholder data, so systems that hold only those forms can drop out of scope. That is the mechanical basis of every descoping strategy discussed below — replace the PAN with something that is not a PAN, everywhere the full value is not strictly required.
Requirement 3 — "Protect stored account data" — is where discovery becomes an explicit obligation rather than good hygiene. Requirement 3.2.1 demands that account data storage be kept to the minimum necessary, with documented retention periods and quarterly processes to securely delete data exceeding retention. Requirement 3.3 prohibits SAD retention. And critically, PCI DSS 4.0's Requirement 12.5.2 requires entities to document and confirm their PCI DSS scope at least every twelve months — an exercise that the standard explicitly says must include identifying all locations where account data is stored, processed, and transmitted, including where it might have leaked outside the defined cardholder data environment (CDE).
Scope is the economic heart of PCI. Every system that stores, processes, or transmits CHD — plus every system connected to or capable of affecting the security of those systems — is in scope for all applicable requirements: segmentation, hardening, logging, vulnerability management, access control, and assessment. A stray PAN in a marketing database does not just create risk; it drags that database, its host, its admins, and potentially its network segment into your assessment. QSAs have a phrase for the recurring nightmare: "scope creep by data leakage."
The scoping confirmation is therefore a data-discovery exercise in disguise. You must be able to demonstrate, with evidence, that no account data exists outside the CDE — and the only credible evidence is the output of scans that actually looked. Network diagrams and dataflow interviews establish where card data is supposed to be; discovery scans establish where it is. Assessors under 4.0 increasingly ask for both, and for the reconciliation between them.
Detection-based discovery serves both sides of this: sweep the systems believed to be out of scope to prove the negative, and sweep the CDE itself to verify that retention limits and SAD prohibitions hold in practice. Findings feed Requirement 3's quarterly deletion process and Requirement 12's annual scope confirmation from a single pipeline.
Twenty years of forensic breach reports agree: compromised card data is rarely stolen from the payment database. It is harvested from the places nobody thought to protect because nobody knew the PANs were there.
Debug logging of full request bodies, error handlers that dump form payloads, and gateway timeout retries that serialize the transaction all write PANs to disk in plaintext. Log shippers then replicate them to aggregators, index them for search, and archive them to object storage — multiplying one leak into five in-scope systems. Scanning the log stream at ingestion catches the leak at hop one.
Customers paste full card numbers into chat when a payment fails; agents copy them into ticket notes to retry the charge. The helpdesk platform — a SaaS tool far outside your CDE diagram — now stores CHD, and sometimes CVVs, indefinitely. Real-time detection on ticket and chat ingestion, with automatic masking, is the only control that scales with agent behavior.
Free-text columns are the classic offenders: order notes, CRM comments, fraud-review annotations, and imported legacy data. So are analytics replicas and data-lake copies of "sanitized" tables that were sanitized before the leak began. Column-level sampling with entity detection finds PANs in fields whose names give no hint — see our database discovery guide.
Reconciliation spreadsheets exported by finance, chargeback evidence emailed by processors, call-center quality recordings transcribed to text, and screenshots in shared drives. These unmanaged copies are the reason 4.0 scoping language says "including where account data might exist outside the CDE." Document and email scanning brings them into the discovery net.
The cheapest PCI control is the one you no longer need. Every system you can demonstrate holds no account data exits the assessment: no quarterly ASV scans against it, no compensating controls, no sampling during the audit. The descoping playbook has two moves — stop new account data from entering non-CDE systems, and eliminate what already leaked in — and detection powers both.
Move one: inline prevention. Put a detection-and-masking step at the choke points where free text crosses into non-CDE systems: the logging library or log shipper, the ticket-creation webhook, the chat-message pipeline, the ETL jobs feeding the warehouse. Configure the scan for payment entities and use mask_mode to neutralize hits before they are written. A masked value — [CREDIT_CARD_NUMBER] or a last-four-preserving token — is not CHD, so the receiving system stays out of scope by construction. Latency is a single API round-trip, and batching keeps cost negligible; see pricing for volume tiers.
Move two: retrospective cleanup. Sweep historical stores — log archives, ticket backlogs, database columns, mailboxes — and remediate findings: secure deletion for SAD, masking or truncation for PANs where the record must survive, cryptographic protection where full PANs are genuinely required (which almost always means "move it into the CDE instead"). The scan report itself, showing zero residual findings, becomes the assessor-facing evidence that the system is clean.
Tokenization services and point-to-point encryption shrink scope at the payment flow's front door; detection-plus-masking shrinks it everywhere else — the exhaust paths the payment architecture diagrams never show. Mature programs run both, and their PCI assessments cover a fraction of the infrastructure they did before.
hash mode preserves record linkage — the same PAN always yields the same token — so fraud analytics and customer-support dedup keep working on descoped data. Use replace or redact where no linkage is needed; less structure means less residual risk.
PCI's rhythm section: Requirement 3.2.1's quarterly purge of over-retained data, Requirement 12.5.2's annual (or semi-annual) scope confirmation, and 4.0's broader push toward continuous, evidenced processes. Here is a cadence that satisfies all three without heroics.
Detection at ingestion points (logging, ticketing, chat, ETL) runs on every record, every day. This is your preventive control: it keeps the out-of-scope estate clean so periodic sweeps become confirmations rather than cleanups. Alert on every hit — an inline detection firing means an upstream process is emitting account data and needs fixing at the source.
CREDIT_CARD_NUMBER, CVV_NUMBER, CREDIT_CARD_EXPIRATION_DATEEach quarter, sweep the systems permitted to store CHD and reconcile findings against documented retention periods, securely deleting anything past its window (Req 3.2.1). Include a sample-based sweep of adjacent, supposedly-clean systems. Store the scan manifest — systems covered, sample sizes, entity counts, remediations — as the quarter's compliance evidence.
For the 12.5.2 exercise, run a full-estate discovery pass: every database, file share, log archive, mailbox export, and SaaS data export you can enumerate. Reconcile results against the CDE inventory and dataflow diagrams; every out-of-diagram finding either expands the documented scope or gets remediated and rescanned. Service providers repeat this every six months.
Three building blocks: a quick cURL check of a suspect log line, a Python quarterly sweep that walks log files and reports findings with offsets, and a Node.js masking filter for a log shipper. All use the canonical contract documented in the API documentation; get a key on the get-started page.
1. cURL — scan a log excerpt for account data
curl -X POST https://piidetectionapi.com/api/moderate.php \
-H "Content-Type: application/json" \
-d '{
"api_key": "YOUR_API_KEY",
"api_type": "pii_detection",
"text": "2026-08-25 14:02:11 WARN retry payment for J. Alvarez card=4111111111111111 exp=09/27 cvv=123 gateway_timeout",
"entities": ["CREDIT_CARD_NUMBER", "CREDIT_CARD_EXPIRATION_DATE",
"CVV_NUMBER", "PERSON_NAME"],
"mask_mode": "replace",
"threshold": 0.5
}'
The response pinpoints each element with offsets and returns anonymized_text with [CREDIT_CARD_NUMBER], [CVV_NUMBER], and the rest already masked — the CVV hit alone should page someone, because that is SAD at rest.
2. Python — quarterly sweep over log archives
import requests, pathlib, json, datetime
API = "https://piidetectionapi.com/api/moderate.php"
PAYMENT_ENTITIES = ["CREDIT_CARD_NUMBER", "CVV_NUMBER",
"CREDIT_CARD_EXPIRATION_DATE"]
CHUNK = 40_000 # stay under the 50,000-char request limit
def scan_chunk(text):
r = requests.post(API, json={
"api_key": "YOUR_API_KEY",
"api_type": "pii_detection",
"text": text,
"entities": PAYMENT_ENTITIES,
"threshold": 0.6,
}, timeout=30)
r.raise_for_status()
return r.json()["detected_entities"]
findings = []
for path in pathlib.Path("/var/log/archive").rglob("*.log"):
text = path.read_text(errors="ignore")
for off in range(0, len(text), CHUNK):
for e in scan_chunk(text[off:off + CHUNK]):
findings.append({
"file": str(path),
"type": e["type"],
"start": off + e["start"], # absolute offset
"confidence": e["confidence"],
# never persist e["text"] — the finding IS the leak
})
report = {
"scan_date": datetime.date.today().isoformat(),
"files_scanned": len(list(pathlib.Path("/var/log/archive").rglob("*.log"))),
"sad_findings": [f for f in findings if f["type"] == "CVV_NUMBER"],
"chd_findings": [f for f in findings if f["type"] != "CVV_NUMBER"],
}
print(json.dumps(report, indent=2))
3. JavaScript (Node) — masking filter inside a log shipper
// Drop-in transform for a log pipeline (e.g. a Logstash/Fluentd sidecar)
async function maskAccountData(line) {
const res = await fetch("https://piidetectionapi.com/api/moderate.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
api_key: process.env.PII_API_KEY,
api_type: "pii_detection",
text: line,
entities: ["CREDIT_CARD_NUMBER", "CVV_NUMBER",
"CREDIT_CARD_EXPIRATION_DATE"],
mask_mode: "replace"
})
});
const data = await res.json();
if (data.entities_detected > 0) {
// Metric + alert: an upstream service is logging account data
metrics.increment("pci.log_leak", {
types: data.detected_entities.map(e => e.type).join(",")
});
}
return data.anonymized_text; // safe to index and archive
}
// Usage in the shipping loop
const safeLine = await maskAccountData(rawLine);
shipper.emit(safeLine);
For high-volume pipelines, batch lines into single requests up to the 50,000-character limit and scan concurrently; the detection call adds one network hop, and the downstream systems it keeps out of PCI scope repay that many times over. You can validate behavior against your own log samples in the interactive demo before wiring anything up.
Lessons from programs that pass assessments quietly, distilled into three operating rules.
Sixteen consecutive digits are everywhere: order numbers, tracking codes, timestamps, hashes. Pure-regex scanners drown teams in false positives until the reports stop being read. Context-aware detection applies Luhn checks, issuer-range logic, and surrounding-language signals, so a hit means a card number with a stated confidence. Set the threshold higher (0.7+) for automated masking and lower (0.4) for audit sweeps where humans review the borderline cases.
Discovery output that contains the discovered PANs is itself CHD, and now your findings database is in scope. Persist entity types, offsets, file paths, confidence scores, and masked context — never the matched text. When remediation needs to locate the value, the offsets take you there. For environments where even transient transmission is unacceptable, run the detection engine on-premise; the API contract is identical.
Every inline detection hit names an upstream defect: a logger serializing request bodies, a form that lets customers paste card numbers into free text, an integration echoing gateway payloads. Track hits per source and drive them to zero with engineering fixes — masked leaks are compliant, but absent leaks are cheaper. The leak-rate trend line is also the single most persuasive chart you can show a QSA to demonstrate a working control.
Scan logs, tickets, and databases for cardholder data with one API call — and mask it before it ever lands. Test it on a sample log line right now.
Try the Live Demo View Pricing