PCI DSS v4.0.1 - Cardholder Data, CDE Scoping, Segmentation, Tokenization, SAQ Types & Payment Page Scripts
PCI DSS v4.0.1: cardholder data vs sensitive authentication data, CDE and connected-to scoping, segmentation and its testing, tokenization vs encryption, and how iframes, your own JS fields or a direct post lead to SAQ A, A-EP or D (including the January 2025 SAQ A change). Covers payment page script controls 6.4.3 and 11.6.1 and the future-dated requirements that are now mandatory, with a runnable token vault and scope calculator.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
May you store the CVV encrypted?
Answer
No. Requirement 3.3.1 forbids storing SAD after authorization, even encrypted.
L2
What else is in scope besides systems that store PAN?
Answer
Systems connected to the CDE and systems that can affect its security, such as the IdP, CI/CD, logging and the server that serves the payment page.
L3
Is segmentation required?
Answer
No, but without it the whole flat network is in scope. If you rely on it, you must penetration-test it at least every 12 months.
L4
Why does a good token fail the Luhn check?
Answer
So it can never be mistaken for a real card number, while it can still keep the last four digits for receipts.
L5
SAQ A vs SAQ A-EP?
Answer
SAQ A when all payment fields come from the PSP; A-EP when your own page controls how card data reaches the PSP, which brings many more requirements.
L6
What do 6.4.3 and 11.6.1 require?
Answer
An inventory, authorization and integrity check for every payment page script, and detection of unauthorized changes to page content and headers at least weekly or per risk analysis.
L7
Why is a plain SHA-256 of a PAN not allowed as an ID?
Answer
The PAN space is small enough to brute-force, so 3.5.1.1 requires keyed cryptographic hashes.
Failure modes
Card fields posted to your own API
Forwarding the PAN through your server pulls the web app, payments API, IdP, CI/CD and orders database into the CDE and forces SAQ D.
Untested segmentation
A firewall rule nobody penetration-tested does not reduce scope in an assessment, so the flat network stays in scope.
Skimming script on the checkout page
A tampered third-party tag overlays fake fields or reads keystrokes around the iframe, the Magecart attack class that 6.4.3 and 11.6.1 target.
Misconceptions
Encrypting the PAN takes the database out of scope.
Encrypted PAN is still account data. Scope relief comes only when you have no access to the keys, for example when the PSP holds them.
SAQ A means no PCI work.
You still attest, confirm your site is not susceptible to script attacks, and run required scans where applicable.
The PSP's AOC covers our integration.
Their responsibility matrix says what you still own, such as the page that loads their fields.
Interviewer traps
Logging request bodies on payment endpoints for debugging.
Debug logs and APM traces then capture PANs; scrub or exclude payment routes.
Calling merchant levels a PCI SSC rule.
Levels are defined by each card brand, and your acquirer decides which validation it accepts.
Design scenario
Same prompt for every reader.
Requirements
Saved cards for one-click repeat purchases, refunds and receipts showing the last four digits.
Failure assumptions
- The checkout page loads several third-party marketing tags.
- Old logs contain full PANs.
- The CI/CD system can deploy to every service.
Constraints
- About 2 million card transactions a year.
- The PSP offers hosted fields and a token vault.
Prompt
Redesign an e-commerce checkout that currently posts card fields to the merchant's own API, so the company can validate with the smallest SAQ while keeping saved cards for repeat buyers.
API
How do hosted fields, tokens and server-side charge calls replace the current card POST?
Data
What do the orders database, logs and backups keep after the change, and how do you purge historical PANs?
Architecture
Which systems remain in scope, which SAQ applies, and how do you control scripts on the payment page?
Overview
Engineering guidance, not legal advice. Whether a law applies to your company, and what a contract obliges you to do, is a question for counsel and your auditors. Facts were checked against official sources on 2026-10-08.
PCI DSS is the card brands' security standard for anyone who stores, processes or transmits payment card data, or can affect its security. It is contractual, not law: your acquiring bank requires it, and failing it can cost fines from the acquirer, liability for fraud, or the right to accept cards. The current version is PCI DSS v4.0.1; every v4 requirement that was "future-dated" became mandatory on 2025-03-31. For engineers the whole game is scope: which systems touch account data. This page explains cardholder data vs sensitive authentication data, how to scope the cardholder data environment (CDE), how segmentation, tokenization, encryption and hosted payment fields shrink it, which SAQ you end up with, and the v4 changes that land on engineering teams (MFA everywhere into the CDE, payment page script controls, customized approach).
Not re-taught here (see Elsewhere in the library): VPC and security-group design for segmentation, CSP and Subresource Integrity mechanics, envelope encryption and secret stores, mTLS between services.
Account data: what you may and may not keep
| Data | Category | Store after authorization? | If stored |
|---|---|---|---|
| Primary account number (PAN) | Cardholder data | Only if needed | Render unreadable (3.5.1): strong cryptography, truncation, index tokens, or keyed hashes (3.5.1.1) |
| Cardholder name, expiration date, service code | Cardholder data | If needed | Protected; in scope when stored with PAN |
| Full track data (magstripe or chip equivalent) | Sensitive authentication data (SAD) | Never | n/a |
| Card verification code (CVV2, CVC2, CID) | SAD | Never, not even encrypted | n/a |
| PIN and PIN block | SAD | Never | n/a |
Requirement 3.3.1 forbids keeping SAD after authorization, even encrypted (card issuers have a narrow exception). v4 added 3.3.2: SAD held electronically before authorization completes (for example a card-on-file flow mid-checkout) must be encrypted. Display rules (3.4.1): show at most the BIN (first six or eight digits) and last four unless a person has a business need to see more.
The cheapest compliant design is the one that never sees the PAN at all.
Hosted fields with PSP tokens, or post the card to your own API?
Prefer
PSP iframe fields plus tokens
The PSP serves every card field and stores the PAN; your systems keep only a token.
- Only the page server stayed in scope, with SAQ A.
- The orders database kept token, last four and brand, never the PAN.
- The token kept the last four digits but failed the Luhn check.
Alternative
Form posts the PAN to your API
Your server receives the card and forwards it to the PSP.
- Web app, payments API, IdP, CI/CD and orders database joined the scope.
- Validation became SAQ D, or a ROC at Level 1.
- Logs, traces and backups now risk holding PANs.
A tokenized checkout, condensed
Diagram 1 condensed.
- 1
Page loads the PSP iframe
Card fields served from the PSP domain keep the PAN out of your page and systems. - 2
PSP stores the PAN and returns a token
The validated vault holds the PAN; your browser and server only see a token. - 3
Server charges with the token
Authorization runs inside the PSP's environment and returns a result; CVV is discarded there. - 4
Save the token and last four
Enough for refunds and receipts without storing cardholder data. - 5
Page scripts changed?
Unauthorized script changes are how skimmers steal cards; weekly integrity checks catch them.
Scoping the CDE
The cardholder data environment is every system component that stores, processes or transmits cardholder data or SAD. Scope also includes components that:
- are connected to the CDE (network paths, shared services such as DNS, NTP, a database the CDE writes to);
- can impact the security of the CDE (the identity provider that grants admin access, the CI/CD system that deploys CDE code, the logging and monitoring stack, jump hosts, the web server that serves your payment page).
Requirement 12.5.2 requires you to document and confirm scope at least once every 12 months and after significant change (every six months for service providers). Under-scoping is a finding; over-scoping wastes the whole budget.
Segmentation
Segmentation is not required, but without it the entire flat network is in scope. With firewalls, security groups and separate accounts isolating the CDE, systems on the other side drop out of scope. If you rely on segmentation, you must penetration-test the segmentation controls at least every 12 months and after changes (11.4.5; every six months for service providers under 11.4.6). In cloud terms: a dedicated account or project for the CDE, explicit security-group allowlists, no peering to general VPCs, separate CI credentials (see vpc-fundamentals-subnets-routes-sg-nacl).
Four ways to shrink scope, compared
| Technique | How it shrinks scope | What stays in scope | Pros | Cons |
|---|---|---|---|---|
| Hosted payment page or redirect | Customer leaves your site to the PSP's page | The site that redirects (it could redirect elsewhere) | Simplest SAQ A path | Checkout UX leaves your brand |
| Hosted fields / iframes | Each card field is an iframe served by the PSP; your page never touches PAN | Your page (it can be tampered with to overlay fake fields) | Native-looking checkout, SAQ A eligible with script protections | Must confirm the page is not susceptible to script attacks |
| Tokenization | Systems store a surrogate token, not the PAN | The token vault and anything that can detokenize | Recurring billing and refunds without PAN in your DB | Vault (yours or the PSP's) is high-value; detokenize rights must be tiny |
| Encryption | PAN stored as ciphertext | Every system with access to keys or ciphertext, usually | Keeps PAN usable by you | Encrypted PAN is still account data; scope relief only if you have no access to keys (a PSP holds them) |
| P2PE (point-to-point encryption) | Card-present: validated devices encrypt at the terminal | The device estate | SAQ P2PE for retail | Hardware and listing requirements |
Tokens beat encryption for scope because there is no mathematical path back to the PAN without the vault. Good tokens also fail the Luhn check so they are never mistaken for real card numbers, and they keep the last four digits for receipts.
How a tokenized checkout flows
Diagram 1: a hosted-fields checkout with a PSP vault, with the failure path.
Decisions
- 1
Step 1: page loads PSP iframe
- nextStep 2: shopper types card
- 2
Step 2: shopper types card
- nextStep 3: PSP stores PAN
- 3
Step 3: PSP stores PAN
- nextStep 4: browser gets token
- 4
Step 4: browser gets token
- nextStep 5: server sends token
- 5
Step 5: server sends token
- nextStep 6: PSP authorizes
- 6
Step 6: PSP authorizes
- nextStep 7: discard CVV at PSP
- 7
Step 7: discard CVV at PSP
- nextStep 8: save token and last4
- 8
Step 8: save token and last4
- nextStep 9: page scripts changed?
- ?
Step 9: page scripts changed?
- nextStep 10: weekly integrity check
- nextFailure: skimming script alert
- 10
Step 10: weekly integrity check
- 11
Failure: skimming script alert
- nextBlock page, incident response
- 12
Block page, incident response
Lesson map
PCI DSS v4.0.1 - Cardholder Data, CDE Scoping, Segmentation, Tokenization, SAQ Types & Payment Page Scripts
PCI DSS v4.0.1: cardholder data vs sensitive authentication data, CDE and connected-to scoping, segmentation and its testing, tokenization vs encryption, and how iframes, your own JS fields or a direct post lead to SAQ A, A-EP or D (including the January 2025 SAQ A change). Covers payment page script controls 6.4.3 and 11.6.1 and the future-dated requirements that are now mandatory, with a runnable token vault and scope calculator.
Architecture. Architecture
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB s1["Step 1: page loads PSP iframe"] s2["Step 2: shopper types card"] s3["Step 3: PSP stores PAN"] s4["Step 4: browser gets token"] s5["Step 5: server sends token"] s6["Step 6: PSP authorizes"] s7["Step 7: discard CVV at PSP"] s8["Step 8: save token and last4"] s9["Step 9: page scripts changed?"] s10["Step 10: weekly integrity check"] f1["Failure: skimming script alert"] f2["Block page, incident response"] s1 -->|continues| s2 s2 -->|continues| s3 s3 -->|continues| s4 s4 -->|continues| s5 s5 -->|continues| s6 s6 -->|continues| s7 s7 -->|continues| s8 s8 -->|continues| s9 s9 -->|continues| s10 s9 -->|continues| f1 f1 -->|continues| f2
Your systems handle only tokens (steps 4, 5, 8). The PSP's systems handle PAN and CVV (steps 3, 6, 7). Step 9 is the part of the page you still control: a malicious script injected into your checkout page can overlay fake fields or read keystrokes around the iframe. That attack class (Magecart-style skimming) is why v4 added payment page script requirements.
Runnable: token vault toy
"""PCI scope reduction with a token vault - toy, stdlib only.
Concept first:
* PAN (card number) is cardholder data; CVV2/CVC2, full track and PIN blocks are
SENSITIVE AUTHENTICATION DATA (SAD). SAD must never be stored after
authorization, not even encrypted (PCI DSS 3.3.1).
* Tokenization swaps the PAN for a surrogate. Systems that only ever see tokens
can drop out of PCI scope; the vault (and anything that can detokenize) stays in.
* Encryption is different: whoever holds the key can decrypt, so systems storing
encrypted PAN usually stay in scope unless the key is held by someone else.
* Display masking: show at most BIN + last four (PCI DSS 3.4.1).
The cipher here is a dependency-free HMAC-SHA256 keystream + tag so the demo runs
anywhere; real vaults use AES-GCM with HSM/KMS-held keys.
"""
import hmac, hashlib, secrets
def luhn_ok(pan: str) -> bool:
total, alt = 0, False
for ch in reversed(pan):
d = int(ch)
if alt:
d = d * 2 - 9 if d * 2 > 9 else d * 2
total, alt = total + d, not alt
return total % 10 == 0
def _stream(key: bytes, nonce: bytes, n: int) -> bytes:
out, ctr = b"", 0
while len(out) < n:
out += hmac.new(key, nonce + ctr.to_bytes(4, "big"), hashlib.sha256).digest()
ctr += 1
return out[:n]
class Vault:
"""The only component that ever stores PAN. Lives in the CDE."""
def __init__(self) -> None:
self._enc_key, self._mac_key = secrets.token_bytes(32), secrets.token_bytes(32)
self._rows: dict[str, tuple[bytes, bytes, bytes]] = {}
def tokenize(self, pan: str, **extra) -> str:
if extra.keys() & {"cvv", "cvc", "track", "pin"}:
raise ValueError("refusing to store sensitive authentication data (PCI DSS 3.3.1)")
if not luhn_ok(pan):
raise ValueError("not a valid PAN")
while True: # random token, same length, keeps last 4, deliberately FAILS Luhn
tok = "9" + "".join(secrets.choice("0123456789") for _ in range(len(pan) - 5)) + pan[-4:]
if not luhn_ok(tok) and tok not in self._rows:
break
nonce = secrets.token_bytes(12)
ct = bytes(a ^ b for a, b in zip(pan.encode(), _stream(self._enc_key, nonce, len(pan))))
tag = hmac.new(self._mac_key, nonce + ct, hashlib.sha256).digest()
self._rows[tok] = (nonce, ct, tag)
return tok
def detokenize(self, tok: str, caller: str) -> str:
if caller != "payment-gateway-adapter": # least privilege: one caller may detokenize
raise PermissionError(f"{caller} may not detokenize")
nonce, ct, tag = self._rows[tok]
if not hmac.compare_digest(tag, hmac.new(self._mac_key, nonce + ct, hashlib.sha256).digest()):
raise ValueError("tampered")
return bytes(a ^ b for a, b in zip(ct, _stream(self._enc_key, nonce, len(ct)))).decode()
def mask(pan_or_token: str) -> str:
return pan_or_token[:6] + "*" * (len(pan_or_token) - 10) + pan_or_token[-4:]
if __name__ == "__main__":
v = Vault()
pan = "4111111111111111" # well-known test PAN
tok = v.tokenize(pan)
order_row = {"order": 1001, "card_token": tok, "last4": tok[-4:], "brand": "visa"}
print("PAN Luhn valid:", luhn_ok(pan), "| token Luhn valid:", luhn_ok(tok), "| same last4:", tok[-4:] == pan[-4:])
print("orders DB stores (out of scope if it never sees PAN):", {**order_row, "card_token": "9" + "#" * 11 + tok[-4:]})
print("receipt shows:", mask(pan))
print("gateway adapter detokenizes:", v.detokenize(tok, "payment-gateway-adapter") == pan)
for caller in ("analytics-job",):
try:
v.detokenize(tok, caller)
except PermissionError as e:
print("blocked:", e)
try:
v.tokenize(pan, cvv="123")
except ValueError as e:
print("blocked:", e)Output:
PAN Luhn valid: True | token Luhn valid: False | same last4: True
orders DB stores (out of scope if it never sees PAN): {'order': 1001, 'card_token': '9###########1111', 'last4': '1111', 'brand': 'visa'}
receipt shows: 411111******1111
gateway adapter detokenizes: True
blocked: analytics-job may not detokenize
blocked: refusing to store sensitive authentication data (PCI DSS 3.3.1)Notice the token keeps the last four digits but fails Luhn, the orders database holds only token, last four and brand, the analytics job cannot detokenize, and the vault refuses CVV outright.
SAQ types and merchant levels
Merchants validate with a Self-Assessment Questionnaire (SAQ) or, at the largest levels, a Report on Compliance (ROC) by a Qualified Security Assessor (QSA), plus an Attestation of Compliance (AOC). The SAQ is chosen by how you accept cards:
| SAQ | Who | Roughly what you attest to |
|---|---|---|
| A | Card-not-present merchants that fully outsource all account data functions; payment page elements come only from a PCI DSS compliant PSP (redirect or iframe); no electronic account data on your systems | A small subset; since the January 2025 SAQ A revision, requirements 6.4.3 and 11.6.1 were removed from SAQ A and replaced by an eligibility criterion: you confirm your site is not susceptible to script attacks (PCI SSC FAQ 1588 explains how) |
| A-EP | E-commerce where your page controls how card data reaches the PSP (for example your JavaScript builds the form and posts directly to the PSP) but your servers never receive it | Many more requirements, including payment page script controls and web server hardening |
| D | Everyone else: you receive, store or process card data, or no other SAQ fits | The full standard (or the service-provider version) |
| Others | B, B-IP, C, C-VT, P2PE, SPoC for card-present and terminal-based models | Specific to the channel |
Merchant levels are defined by each card brand, not by PCI SSC. Visa's, for example: Level 1 is over 6 million transactions a year (annual ROC by a QSA or qualified internal assessor), Level 2 is 1 to 6 million, Level 3 is 20,000 to 1 million e-commerce, and Level 4 is everyone smaller. Your acquirer tells you which validation it accepts. Service providers (PSPs, hosting for CDEs, tokenization services) have their own levels and stricter frequencies.
Runnable: scope calculator for three checkout designs
// PCI DSS scoping: which systems are in scope under three checkout designs?
// Concept: the CDE is every system that stores, processes or transmits account
// data. Systems that CONNECT to the CDE or can AFFECT ITS SECURITY (IdP, CI/CD,
// logging, jump hosts, the web server that serves the payment page) are in scope
// too. Segmentation and outsourcing shrink the set; the SAQ you qualify for
// follows from who controls the payment page and who touches the PAN.
type Role = "CDE" | "connected/security-impacting" | "out of scope";
interface System { name: string; touchesPan: boolean; links: string[]; servesPaymentPage?: boolean }
interface Design { name: string; systems: System[]; pageFrom: "merchant" | "merchant-js" | "psp-iframe" }
function classify(d: Design): Map<string, Role> {
const role = new Map<string, Role>();
for (const s of d.systems) role.set(s.name, s.touchesPan ? "CDE" : "out of scope");
for (const s of d.systems) {
if (role.get(s.name) === "CDE") continue;
const linked = s.links.some((l) => role.get(l) === "CDE") ||
d.systems.some((o) => role.get(o.name) === "CDE" && o.links.includes(s.name));
if (linked || s.servesPaymentPage) role.set(s.name, "connected/security-impacting");
}
// if YOUR code builds the payment form (A-EP), anything that can change the page
// server (IdP that grants deploy rights, CI/CD that ships it) is security-impacting too
if (d.pageFrom === "merchant-js") {
const page = d.systems.find((s) => s.servesPaymentPage);
for (const l of page?.links ?? []) if (l === "IdP/SSO" || l === "CI/CD") role.set(l, "connected/security-impacting");
}
return role;
}
function saq(d: Design, role: Map<string, Role>): string {
const merchantTouchesPan = d.systems.some((s) => s.touchesPan && !s.name.startsWith("PSP"));
if (merchantTouchesPan) return "SAQ D (or ROC if Level 1)";
if (d.pageFrom === "merchant-js") return "SAQ A-EP (your page controls how card data reaches the PSP)";
if (d.pageFrom === "psp-iframe") return "SAQ A (all payment fields served by the PSP; confirm script-attack protection)";
return "SAQ D";
}
const shared: System[] = [
{ name: "IdP/SSO", touchesPan: false, links: [] },
{ name: "CI/CD", touchesPan: false, links: [] },
{ name: "analytics warehouse", touchesPan: false, links: ["orders DB"] },
{ name: "orders DB", touchesPan: false, links: [] },
];
const designs: Design[] = [
{ name: "A) form posts PAN to your API", pageFrom: "merchant", systems: [
{ name: "web app", touchesPan: true, links: ["orders DB", "IdP/SSO", "CI/CD"], servesPaymentPage: true },
{ name: "payments API", touchesPan: true, links: ["orders DB", "IdP/SSO", "CI/CD", "PSP"] },
{ name: "PSP", touchesPan: true, links: [] }, ...shared] },
{ name: "B) your JS posts PAN straight to PSP", pageFrom: "merchant-js", systems: [
{ name: "web app", touchesPan: false, links: ["orders DB", "IdP/SSO", "CI/CD"], servesPaymentPage: true },
{ name: "PSP", touchesPan: true, links: [] }, ...shared] },
{ name: "C) PSP-hosted iframe fields + tokens", pageFrom: "psp-iframe", systems: [
{ name: "web app", touchesPan: false, links: ["orders DB", "IdP/SSO", "CI/CD"], servesPaymentPage: true },
{ name: "PSP", touchesPan: true, links: [] }, ...shared] },
];
for (const d of designs) {
const role = classify(d);
const yours = [...role.entries()].filter(([n]) => !n.startsWith("PSP"));
const inScope = yours.filter(([, r]) => r !== "out of scope");
console.log(`== ${d.name}`);
console.log(` in scope (yours): ${inScope.map(([n, r]) => `${n}[${r === "CDE" ? "CDE" : "conn"}]`).join(", ") || "none"}`);
console.log(` validation: ${saq(d, role)}`);
}Output:
== A) form posts PAN to your API
in scope (yours): web app[CDE], payments API[CDE], IdP/SSO[conn], CI/CD[conn], orders DB[conn]
validation: SAQ D (or ROC if Level 1)
== B) your JS posts PAN straight to PSP
in scope (yours): web app[conn], IdP/SSO[conn], CI/CD[conn]
validation: SAQ A-EP (your page controls how card data reaches the PSP)
== C) PSP-hosted iframe fields + tokens
in scope (yours): web app[conn]
validation: SAQ A (all payment fields served by the PSP; confirm script-attack protection)Expected== A) form posts PAN to your API in scope (yours): web app[CDE], payments API[CDE], IdP/SSO[conn], CI/CD[conn], orders DB[conn] validation: SAQ D (or ROC if Level 1) == B) your JS posts PAN straight to PSP in scope (yours): web app[conn], IdP/SSO[conn], CI/CD[conn] validation: SAQ A-EP (your page controls how card data reaches the PSP) == C) PSP-hosted iframe fields + tokens in scope (yours): web app[conn] validation: SAQ A (all payment fields served by the PSP; confirm script-attack protection)
Press Run. Snippets must be self-contained — no network, files, or native modules.
Same product, three architectures: posting the PAN to your API pulls your web app, payments API, IdP, CI/CD and orders database into scope (SAQ D). Your own JavaScript posting to the PSP keeps PAN off your servers but leaves the page, IdP and CI/CD in scope (A-EP). PSP iframes leave only the page server, with a short SAQ A.
PCI DSS v4.0 and v4.0.1 changes that land on engineers
v4.0 was published in March 2022, v3.2.1 retired on 2024-03-31, v4.0 retired on 2024-12-31, and v4.0.1 (June 2024, clarifications only, no new or deleted requirements) is the only active version. Future-dated requirements became mandatory on 2025-03-31.
| Change | Requirement | What it means in practice |
|---|---|---|
| Customized approach | 12.3.2 plus controls matrix | Meet a requirement's stated objective with your own control design, backed by a targeted risk analysis; assessed by a QSA; not available through SAQs |
| Targeted risk analyses | 12.3.1 | You justify the frequency of flexible activities (for example how often to run 11.6.1 checks) |
| MFA for all access into the CDE | 8.4.2 | Not only admins or remote access: every non-console access into the CDE |
| Longer passwords | 8.3.6 | At least 12 characters (8 if the system cannot support 12) |
| Payment page script management | 6.4.3 | Inventory every script on payment pages, authorize each with a written justification, and assure its integrity (CSP, SRI, or equivalent) |
| Change and tamper detection on payment pages | 11.6.1 | Detect unauthorized changes to HTTP headers and page content as received by the browser, at least weekly or per your risk analysis |
| Keyed hashes for PAN | 3.5.1.1 | Plain hashes of PAN no longer acceptable; use keyed cryptographic hashes with managed keys |
| Disk-level encryption limits | 3.5.1.2 | Disk or partition encryption alone no longer renders PAN unreadable on non-removable media |
| Automated log review | 10.4.1.1 | Use automated mechanisms to review audit logs |
| Anti-phishing | 5.4.1 | Technical controls against phishing |
| Vulnerability fixes | 6.3.3 | Critical vulnerabilities patched within one month (v4.0.1 restored the "critical only" wording) |
For script controls, strict CSP with nonces or hashes and Subresource Integrity are the standard tools (see xss-contextual-encoding-csp-nonces-trusted-types), plus a monitor that loads the page as a browser would and diffs scripts and headers.
Decision chart: which validation path?
Decisions
- 1
Q1
- nextSAQ D or ROC, segment the CDE
- nextQ2: who renders the card fields?
- 2
SAQ D or ROC, segment the CDE
- nextQ4: need PAN for recurring billing?
- ?
Q2: who renders the card fields?
- nextSAQ A-EP, plus 6.4.3 and 11.6.1
- nextQ3: script-attack protection confirmed?
- 4
SAQ A-EP, plus 6.4.3 and 11.6.1
- ?
Q3: script-attack protection confirmed?
- nextSAQ A
- nextAdd CSP, SRI, monitoring or PSP confirmation
- 6
SAQ A
- 7
Add CSP, SRI, monitoring or PSP confirmation
- nextSAQ A
- ?
Q4: need PAN for recurring billing?
- nextUse PSP tokens, not your own PAN store
- nextDo not store PAN at all
- 9
Use PSP tokens, not your own PAN store
- 10
Do not store PAN at all
What never to store, and other hard rules
- Never store CVV2/CVC2/CID, full track data or PINs after authorization, in any database, log, cache, backup or support ticket.
- Never log full PAN; mask to BIN plus last four at most, and scrub request bodies on payment routes.
- Never email or chat PANs; if customers send them, delete and tell them not to.
- Never use plain SHA-256 of a PAN as an ID: the PAN space is small enough to brute-force, which is why 3.5.1.1 requires keyed hashes.
What happens if you choose otherwise
- Post card fields to your own API "just to forward them": your app, its database and everything connected join the CDE; SAQ D.
- Encrypt PAN in your database instead of tokenizing: still account data; your app, key management and backups stay in scope.
- Rely on segmentation without testing it: an untested firewall rule does not reduce scope in an assessment.
- Load third-party tags on the checkout page: each script needs inventory, authorization and integrity controls, or a skimming risk you own.
Pitfalls
- Debug logs or APM traces capturing request bodies on payment endpoints.
- Support tooling and call recordings capturing card numbers.
- Assuming SAQ A means "no PCI work": you still attest, keep your page safe from script attacks, and run the required ASV scans if applicable.
- Treating the PSP's AOC as covering your integration; check what their responsibility matrix says you own.
How the code was checked
- The token vault ran under Python 3.13 and the scope calculator under
tsc --strictand Node 20. Their output blocks are the real captured output. - The card number is the standard 4111 1111 1111 1111 test PAN; no real card data was used.
Interview Q&A
Cardholder data vs sensitive authentication data?
Answer
Cardholder data is PAN plus name, expiry and service code; it may be stored if protected. SAD is full track, CVV2/CVC2/CID and PINs; it must never be stored after authorization, even encrypted.
What is the CDE and what else falls into PCI scope?
Answer
The CDE is every component that stores, processes or transmits account data. Systems connected to it or able to affect its security, such as the IdP, CI/CD, logging, jump hosts and the payment page server, are in scope too.
Is network segmentation required?
Answer
No, but without it everything on the flat network is in scope. If you use it to reduce scope, you must penetration-test the segmentation at least annually and after changes (every six months for service providers).
Tokenization vs encryption for scope reduction?
Answer
Tokens have no mathematical relationship to the PAN, so systems that only hold tokens and cannot detokenize leave scope. Encrypted PAN is still account data, and systems with key or ciphertext access usually stay in scope unless a third party holds the keys.
SAQ A vs SAQ A-EP?
Answer
SAQ A: all payment page elements come from a compliant PSP (iframe or redirect) and you never handle account data electronically; since January 2025 you also confirm your site is not susceptible to script attacks. A-EP: your own page code controls how card data is sent to the PSP, so many more requirements apply.
What did PCI DSS v4.0 change for MFA?
Answer
Requirement 8.4.2 extends MFA to all non-console access into the CDE, not just administrators or remote access. It became mandatory on 2025-03-31.
What are requirements 6.4.3 and 11.6.1 about?
Answer
Payment page security against skimming. 6.4.3 requires an inventory, authorization and integrity assurance for every script on payment pages. 11.6.1 requires detecting unauthorized changes to payment page headers and content at least weekly or per a targeted risk analysis.
What is the customized approach?
Answer
A v4 option to meet a requirement's objective with your own control design, supported by a targeted risk analysis and a controls matrix, and assessed by a QSA. It is not available for SAQ self-assessments.
How should a merchant handle recurring billing without storing PAN?
Answer
Use the PSP's tokens (network tokens where available). The PSP keeps the PAN in its vault; you store the token, last four and expiry if needed.
Why is a plain hash of a PAN weak?
Answer
The space of valid PANs for a given BIN is small enough to brute-force, so an unkeyed hash is reversible in practice. v4 requires keyed cryptographic hashes with managed keys.
Who decides merchant level and validation method?
Answer
The card brands define levels (for example Visa Level 1 above 6 million transactions a year) and the acquirer tells you what validation it requires: an SAQ or a ROC by a QSA, plus an AOC.
Check yourself
Draw your own checkout's data path and mark every system that sees the PAN, connects to a system that does, or can change the payment page. Then redraw it with PSP iframe fields and tokens and count the systems that drop out.
Elsewhere in the library
These pages stay as they are. This lesson only points at them: VPC Fundamentals — Subnets, Route Tables, Security Groups & NACLs, Private Networking — VPC, NAT, SSM, Tunnels & Ingress, Cross-Site Scripting (XSS) - Contextual Encoding, Strict CSP Nonces & Trusted Types, Web Application Security - OWASP Top 10, Injection, XSS, CSRF & SSRF, Envelope Encryption — DEK, KEK & CMK Hierarchy, Secret Stores Compared — Vault, Cloud Secrets Manager & Kubernetes Secrets, Service-to-Service Auth — mTLS, Client Credentials & Workload Identity, TLS — Handshake, Certs, mTLS & Termination.