Skip to main content

Three PCI DSS Controls I Shipped Before Lunch

· 3 min read
Sam Cookes
Senior Dev at cookedup.sh

My hands woke up at 8am and decided today was compliance day. By noon they had shipped three PCI DSS v4.0 controls and my Neovim session had not closed once. Compliance is not glamorous. But it is the difference between "we take payments" and "we used to take payments."

Control 1: Security headers on every response

The platform serves payment pages through CloudFront. Every response now carries a full security header set: Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, and Referrer-Policy.

The CSP is strict. Script sources are locked to the origin domain. No inline scripts. No eval. A report-to directive catches violations before attackers do. HSTS forces TLS for a full year with subdomains included. X-Frame-Options blocks clickjacking. These are not optional decorations. They are the baseline.

The implementation is a CloudFront response headers policy, not application code. That means every path gets the headers, not just the ones a developer remembered to instrument. Infrastructure-level enforcement beats application-level hope every time.

Control 2: PAN redaction in every log

PCI DSS v4.0 Requirement 3.3: primary account numbers must not appear in logs. Period.

The redaction layer sits in the Lambda logging pipeline, between the application and CloudWatch. It uses the Luhn algorithm to detect 13-to-19-digit sequences that validate as card numbers, then masks them to ****LAST4 before the log event is written. This is a defensive layer, not the primary protection. The primary protection is never handling raw card data in the first place (Stripe's tokenized flows handle that). But defense in depth means assuming the primary protection will fail someday.

The test suite covers the edge cases that matter: valid Visa, Mastercard, and Amex patterns all get masked. Phone numbers, timestamps, and random digit strings that fail Luhn validation pass through untouched. A false positive in log redaction is annoying. A missed card number in CloudWatch is a breach.

Control 3: Payment page tamper detection

PCI DSS v4.0 Requirement 11.6.1: detect unauthorized changes to payment pages.

The CI pipeline now compares live response headers and script domains against a known-good baseline on every deploy and on a daily schedule. If the headers change, if a new script domain appears, if anything differs from baseline, an alert fires. This catches CDN misconfigurations, injected scripts, or unauthorized header changes before a customer's card details are at risk.

The detection runs in CI, not in the browser. Client-side integrity checks can be disabled by the same attacker who injected the script. Server-side comparison against a committed baseline cannot.

The pattern

Three controls. Three requirements. One morning. None of them are individually complex. CSP is a header policy. PAN redaction is a regex with Luhn validation. Tamper detection is a diff against a baseline. The hard part is not the code. The hard part is doing it before you accept your first payment instead of scrambling after an auditor asks why you did not.

My hands typed all three before the coffee got cold. They have opinions about PCI DSS and those opinions are: do it early, do it at the infrastructure layer, and do it before someone makes you.

-- Sam, whose hands have now implemented more compliance controls than most startups have engineers, and they are not done yet

Relevant xkcd