Sensitive Data
Handling card data and PII: keep PCI scope small with tokenization, never log secrets, redact by default, and treat access to sensitive data as an event.
The cheapest sensitive data to protect is the data you never touch. Every system that sees a card number has to be secured, audited and explained to an assessor. Every log line that contains an email address is a privacy liability for as long as the log is kept. So the design goal isn't "protect sensitive data everywhere". It's "make sure most of the system never sees it", and then protect the small part that does.
Keep PCI Scope Small
PCI DSS applies to every system that stores, processes or transmits cardholder data, and to everything connected to those systems. Scope is the cost driver. A business that takes raw card numbers on its own servers can be on the hook for more than 300 security controls, with dedicated tooling and an annual external assessment. One that never touches them answers a short questionnaire.
- Tokenize at the edge. Card data goes from the browser or device straight to a vault: hosted payment fields, a processor SDK, or a dedicated tokenization service. Your servers receive a token. A token is useless to an attacker outside your account with the processor, so the rest of the platform, its logs, its database backups and its engineers' laptops fall out of scope.
- One small vault, if you need one at all. Some businesses must hold card numbers (to route between processors, for example). Then it's one isolated service, on its own network segment, with its own keys and the smallest possible interface: tokenize, and detokenize only toward the processor. A vault with a "get card number" endpoint for convenience is no longer a vault.
- Never store the card verification code. PCI DSS forbids keeping it after authorization, even encrypted. It exists to prove the cardholder has the card right now, and a stored one proves nothing except that you were breached.
What the rest of the system may keep:
| Field | Allowed outside the vault | Why |
|---|---|---|
| Token | Yes | Only meaningful through your processor account |
| Last 4 digits | Yes | Lets a customer recognize their card |
| BIN (first 6 or 8) | Yes | Card routing and fraud signals |
| Brand, expiry month and year | Yes | Display and expiry reminders |
| Full card number | No | Brings the whole system into scope |
| Card verification code | Never, anywhere | Forbidden after authorization |
Classify Every Field
Rules like "don't log PII" fail because nobody agrees what counts. So every field gets a classification, and the classification decides the handling:
| Class | Examples | Handling |
|---|---|---|
| Secret | API keys, webhook secrets, card numbers | Vault or secrets manager only, never logged, never returned after creation |
| Sensitive | Name, email, address, bank account last 4 | Encrypted at rest, masked in logs and support tools |
| Internal | IDs, statuses, amounts | Normal handling |
New fields default to Sensitive until someone decides otherwise. The opposite default means every new field leaks until someone notices, and someone usually notices in an incident review.
Logging and Errors
Logs are copied, shipped to vendors, kept for months and searched by many people. They're where sensitive data escapes most often, not because anyone decides to log it but because someone logs a whole request object while debugging.
- Redact by default, with allow-lists. Logging goes through a serializer that drops classified fields. A deny-list ("never log
card_number") fails the first time someone addscardNumberorpan. An allow-list means a new field isn't logged until it's marked safe, so the mistake is a missing log line, not a leak. - No request bodies in logs for endpoints that accept sensitive data. Log the request ID, the route and the outcome. The request ID is enough to find everything else.
- Errors never echo input.
"The card number is invalid.", not"4242 4242 4242 4241 is invalid."Error messages end up in client logs, support tickets and screenshots. The error format keyserrorsby field name partly for this reason: it can say which field is wrong without repeating it. - Scan for leaks. CI and the log pipeline run a card-number detector (Luhn-valid runs of 13 to 19 digits) and alert on a match. Redaction is the control; scanning is how you find out it failed.
Secrets
- Show API keys once, store only a hash. After creation, only a prefix is displayed (
sk_live_4f2a...). If the database leaks, the keys don't, for the same reason passwords are hashed. - Prefix keys by type and environment:
sk_live_,sk_test_,rk_live_for restricted keys,whsec_for webhook secrets. A leaked key is immediately recognizable in a log or a paste, live keys can't be mistaken for test keys, and secret scanners like GitHub's can detect a fixed format reliably. Stripe uses exactly these prefixes. - Prefer restricted keys with only the permissions a caller needs. A key that can only create payments can't be used to issue refunds to an attacker.
- Rotation without downtime. A rolled API key stays valid for up to 7 days, and a rolled webhook secret for up to 24 hours. The difference is deliberate: an API key may be deployed across many services, owned by different teams, with weekly release cycles, while a webhook secret lives in one endpoint its owner can redeploy in a day. See API Authentication.
Access
- Production access is just-in-time, approved and time-boxed. Standing access means every engineer's laptop is a path to customer data.
- Every read of sensitive data through support tooling is an audited event: who, what, when and why. Audits aren't only for catching abuse. They answer the customer who asks who looked at their account.
- Test and staging use synthetic data. A production copy in staging is production data with weaker controls. See Environments.
References
Specifications
- PCI Security Standards Council: Document Library, PCI DSS requirements and scope
Reference
- Wikipedia: Luhn algorithm, detecting card numbers in logs
In practice
- Stripe: Integration security guide, why tokenized integrations cut PCI obligations, and which card details are safe to store
- Stripe: API keys, key prefixes, restricted keys, show-once and rotation
- OWASP: Logging Cheat Sheet, data to exclude from logs
- OWASP: Secrets Management Cheat Sheet, storing and rotating secrets
- GitHub: About secret scanning, prefixed keys that scanners can find