
Compliance
PCI DSS Security Metrics: The 4.0.1 Clocks You Must Prove
PCI DSS 4.0.1 sets intervals, not metrics: daily log review, weekly file checks, one month for critical patches. How to prove the first two continuously.
The standard asks for something narrower than a dashboard: specific intervals, on specific populations, examined by specific testing procedures. This article maps those intervals, corrects a common misreading of the patch clock, and builds the open-source pipeline — Wazuh, Suricata, Loki, Prometheus — that turns the log-review and file-integrity clocks into series that alert when they stop.
PCI DSS sets clocks, not metrics
PCI DSS v4.0.1, published by the PCI Security Standards Council in June 2024 as an update to v4.0, contains no requirement called mean-time-to-patch and none called log-review completion rate. Its testing procedures examine intervals and evidence. An assessor checking 11.3.1 examines internal scan results from the last 12 months to verify scans happened at least once every three months. An assessor checking 6.3.3 compares the installed patches against the most recent patch information.
A metric is how an operator proves, on day 200 of the assessment cycle as well as day 364, that an interval was met: it goes red the moment a clock slips and records how long it stayed red.
Two requirements make this unavoidable. 10.4.1.1 requires automated mechanisms to perform audit log reviews — a best practice until 31 March 2025 and required since. And 10.7.2, also required since that date, requires that failures of critical security control systems are detected, alerted and addressed promptly. Its list includes change-detection, audit logging and audit log review mechanisms, so the evidence pipeline is itself monitored. Scope boundaries are their own problem, covered in the PCI DSS audit-scope article; this one assumes the system-component inventory exists.
The requirement-to-clock map
Each line gives the requirement, its interval, the series or record that evidences it, and what the assessor examines.
- 10.4.1 / 10.4.1.1 — daily, automated. Four populations reviewed at least once daily: all security events; logs of components that store, process or transmit CHD and/or SAD; logs of all critical system components; logs of servers and components performing security functions (network security controls, IDS/IPS, authentication servers). Evidence: the pipeline watchdog,
absent(wazuh:alerts:count5m), and per-agent liveness from the 6009 scan-end line — neither shows the four populations were reviewed; that stays an assessor judgement. The assessor examines the log review mechanisms and interviews personnel. - 10.4.2 / 10.4.2.1 — periodic, your interval. Logs of all other components are reviewed at a frequency your targeted risk analysis under 12.3.1 defines. Evidence: the same liveness series, with the window set from the risk analysis.
- 10.4.3 — addressed. Exceptions and anomalies found during review are addressed. Evidence: the ticket trail, not a series.
- 10.5.1 — 12 months, three immediate. Audit log history retained at least 12 months, the most recent three immediately available for analysis. Evidence: storage retention configuration and a query that returns data from month 11.
- 10.7.2 / 10.7.3 — on failure. Control failures generate an alert (10.7.2.b), and the response documents the failure's duration, date and time from start to end. The alert time is detection time, not the failure's start: the 10.7.3 start is when the control stopped working, and cause, remediation and the other 10.7.3 bullets are written down outside Prometheus.
- 11.5.1 — no fixed interval. IDS/IPS monitors all traffic at the CDE perimeter and at critical points inside it, alerts personnel, and keeps engines, baselines and signatures current.
- 11.5.2 — weekly. A change-detection mechanism alerts personnel to unauthorized modification of critical files and performs critical file comparisons at least once weekly. Evidence: per-agent FIM scan-completion staleness.
- 6.3.3 — one month of release, critical only. Covered next; it is the most misquoted.
- 11.3.1 / 11.3.2 — every three months. Internal scans, high-risk and critical findings resolved and rescanned. External scans by a PCI SSC Approved Scanning Vendor (ASV), with vulnerabilities resolved, the ASV Program Guide requirements for a passing scan met, and rescans until the scan passes; 11.3.2 is not eligible for the customized approach.
- 8.3.4 — configuration. Lockout after not more than 10 invalid attempts, held for at least 30 minutes or until the user's identity is confirmed.
6.3.3 is one month for critical only — highs get your own timeframe
A commonly repeated summary of PCI DSS says critical and high vulnerabilities must be remediated within 30 days. Under v4.0.1, that is wrong twice. 6.3.3 reads: patches/updates for critical vulnerabilities, identified through the 6.3.1 risk ranking, are installed within one month of release. Every other applicable patch is installed within a time frame set by the entity's own assessment of the risk.
v4.0 had widened the requirement. The PCI SSC Summary of Changes from v4.0 to v4.0.1 reverts 6.3.3 to v3.2.1 language covering patches for critical vulnerabilities, and removes the v4.0 wording that applied it to high-security patches. And the clock starts at the patch's release, not at the day a scanner noticed the gap.
High-risk patches still have an install clock: the second bullet of 6.3.3. 6.3.1 requires risk rankings that identify, at a minimum, every vulnerability considered high-risk or critical, and every applicable patch that is not critical — high-risk included — is installed within the timeframe the entity documents from that risk assessment. 11.3.1 is a separate obligation: internal scans at least once every three months, resolution of high-risk and critical findings, and rescans confirming they are resolved. Three months is the scan interval, not an install deadline. Lower-ranked scan findings fall under 11.3.1.1, addressed per a targeted risk analysis under 12.3.1.
Running the scanner that feeds 11.3.1 is its own discipline, covered in the vulnerability management life cycle when you run the scanner; for hosts that cannot reach upstream repositories, air-gapped patching with Katello is where the one-month window gets spent.
A self-hosted pipeline that produces the numbers
Every component here is open source and was current at the time of writing: Wazuh v4.14.10 (v5.0.0 is still a release candidate), Loki v3.7.10 and Prometheus 3.16.0.
- Wazuh agents on each CDE host run file integrity monitoring (syscheck) and collect the software inventory that Wazuh vulnerability detection matches against the Wazuh CTI repository, or an offline, locally hosted copy of it.
- The Wazuh manager writes alerts to
/var/ossec/logs/alerts/alerts.json(and forwards them to the Wazuh indexer with Filebeat). Only alerts at or abovelog_alert_level, default 3, are written. - Suricata, the OISF's open-source network IDS/IPS engine, sits at the CDE perimeter and critical internal points for 11.5.1.
- Grafana Alloy ships two streams to Loki: the manager's
alerts.json, and each agent's own/var/ossec/logs/ossec.log. Promtail, its predecessor, has reached end of life. Alloy runs on every CDE host, so it is an in-scope system component to inventory and patch. - The Loki ruler runs recording rules over those streams and remote-writes the results to Prometheus. Alertmanager routes the alerts to people.
Two links need explicit configuration. Prometheus refuses remote writes unless started with --web.enable-remote-write-receiver, which defaults to false. And the Loki ruler needs remote_write enabled with a client pointing at Prometheus's /api/v1/write; Loki's alerting docs list Prometheus from v2.25.0, Grafana Mimir and the Thanos Receiver as compatible targets.
ruler:
# ... other settings
remote_write:
enabled: true
clients:
prometheus:
url: http://prometheus.monitoring.svc:9090/api/v1/writeOne requirement deliberately gets no Prometheus alert: 6.3.3. The vulnerability records Wazuh produces carry a CVE, its publication date (published_at) and when it was found on a host — not when the fixing patch was released. 6.3.3 stays what its testing procedure describes: a documented comparison of installed patches against the most recent patch information, produced on a schedule and kept as evidence.
Daily review as a measured event, and the watchdog 10.7.2 demands
Alerts per agent per day fails exactly when it matters: at the default alert level a healthy, quiet host can go a full day without an alert. And a Loki count_over_time recording rule writes no sample at all, not a zero, for an agent with no matching lines. A quiet host and a dead host therefore look identical in the alert stream.
The design separates two questions. Which agents should exist comes from a static recording rule, pci:cde_agent:expected, one series per in-scope agent, generated from the system-component inventory and changed only through it — so a dead agent never drops out. Which agents are alive comes from a signal every healthy agent must emit on a schedule: the line (6009): File integrity monitoring scan ended. that the agent writes to its own ossec.log after every scan, about every 12 hours at Wazuh's default syscheck frequency of 43200 seconds.
groups:
- name: wazuh-pci-recording
interval: 1m
rules:
- record: wazuh:alerts:count5m
expr: |
sum by (agent) (
count_over_time({job="wazuh-alerts"} | json agent="agent.name" | __error__="" [5m])
)
- record: wazuh:fim_scan_ended:count1h
expr: |
sum by (agent) (
count_over_time({job="wazuh-agent-log"} |= "(6009): File integrity monitoring scan ended." [1h])
)The second rule matches the agent's own log message, not an alert: Wazuh does not document the 6009 line as an alerts.json event. One match is critical: the agent label Alloy sets on the agent-log stream must carry exactly the value of agent.name in alerts.json — the Wazuh agent name, not a hostname or IP. If the values differ, no unless on (agent) match succeeds and every agent alerts as stale.
groups:
- name: cde-inventory
rules:
- record: pci:cde_agent:expected
expr: vector(1)
labels:
agent: pay-api-01
- record: pci:cde_agent:expected
expr: vector(1)
labels:
agent: pay-api-02
- record: pci:cde_agent:expected
expr: vector(1)
labels:
agent: tokenizer-01
- name: pci-dss-clocks
rules:
- alert: WazuhAlertStreamAbsent
expr: absent(wazuh:alerts:count5m)
for: 26h
labels:
requirement: "10.7.2"
annotations:
summary: No Wazuh alert from any agent reached Loki for 26 hours, longer than a healthy quiet estate stays silent.
- alert: CDEAgentSilent
expr: |
pci:cde_agent:expected
unless on (agent)
count by (agent) (max_over_time(wazuh:fim_scan_ended:count1h[25h]))
for: 30m
labels:
requirement: "10.7.2"
annotations:
summary: An in-scope agent has not finished an FIM scan in 25 hours.
- alert: CDEAgentFIMScanStale
expr: |
pci:cde_agent:expected
unless on (agent)
count by (agent) (max_over_time(wazuh:fim_scan_ended:count1h[7d]))
for: 1h
labels:
requirement: "11.5.2"
annotations:
summary: An in-scope agent has no completed FIM scan in 7 days.The inventory group sets the agent name as a rule label, so the file regenerates cleanly from the inventory. Absence is always a set difference — unless on (agent) against the static set — never == 0, because there is no zero to compare against.
WazuhAlertStreamAbsentis a coarse watchdog on the review pipeline itself. Prometheusabsent()returns 1 only when the input vector is empty;for: 26hholds it pending past the full quiet day a healthy estate can produce at the default alert level. A quiet hour is not a failed review and must not page as one. It is a slow backstop; the per-agent rules carry liveness.CDEAgentSilentfires for an in-scope agent with no scan-end line in 25 hours — roughly two missed scans at the 43200-second default. Change the frequency and this window must be re-derived.CDEAgentFIMScanStaleis the 11.5.2 evidence check. At default settingsCDEAgentSilentfires first; this rule exists so the weekly clock has its own labelled record, and still holds if someone lengthens the liveness window.
Both per-agent alerts resolve only when the agent logs a scan-end line again, or when it leaves the inventory — a reviewable change in git. Do not add keep_firing_for, which stretches the firing interval past the failure. The firing interval is evidence toward the start-to-end record 10.7.3 asks for, not the record itself. The alert time is detection time, and the for backdate is only Prometheus pending time: for CDEAgentSilent the condition turns true about a day after the last scan-end line. Record the start of the control failure instead — the last (6009) line plus the scan interval, or the manager's 504/506 alert for that agent, whichever is earlier.
File integrity: weekly comparison, alerting on change
11.5.2 has two halves: alert personnel to unauthorized modification of critical files, and compare critical files at least once weekly. The scheduled scan covers the second at its 43200-second default. For the first, realtime monitoring uses inotify on Linux, works only on directories, not individual files, and is off by default — enable it where a change must alert when written, not at the next scan.
<ossec_config>
<syscheck>
<disabled>no</disabled>
<frequency>43200</frequency>
<directories check_all="yes">/etc,/usr/bin,/usr/sbin</directories>
<directories check_all="yes">/bin,/sbin,/boot</directories>
<directories check_all="yes" realtime="yes">/opt/payments/bin,/opt/payments/conf</directories>
<ignore>/etc/mtab</ignore>
<ignore>/etc/adjtime</ignore>
</syscheck>
</ossec_config>The seven-day window is applied in Prometheus with max_over_time over the remote-written series, not in LogQL. No Loki ruler query spans more than an hour, well inside Loki's max_query_length (default 30d1h), and Prometheus's default 15-day retention holds the week the rule looks back over. The staleness rule uses the same static set as the liveness rule; building it from agents seen in alerts would let a stopped agent leave the set and clear its own alert.
Lockout thresholds are configuration; invalid attempts are the metric
8.3.4's testing procedures check that accounts lock after not more than 10 invalid logon attempts and stay locked for at least 30 minutes or until the user's identity is confirmed. That is a configuration fact, verified by examining settings, not a rate to trend. It is user-ID lockout on in-scope system components; Linux PAM is one example. Its applicability note excludes user accounts on point-of-sale terminals that have access to only one card number at a time to facilitate a single transaction.
On Linux, pam_faillock's defaults fail it: unlock_time defaults to 600 seconds, ten minutes. deny defaults to 3, which is stricter than required and fine. Three further details decide whether the file does anything:
faillock.confhas no effect unlesspam_faillockis in the PAM stack; on RHEL-family systems, authselect'swith-faillockfeature adds it.- root is exempt unless
even_deny_rootis set. fail_intervaldefaults to 900 seconds: withdeny = 10, ten failures spread beyond 15 minutes never lock. 8.3.4 states no window; record and defend the one you choose.
# 8.3.4: not more than 10 attempts, locked at least 30 minutes
deny = 10
fail_interval = 900
unlock_time = 1800
even_deny_root
root_unlock_time = 1800
auditThe measurable part is 10.2.1.4: audit logs capture all invalid logical access attempts. A per-agent recording rule counting them is cheap — a 10.4.1 security-event signal, not a lockout KPI the standard never asked for. To catch brute force, alert on spikes against a baseline; 10.4.1.1's own guidance calls a baseline of normal audit activity critical to an automated review.
Where self-hosted metrics mislead an assessor
- Agent-to-manager disconnection. The liveness signal goes around the manager, so an agent cut off from it stays green while the alert half of 11.5.2 silently stops — a change-detection failure 10.7.2 requires be detected. Close it with the manager's own rules in 0015-ossec_rules.xml: 504, Wazuh agent disconnected, and 506, Wazuh agent stopped, are level 3, so they reach
alerts.jsonat the defaultlog_alert_level. Joining them per agent againstpci:cde_agent:expectedis the natural extension, not in the rules above. - Clock skew. 10.6.1 requires synchronized system clocks. A skewed agent stamps lines outside the windows, and the alert intervals become unreliable as 10.7.3 evidence.
- Retention. 10.5.1 needs 12 months of history, three immediately available. Loki retention is off unless
compactor.retention_enabledis true, and the excerpt above does not set it. Keep a durable copy of the alert record, not a 15-day Prometheus. - Indicative mappings. Wazuh tags rules as
pci_dss_plus a requirement number, states the mappings are indicative and do not by themselves certify compliance, and writes its PCI guide against v4.0, not v4.0.1. Treat the tags as a starting index. - Configuration drift. 10.4.1.1's guidance says to keep logging tools aligned with environment changes. The generated inventory group is that alignment, made reviewable.
- The dashboard as the control. A green Grafana panel is evidence that a control ran, not the control; interviews show which one the team watches.
Exit ramp and the long game
Validate both rule files in CI. promtool parses expr as PromQL, so it cannot check LogQL; the Loki file needs lokitool, which replaced cortextool for Loki 3.1 and later.
# PromQL: alerting and inventory rules
promtool check rules /etc/prometheus/rules/pci-dss.rules.yml
# LogQL: parse the rules without rewriting the file (lokitool v3.7.10)
lokitool rules lint --dry-run /etc/loki/rules/fake/wazuh-pci.yaml
# Best-practice checks on recording-rule names
lokitool rules check /etc/loki/rules/fake/wazuh-pci.yamlCurrent Loki docs name the command rules format; v3.7.10 still ships lint — pin the tool version alongside the rules. Everything that defines a metric here is a text file. Because the ruler uses standard remote write, the same rules move to Mimir or Thanos without being rewritten. The evidence outlives the backend.
That is the long game for a yearly assessment. If the clocks live in git — reviewed when changed, linted in CI, regenerated from the inventory — each assessment examines the same definitions with another year of firing intervals behind them. For the wider stack these rules run on, see self-hosted observability with Prometheus, Grafana and Loki, and for getting Kubernetes control-plane events into the same Wazuh, Kubernetes audit logs into a SIEM you operate. SAQ versus full report is settled in when the SAQ path closes.
§FAQ/Common questions
Frequently asked
Does PCI DSS 4.0.1 require specific security metrics?
No. It sets intervals and populations — daily review of four log populations, weekly critical-file comparison, one month to install critical patches, quarterly internal and ASV scans — and its testing procedures examine evidence that those intervals were met. Metrics are the operator's way of producing that evidence continuously.
Is the PCI DSS 6.3.3 deadline 30 days for critical and high vulnerabilities?
Not under v4.0.1. 6.3.3 requires patches for critical vulnerabilities to be installed within one month of release. The Summary of Changes reverted the v4.0 wording that extended it to high-security patches. Every other applicable patch, high-risk included, is installed within a timeframe the entity sets from its own risk assessment and documents. Separately, 11.3.1 requires internal scans every three months with high-risk and critical findings resolved and rescanned; that scan interval does not replace the install timeframe.
How do you tell a quiet Wazuh agent from a dead one?
Not by alert volume: a quiet host may produce no alerts all day, and a Loki count_over_time rule writes no sample rather than a zero. Compare a static, inventory-generated set of expected agents against a periodic signal every healthy agent must emit, such as the 6009 FIM scan-ended line in its own ossec.log.
Does the pam_faillock default satisfy PCI DSS 8.3.4?
No. pam_faillock's default unlock_time is 600 seconds, and 8.3.4 requires a lockout of at least 30 minutes or until identity is confirmed. Set unlock_time = 1800 or higher, keep deny at 10 or lower, and make sure pam_faillock is actually in the PAM stack.
Further reading
- PCI DSS Audit Scope: An Evidence Problem, Not a Network One
- Kubernetes Audit Logs Into a SIEM You Operate: Wazuh
- Self-Hosted Grafana vs Datadog: Kubernetes Observability
- Vulnerability Management Life Cycle When You Run the Scanner
- Air-Gapped Patching with Katello: Mirrors and Evidence
- PCI DSS Self-Assessment: When the SAQ Path Closes
Executive Briefing
Thirty minutes to clarify your infrastructure risk
Walk us through your vendor footprint and regulatory constraints. We will tell you honestly where sovereignty creates leverage — and where it does not. No pitch deck. No obligation.