← back to all posts
Security Standards· PCI DSS 4.0· 5 min read

PCI DSS Requirement 10: reading access history with a query

Requirement 10 asks who accessed cardholder data and when. This post shows the queries that read that access history from one recorded graph.

The QSA — your Qualified Security Assessor — shows up for the annual assessment. They want Requirement 10 evidence for one cardholder account number. Six-month lookback. Every access, every actor, every authorization method, every masking decision. They want it for PAN 4111-****-****-1234. They want it within twenty days.

In most stacks, this is a forty-five-day project. Your cardholder access logs are in eight systems. Your payment gateway. Your tokenization service. Your fraud platform. Your call-center recorder. Your data warehouse. Your customer-service tool. Your refunds processor. Your chargeback handler. Reconciling timestamps across eight systems is hard. Reconciling them in a way that the auditor will accept is harder.

And if you miss a single access, the auditor finds it, and you fail PCI. Losing your merchant ability is existential. There's no graceful way to recover from a non-conforming PCI report — you go back into remediation, you lose card-processing capability for the duration, and you accept whatever penalty your acquirer adds.

What Req 10 + Req 11 actually need

The two requirements are tangled. Req 10 wants the access log. Req 11 wants proof that the access controls actually work — not just that the log exists, but that unauthorized reads were prevented from happening.

InvariantDB's field-level ACLs return null for a property the caller may not read and append each read denied in Cypher to a hash-chained ACL event log. Allowed reads are not logged by the engine, so the Requirement 10 access records come from the access events your application writes to the graph, as in the query below.

The one-query reconstruction

MATCH (pan:CardholderData)
WHERE pan.token = 'tok_4111xxxxxxx1234'
OPTIONAL MATCH (pan)<-[a:ACCESSED]-(actor:User)
WHERE a.ts >= $windowStart
OPTIONAL MATCH (a)-[:AUTHORIZED_BY]->(auth:Method)
OPTIONAL MATCH (a)-[:FOR_PURPOSE]->(p:Purpose)
RETURN pan, collect(a), collect(actor), collect(p)

Pass $windowStart, the start of the six-month window, in the same format that a.ts stores. The query returns the access events your application recorded for the tokenized PAN, each with its actor, authorization method and declared purpose. In the illustrative walkthrough: forty-two reads by the fraud team, twenty-nine by the call center after step-up authentication, and two by the refunds processor.

The Req 11 evidence

The harder question is whether the access controls are actually firing. db.aclEvents returns the Cypher reads that field ACLs or purpose rules denied, and the searches and node creates they refused, with the principal, label, property, action and reason. Allowed reads are not logged, so the log shows enforcement, not a full access history. db.verifyAclChain() re-checks that log’s hash chain and reports the first event whose hash no longer matches. The log lists denied accesses per principal, not per cardholder record.

The CDE boundary walk

The third question is where the PAN flowed through. If each processing step is linked with DERIVED_FROM edges, db.derivedFrom returns the tokenization, masking, fraud-model and storage steps recorded for the PAN.

The GDPR + PCI dual compliance

The trickiest moment in PCI is when GDPR shows up too. A customer asks for their data deleted under privacy law. But PCI requires retention of cardholder access logs for fraud investigation. Most stacks have to violate one or the other.

Crypto-shred (per-subject encryption keys and key destruction) is in development. Contact us before relying on it for erasure requests.

The numbers

The figures in this post and in the walkthrough are illustrative.

Illustrative walkthrough on the PCI DSS tab.

See the walkthrough

Step through the illustrative walkthrough.

The full illustrative flow, ending with the chain verification. It is pre-rendered, runs in any browser, and does not connect to an engine.

Open the Security Standards walkthrough →