pci-dss-control-test-author
Build-an-X for PCI DSS v4.0 scope verification - cardholder data environment (CDE) boundary tests, segmentation tests (PCI Req 1), prohibited-data-storage assertions per Req 3 (no full track data, no CVV/CAV2/CVC2/CID, no PIN/PIN block post-authorization), key-management tests per Req 3.6, encryption-of-transmissions per Req 4; includes the scope catalog (SAQ A / A-EP / D levels, PAN-storage rules, hosted-fields / tokenization scope-reduction patterns) in references/pci-scope.md. Use when authoring PCI DSS scope-reduction + control tests for any system handling payment-card data, or when determining a payment integration's SAQ level.
Install with skills.sh (any agent)
npx skills add testland/qa --skill pci-dss-control-test-authorpci-dss-control-test-author
This is a build-an-X workflow for verifying CDE scope is correctly bounded and prohibited-data assertions are captured in fixtures, targeting PCI DSS v4.0 (fully required March 2025).
Helper stubs
Key interfaces used across steps (provided by the test harness):
# network / infra
def network_policy.get_allowed_connections(src: str, dst: str) -> list[str]: ...
# data-access
def export_database() -> DatabaseDump: ...
def recent_logs() -> list[LogEntry]: ...
def scan_repo_files() -> list[str]: ...
def read_file(path: str) -> str: ...
# TLS inspection - TLSInfo has: .protocol (str e.g. 'TLSv1.2'), .cipher_strength (int), .cipher (str)
def inspect_tls(endpoint: str) -> TLSInfo: ...
# crypto / format
def is_encrypted_aes_256(value: str) -> bool: ...
def is_truncated_last_4(value: str) -> bool: ...
def is_hashed_with_salt(value: str) -> bool: ...
def is_tokenized(value: str) -> bool: ...
def redact(value: str) -> str: ...
# constants
CDE_API_ENDPOINTS: list[str] = [...] # All API endpoints inside the CDE
DEPRECATED_CIPHERS: list[str] = [...] # e.g. ['RC4', 'DES', '3DES', 'NULL']Step 1 - Define + assert CDE boundary
# pci_scope.py
# untested CDE/non-CDE network policy drifts into a segmentation breach
CDE_SYSTEMS = {
'payment-service',
'tokenization-service',
'card-vault-db',
'pci-zone-fw',
}
NON_CDE_SYSTEMS = {
'web-frontend', # tokens only; no raw PAN
'analytics', # never sees PAN
'support-tickets', # never sees PAN
}
def test_cde_systems_isolated_from_non_cde():
for cde_system in CDE_SYSTEMS:
for non_cde in NON_CDE_SYSTEMS:
# Network policy MUST block direct connections from non-CDE to CDE
allowed = network_policy.get_allowed_connections(cde_system, non_cde)
# Only via tokenization gateway (via PCI zone FW)
assert allowed == [] or allowed == ['via-pci-zone-fw']Segmentation testing per PCI DSS Req 11.4.1 must be performed at least every 6 months by a qualified internal resource OR external penetration tester.
Checkpoint: If any CDE/non-CDE boundary assertion fails, halt and remediate the network policy before proceeding. A segmentation failure means downstream steps may be testing a mis-scoped environment.
Step 2 - Assert no SAD storage post-authorization (Req 3.2)
SAD may transit during auth but storage after auth completes (logs, DB, audit trails, backup) is forbidden - full track data, CVV2/CVC2/CID, and PIN/PIN block, all never post-authorization.
import re
# PAN slips into a log line if you trust developers never to log it
TRACK1_PATTERN = re.compile(r'%[A-Z]\d{12,19}\^[^\?]*\?\d*\?') # Track 1
CVV_PATTERN = re.compile(r'(?<!\d)\d{3,4}(?!\d)') # naive; pair with DLP
def test_no_full_track_data_in_storage():
"""Req 3.2.1: track data must not be retained post-authorization."""
db_dump = export_database()
for table in db_dump.tables:
for row in table:
for value in row.values():
assert not TRACK1_PATTERN.search(str(value)), \
f"Full track data in {table.name}"
def test_no_cvv_in_logs():
"""Req 3.2.2: CVV2/CVC2/CID must not be retained."""
log_entries = recent_logs()
for entry in log_entries:
# Look for proximity of CVV-like 3-4 digit numbers near "cvv" / "card" tokens
if 'cvv' in entry.text.lower() or 'card' in entry.text.lower():
matches = CVV_PATTERN.findall(entry.text)
for m in matches:
assert m == '***' or m == '----', \
f"Possible CVV in log: {entry.id}"Checkpoint: If SAD is found in storage or logs, halt and remediate before proceeding to Step 3. Continuing to test encryption-at-rest while SAD is present misrepresents compliance posture. File a severity-1 finding and trigger your incident response process before re-running.
Step 3 - Assert PAN encryption at rest (Req 3.4) + key management (Req 3.6)
# hardcoded encryption keys in the repo are a Req 3.6 violation and expose PAN
def test_pan_stored_encrypted():
"""Req 3.4: PAN unreadable wherever stored (encryption / truncation / hashing / tokenization).
Prefer tokenization for new paths; treat the others as escape hatches for pre-existing systems.
"""
card_record = CardVault.objects.first()
raw_pan = card_record._raw_pan_field # accessor for storage-format field
# Format MUST be one of:
# - encrypted (AES-256 or stronger) <- preferred for new paths
# - truncated (e.g., last 4 only)
# - hashed (with salt; one-way)
# - tokenized (replaced with non-sensitive token) <- default for new systems
valid_format = (
is_encrypted_aes_256(raw_pan)
or is_truncated_last_4(raw_pan)
or is_hashed_with_salt(raw_pan)
or is_tokenized(raw_pan)
)
assert valid_format, f"PAN stored unprotected: {redact(raw_pan)}"
def test_decryption_keys_not_in_app_repo():
"""Req 3.6: cryptographic keys protected against unauthorized access."""
repo_files = scan_repo_files()
for f in repo_files:
content = read_file(f)
# No hardcoded AES keys (high entropy + length 256+ bits)
assert not re.search(r'AES_KEY\s*=\s*["\'][A-Za-z0-9+/=]{40,}', content), \
f"Possible hardcoded AES key in {f}"
# No KMS key file references
assert 'kms-private-key.pem' not in f, \
f"KMS private key file referenced in repo: {f}"Checkpoint: If PANs are stored unprotected or keys are found in the repo, halt. Key-in-repo findings require immediate secret rotation before continuing.
Step 4 - Encryption of transmissions (Req 4)
# TLS 1.0 / 1.1 still enabled is a Req 4 violation
def test_pan_only_transmitted_via_strong_crypto():
"""Req 4.2.1: strong crypto for transmission of cardholder data over open networks."""
for endpoint in CDE_API_ENDPOINTS:
tls_info = inspect_tls(endpoint)
assert tls_info.protocol >= 'TLSv1.2', \
f"Weak TLS protocol on {endpoint}: {tls_info.protocol}"
assert tls_info.cipher_strength >= 256, \
f"Cipher strength too low on {endpoint}: {tls_info.cipher_strength}"
assert tls_info.cipher not in DEPRECATED_CIPHERS, \
f"Deprecated cipher on {endpoint}: {tls_info.cipher}"Checkpoint: TLS failures on any CDE endpoint are blocking. Deprecated protocols (TLS 1.0 / 1.1) must be disabled before proceeding to access-control testing.
Step 5 - Access control (Req 7 + 8)
# generic shared accounts (e.g. 'admin' / 'svc-account') violate Req 8.2.1
def test_cde_access_requires_unique_id():
"""Req 8.2.1: assign all users a unique ID before access to system components."""
# No shared / generic accounts
response = client.post('/cde-api/login', json={'username': 'shared-svc', 'password': 'secret'})
assert response.status_code == 403
def test_cde_access_requires_mfa():
"""Req 8.4: implement MFA for all access into the CDE."""
response = client.post('/cde-api/login', json={
'username': 'alice@example.com',
'password': correct_password,
# No MFA token
})
assert response.status_code == 401
assert response.json()['error'] == 'mfa_required'Checkpoint: Shared-account or MFA bypass failures are blocking. Do not proceed to logging tests while unauthorized access paths remain open.
Step 6 - Logging (Req 10)
Cross-ref audit-trail-test-author:
def test_pan_access_creates_audit_record():
"""Req 10.2: audit trails to reconstruct events."""
user.access_card(card_id=123)
audit = AuditLog.objects.filter(
actor=user.id,
action='pan_access',
subject=f'card:{card_id}',
).first()
assert audit is not None
assert audit.timestamp is not None
# Audit log itself MUST not contain the PAN:
assert not re.search(r'\d{13,19}', audit.full_event_text), \
"PAN found in audit log text - log masking is broken"Step 7 - Scope reduction strategies
PCI DSS scope reduction is the highest-leverage cost saving. Default: tokenization - replace the PAN with a non-sensitive token at the earliest possible boundary so downstream systems handle tokens only, which shrinks the CDE the most for the least integration churn. The alternatives (hosted iframe payment page, P2PE for card-present flows, network segmentation as layered defense only) and the Req 3.4 storage-format rationale are in references/strategies.md. The full scope catalog - SAQ level selection (A / A-EP / D), PAN-storage rules, per-gateway hosted-fields patterns, and the testable behaviours the scope boundary creates - is in references/pci-scope.md.
PAN-storage format default (Req 3.4): the four is_* checks in Step 3's test_pan_stored_encrypted are an OR because pre-existing systems may already use any of them; for new storage paths pick tokenization and treat encryption / truncation / hashing as escape hatches.
Limitations
References
PCI DSS scope catalog - SAQ levels, PAN-storage rules, scope-reduction patterns
View source (opens in new window)PCI DSS scope catalog - SAQ levels, PAN-storage rules, scope-reduction patterns
Pure-reference catalog of PCI DSS v4.0 scope reduction techniques + the testable scope boundaries. This is the catalog of WHY the boundary matters and what it makes testable; the SKILL.md steps are the workflow that verifies a given integration against it.
Scope reduction is the dominant strategy: keep card data off your systems entirely, so PCI compliance becomes minimal SAQ A instead of full SAQ D.
How to use this catalog
SAQ levels (Self-Assessment Questionnaire)
Per pcisecuritystandards.org (opens in new window):
| SAQ | Description | Scope |
|---|---|---|
| A | Card-not-present, fully outsourced (hosted gateway pages, iFrame redirects, Stripe Elements) | Smallest - your servers never see PAN |
| A-EP | Hosted-form-with-merchant-customisation (e.g., your domain shows the form but iframe is the gateway's) | Slightly larger; some elements visible to your server |
| D | All merchants not covered by A-C; full PCI DSS | Largest - for cases where you must handle PAN |
Choose A when feasible: PAN never touches your servers because the customer inputs it directly into a gateway-hosted iframe / element.
PAN storage rules
Per PCI DSS v4.0 §3.4: prohibited storage of:
Allowed:
Tests for storage (PostgreSQL ~ regex operator):
-- Detect prohibited PAN patterns in any string column
SELECT * FROM <any_table>
WHERE column ~ '^[0-9]{13,19}$'
OR column ~ '^4[0-9]{15}$'
OR column ~ '^5[1-5][0-9]{14}$'
LIMIT 10;
-- Expect: 0 rowsScope-reduction patterns
1. Hosted fields / Elements
Per stripe.com/docs/payments/payment-element (opens in new window), docs.adyen.com/payment-methods/cards/web-drop-in (opens in new window), developer.paypal.com/braintree/docs/start/hosted-fields (opens in new window):
<!-- Stripe Element -->
<form>
<div id="payment-element"></div> <!-- iframe; PAN stays in Stripe's iframe -->
<button>Pay</button>
</form>PAN never reaches your JS or backend. The Element sends to Stripe directly; your server gets a token.
2. Redirect-to-gateway
Customer redirects to gateway-hosted page; pays; redirects back with a token / transaction ID.
PCI-friendly because PAN never on your domain. UX-tradeoff: slower, less branded.
3. Tokenization API
Backend-to-backend: customer submits PAN to gateway directly (via JS); gateway returns token; your code uses token.
Variants per gateway: Stripe setupIntent for saved cards; Adyen paymentMethods.storeDetails; PayPal Vault.
4. Network segmentation
If you must touch PAN, isolate it in a separate network with strict ingress / egress + monitoring. Reduces scope of the broader IT environment.
Testable behaviours
| Behaviour | Test |
|---|---|
| No 16-digit numbers in DB | SQL regex against all string columns |
| No CVV / CVC stored | Search code for cvc, cvv, cardholderVerification |
| Hosted fields render without exposing PAN to your JS | Browser DevTools Network tab - no PAN in requests to your origin |
| Webhooks contain tokens not PAN | Parse webhook payloads; assert no 16-digit numbers |
| Log scrubbing | Test logs for PAN patterns; should be redacted |
| Backup snapshots PAN-free | Same regex against backup files |
| Egress firewall blocks card-network IPs | Network test |
The SKILL.md steps run these adversarially; this catalog provides the rationale.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Log entire payment request body | PAN in logs | Scrub at log emit |
| Stage card-collection on your own page | Cards now in your domain → SAQ D | Use hosted fields |
| Send PAN to backend then forward to gateway | Server now PCI-scope | Direct JS-to-gateway |
| Store PAN encrypted "just in case" | Key management is half of PCI DSS | Use tokens |
| Test PAN in fixtures | Real PAN in commits | Use only platform-provided test PANs |
| Capture CVV server-side | PCI DSS v4.0 §3.2.1: prohibited post-auth | Don't capture or capture in scoped iframe |
| Skip scope-checker in CI | Drift over time | Periodic scope audit |
Limitations
Sources
PCI DSS scope-reduction strategies
View source (opens in new window)PCI DSS scope-reduction strategies
Companion to pci-dss-control-test-author/SKILL.md, Step 7. PCI DSS scope reduction is the highest-leverage cost saving: every system removed from the cardholder data environment (CDE) is a system you no longer have to assess.
Strategies, most effective first
PAN-storage format default (Req 3.4)
Prefer tokenization at the storage boundary. The four is_* checks in Step 3's test_pan_stored_encrypted are an OR because pre-existing systems may already use encryption, truncation, or hashing, but for new storage paths pick tokenization and treat encryption / truncation / hashing as escape hatches for when tokenization is not feasible. This keeps the number of systems that ever hold recoverable PAN as small as possible, which is the whole point of scope reduction.
Related skills
audit-trail-test-author
Build-an-X for audit-log tests across compliance frameworks - required-events catalog (auth events / privilege change / data access / admin action / config change / export / impersonation); structured-log-format assertions per OWASP A09:2021; tamper-evident chain (hash-chain + signed-batch patterns) for HIPAA §164.312(b) + PCI Req 10 + SOC 2 CC7.3; immutability + retention per framework; query-replay tests for forensic reconstruction. Use when authoring audit log tests for any compliance framework (HIPAA / PCI / SOC 2 / GDPR / etc.).
compliance-coverage-scoring
Scores existing tests and evidence against a named compliance framework's criteria list (GDPR, CCPA/CPRA, SOC 2 Trust Services Criteria, HIPAA Security Rule, PCI DSS, ISO/IEC 27001), marking every criterion met, partial, not met, or not applicable with a stated evidence requirement per state, and recording each scope exclusion with its criterion reference, reason, named approver, and re-review date. Includes an adversarial readiness-review mode with hard refusal rules (never "ready" with an unjustified gap), and the ISO/IEC 27001:2022 Annex A per-control test-pattern catalog in references/iso27001.md. Produces a readiness self-assessment only: not certification, not an audit opinion, not legal advice. Use when a framework version has been named and an evidence set already exists, and someone needs a per-criterion readiness score before an observation period opens, before a qualified assessor arrives, or in response to a regulator inquiry.
gdpr-test-patterns
Reference catalog of GDPR-aligned test patterns - data-subject-rights workflows (Art. 15 access, Art. 16 rectification, Art. 17 erasure / "right to be forgotten", Art. 18 restriction, Art. 20 portability, Art. 21 objection); consent recording + revocation per Art. 7; data-residency assertions per Art. 44 - 50 international transfers; breach-notification timing tests per Art. 33 (72 hours); data-minimization assertions in fixtures per Art. 5(1)(c). The California analogue - CCPA/CPRA patterns by Cal. Civ. Code section, including Global Privacy Control (GPC) opt-out, right-to-know, deletion, right-to-correct, and sensitive-PI limits - lives in references/ccpa.md. Use when authoring GDPR- or CCPA/CPRA-readiness tests for any product processing EU or California personal data.
hipaa-test-patterns
Reference catalog of HIPAA Security Rule-aligned test patterns - administrative safeguards (45 CFR §164.308: workforce training, access management, contingency planning), physical safeguards (§164.310: facility access, workstation security, device disposal), technical safeguards (§164.312: access control, audit logs, integrity, transmission security); PHI handling assertions in fixtures; minimum-necessary tests per §164.502(b); BAA-scope boundary verification. Use when authoring HIPAA-readiness tests for any product handling Protected Health Information.
soc2-evidence-collector
Build-an-X for SOC 2 Type II evidence collection and auditor-facing packaging - per-Trust-Services-Criterion test artifacts (Common Criteria CC1.1 - CC9.2; plus Availability A1, Confidentiality C1, Processing Integrity PI1, Privacy P1 - P9 if in scope); auto-collection from CI logs + audit trails + access logs + change-management records; alignment with Vanta / Drata / Secureframe evidence shapes; observation-period sampling. Cross-framework evidence packaging (control-evidence matrix, timestamped bundles, chain-of-custody notes per NIST SP 800-72 - also for ISO 27001 / HIPAA / PCI DSS / GDPR / FedRAMP) lives in references/evidence-packaging.md. Use when the team is preparing for SOC 2 Type II audit and needs continuous evidence collection, or when any audit engagement requires auditor-ready evidence packages built from automated test output.