PCI DSS is written for organisations, not for engineers, which is why most developers meet it as a list of things someone in compliance says they cannot do. This page covers the parts that actually change how you write code: which data you may store, which you may never store, what “reducing scope” means in practice, and why your test environment is not allowed to contain real card numbers.
This is general technical information, not compliance advice. The authoritative source is the PCI Security Standards Council, and a real compliance programme needs a Qualified Security Assessor.
What PCI DSS is
The Payment Card Industry Data Security Standard is published by the PCI Security Standards Council, founded by Visa, Mastercard, American Express, Discover, and JCB.
It is not a law. It is a contractual condition of accepting card payments, which in practice makes it more immediate than most regulation: the consequences of non-compliance are financial penalties, liability for the cost of a breach, and ultimately losing the ability to take card payments at all. There is no regulator to appeal to, only your acquirer.
The current standard is PCI DSS v4, with v4.0.1 the revision published in the Council’s document library. Version 3.2.1 has been retired, and the future-dated requirements introduced with v4 are now in force. Requirement numbering changed between the two versions — the rule about test data, for instance, moved from 6.4.3 to 6.5.5 — so a Stack Overflow answer citing a requirement number may be pointing at the right rule under the wrong index.
The data categories
This table is the part worth committing to memory. Everything else follows from it.
| Data | Storage permitted? | Must be protected if stored? |
|---|---|---|
| Cardholder Data | ||
| Primary Account Number (PAN) | Yes | Yes — rendered unreadable |
| Cardholder name | Yes | Yes |
| Expiry date | Yes | Yes |
| Service code | Yes | Yes |
| Sensitive Authentication Data | ||
| Full magnetic stripe or chip track data | Never after authorisation | — |
| CAV2 / CVC2 / CVV2 / CID | Never after authorisation | — |
| PIN and PIN block | Never after authorisation | — |
Sensitive Authentication Data may not be retained after authorisation — not encrypted, not hashed, not in a log file, not “temporarily”. The prohibition is absolute. This is the single rule most likely to be violated by accident, because security codes end up in application logs, error tracker payloads, and support screenshots without anyone deciding to store them.
The distinction is easier to remember through what each field is for. A PAN identifies an account, which is why it can legitimately persist. A security code proves that whoever is using the card is holding it at that moment, which is why it has no purpose after authorisation and no justification for existing afterwards. Ours is a random value of the right length precisely because a real one cannot be derived from the number — see the CVV generator for what that means when you need test input.
Where card data accidentally lands
Almost nobody decides to store card data improperly. It arrives through infrastructure built for other purposes:
| Location | How it gets there | Prevention |
|---|---|---|
| Application logs | Logging the whole request body | Redact by field name at the logger, not the call site |
| Error trackers | Exception context carries form data | Configure denyUrls and scrub payment fields in the SDK |
| Analytics events | Form values sent as event properties | Never pass raw input as event data |
| localStorage / sessionStorage | Persisting form state for UX | Exclude payment fields from any state persistence |
| Database backups | An unencrypted PAN column, copied off-site | Encrypt the field, not just the disk |
| Support tickets | Customers paste details; agents screenshot | Auto-redact card-shaped strings on ingest |
| Session replay tools | Input fields recorded by default | Mask all inputs, then allowlist — never the reverse |
| Crash dumps and core files | Raw memory contents | Disable dumps on payment services |
| CDN and proxy logs | Query strings and POST bodies | Never put card data in a URL; audit access logs |
| Git history | One test fixture with a real card, committed once | Pre-commit secret scanning; treat history as permanent |
The last two deserve emphasis because they are rarely discussed. Session replay tools record input fields unless configured otherwise, and the default is usually to capture. Git history is worse: a card number committed once and deleted in the next commit is still in the repository, in every clone, on every developer’s laptop, and in your CI cache. Rotating that out means rewriting history across every fork.
Reducing scope — the only real strategy
Every system that stores, processes, or transmits cardholder data is in scope. The most effective compliance strategy is not to secure more systems; it is to have fewer systems touch the data.
In order of effectiveness:
- Hosted payment page or redirect. The customer enters card details on the provider’s page. The data never reaches your infrastructure at all. Narrowest possible scope.
- Hosted fields or iframes — Stripe Elements, Braintree Hosted Fields, Adyen Components. The input fields live in the provider’s iframe, so card data never enters your DOM even though the form looks like yours.
- Tokenisation. The provider stores the PAN and hands you a token that is meaningless outside their system. Tokens are not cardholder data, so storing them keeps you out of scope — covered in the tokenisation guide.
- Network segmentation. Where card data genuinely must be handled, isolate those systems so the rest of your estate is not dragged in with them.
- Encryption at rest with your own vault. Broadest scope, highest cost, and the option to choose only when the first four are impossible.
Each step down that list adds systems, audits, and cost. The corresponding self-assessment questionnaires make the trade explicit:
| Integration | SAQ | Roughly |
|---|---|---|
| Redirect or hosted payment page | A | Shortest |
| Hosted fields on your own page | A-EP | Moderate, and your page’s scripts are in scope |
| Card data handled by your systems | D | Longest by a wide margin |
Your acquirer decides which questionnaire applies. It is worth asking before you choose an integration pattern rather than after.
One nuance in the middle row catches teams out. Hosted fields keep card data out of your DOM, but every script on the page containing them can still alter that page — swap the iframe, overlay a fake field, or read what the customer types before the real field receives it. That is the mechanism behind digital skimming attacks, and it is why v4 added explicit expectations around managing and monitoring the scripts on payment pages. If your checkout loads a tag manager, a chat widget, an A/B testing snippet, and three analytics libraries, you have four vendors who can modify your payment page, and the compliance question is whether you can say what each of them is doing today.
Requirement 3 — protecting stored data
The parts a developer implements:
- Sensitive Authentication Data is not retained after authorisation. Absolute, as above.
- PAN is masked when displayed — a maximum of the first six and last four digits, and only where a role has a documented need for more than the last four.
- Stored PAN is rendered unreadable by one of: a one-way hash with a salt, truncation, a token, or strong cryptography with proper key management.
That salt is not optional, and the reason is arithmetic. The search space for a card number is small: a known BIN, a known length, and a Luhn-valid final digit leave only the account identifier free — often seven or eight digits, which is trivially brute-forceable against an unsalted hash. Hashing a PAN without a salt produces something that looks protected and is not. The structure guide works through why so few digits are actually unknown.
Requirement 6 — secure development
Requirement 6 is where PCI DSS reads most like a normal engineering standard: secure coding practices and training, code review or automated security testing before release, protection against known classes of vulnerability — the OWASP Top Ten is the usual reference — change management with separated environments, and keeping dependencies patched.
It also contains the requirement this site exists to help with.
Test environments — why synthetic data is required
Production data may not be used for testing or development. This is not a recommendation; it is a requirement — Requirement 6.5.5 in version 4, previously 6.4.3 in version 3.2.1 — and it exists because test environments are consistently less protected than production: looser access control, shared credentials, data copied to laptops, screenshots in tickets.
That leaves two options. Mask or remove the card data before it moves, which is work you must repeat correctly on every refresh forever. Or generate synthetic data that never contained anything real, which is repeatable and cannot leak what it never held.
What a realistic test data set needs:
- Synthetic PANs across every network you accept — the generator for individual cases, the bulk generator for fixture files and load tests.
- Gateway sandbox cards for anything requiring a processor response — approvals, declines, 3-D Secure. The test card reference collects them, and the Stripe set is documented in full.
- Synthetic names and addresses, because a production dump is not only about card numbers — the identity generator covers the rest of the row.
- Deliberately invalid data for negative paths: broken check digits, wrong lengths, expired dates. The validator is a quick way to confirm a fixture fails the way you intended.
The single most common violation in this whole area is copying a production database dump into staging. It is done for good reasons — realistic data finds real bugs — and it moves the most sensitive data you hold into the least protected environment you run.
This also intersects with data protection law. A production dump in staging is still personal data under the GDPR, with the same lawful basis requirements and the same breach obligations, sitting in an environment that is usually easier to compromise. Two regimes, one bad habit.
What developers most often get wrong
- Logging the entire request body, payment fields included.
- Sending form data to an error tracker as exception context.
- Copying a production dump into staging.
- Keeping the CVV in session “just until the retry”.
- Using a real card in a test fixture — and committing it.
- Hashing a PAN without a salt.
- Leaving input recording on in a session replay tool.
- Masking the PAN in the interface while storing it in full in the database.
Every one of these is an accident of convenience rather than a decision, which is why they survive code review: nothing in the diff looks like a compliance decision.
A practical developer checklist
- No card data in application logs — verified by grepping logs, not by reading code
- Payment fields scrubbed in the error tracker SDK
- No card data in analytics or product telemetry
- Session replay masks all inputs by default
- No card data in
localStorage,sessionStorage, or cookies - Card data never appears in a URL or query string
- CVV never persisted anywhere, including caches and session stores
- Stored PAN encrypted, tokenised, truncated, or salted-hashed
- Display masking applied at the API boundary, not only in the UI
- Test and staging environments contain synthetic data only
- Test fixtures generated, never derived from production
- Pre-commit secret scanning covers card-shaped strings
- Database backups encrypted, with access logged
- Crash dumps disabled on services that handle payments
- Dependency scanning running in CI
- Integration pattern and its SAQ confirmed with your acquirer
The test data management guide covers how to keep such a fixture set healthy over time, and the payment form checklist covers what to assert once you have one.
Where to get authoritative answers
- The PCI Security Standards Council — the standard itself, plus the SAQs and the Prioritized Approach
- The document library for the current version and its summary of changes
- The Qualified Security Assessor directory when you need a formal opinion
- Your payment provider’s compliance documentation, which will tell you which SAQ its integrations map to
- Your acquirer, who ultimately determines your validation requirements
Again: this page is technical background, not compliance advice. Requirement numbers change between versions, interpretations vary by acquirer, and only a QSA can tell you where your specific systems stand.
The requirement numbers and version details above were last checked against provider documentation on . Providers do change what they publish — the official link beside each claim is authoritative.