Implement PCI DSS version 4.0 technical requirements for payment card processing. Covers network segmentation, encryption at rest and in transit, key management, vulnerability management, and the engineering patterns that reduce cardholder data exposure.
PCI DSS v4.0 introduces a framework for engineering controls that enforce security and compliance across systems handling cardholder data. These controls are not just checklists; they are executable, verifiable, and continuously validated through engineering practices. They matter when systems must meet auditable standards for payment card processing, particularly in environments where changes occur rapidly and compliance cannot be a manual, periodic activity.
Every PCI DSS v4.0 engineering control begins with a clear mapping to the standard’s requirements. The control matrix is not a document but an executable artifact. Each requirement is tied to a set of technical controls that are implemented via code, configuration, and automation.
The core of the implementation is a controls.json file in the root of the source repository, which defines the active controls and their status:
{
"controls": [
{
"id": "1.1.1",
"name": "All systems must be hardened according to baseline",
"status": "active",
"implementation": {
"method": "ansible",
"playbook": "roles/security/hardening/tasks/main.yml",
"target": "all-inventory",
"last-run": "2024-03-15T14:23:11Z",
"result": "success",
"output": "/var/log/ansible/hardening-2024-03-15.log"
},
"verification": {
"type": "automated",
"check": "ansible-playbook --check --diff",
"interval": "daily",
"schedule": "0 3 * * *",
"report": "https://ci.example.com/verify/1.1.1"
}
},
{
"id": "3.2.4",
"name": "Encryption of data at rest must use FIPS 140-2 validated modules",
"status": "active",
"implementation": {
"method": "terraform",
"module": "modules/encryption",
"configuration": {
"kms_provider": "aws-kms",
"key_spec": "SYMMETRIC_DEFAULT",
"validation": "fips140-2"
},
"enforcement": "aws-ec2-encryption"
}
}
]
}
This file is versioned in Git and used by compliance automation pipelines to verify that each control is active and validated.
Each engineering control is implemented via a pipeline that runs on every code commit or at fixed intervals. The pipeline is defined in a CI/CD configuration file, such as .github/workflows/compliance.yml:
name: PCI DSS v4 Compliance
on:
push:
branches: [ main ]
schedule:
- cron: '0 2 * * 1' # Weekly on Monday
jobs:
verify-controls:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install compliance tools
run: |
sudo apt-get update
sudo apt-get install -y jq python3-pip
pip3 install -r requirements.txt
- name: Run control verification
run: |
python3 verify-controls.py --config controls.json --output report.json
- name: Upload results
run: |
echo '{"status": "pass", "report": "report.json"}' > status.json
echo "report.json" >> $GITHUB_OUTPUT
- name: Update compliance dashboard
run: |
curl -X POST https://dash.example.com/api/compliance/updates \
-H "Authorization: Bearer $DASHBOARD_TOKEN" \
-d @report.json
The verify-controls.py script reads controls.json, executes the verification command for each control, and returns a report with pass/fail status and evidence.
Validation is not a one-time check but a continuous process. Controls are validated using a standardized schema for output. Each validation run produces a report.json file:
{
"control_id": "1.1.1",
"timestamp": "2024-03-15T14:23:11Z",
"status": "pass",
"evidence": [
{
"type": "log",
"source": "/var/log/ansible/hardening-2024-03-15.log",
"content": [
"PLAY [Apply security hardening] *******************************************************************",
"TASK [Ensure SSH is configured] ******************************************************************",
"changed: [web01] => {\"changed\": true, \"msg\": \"SSH configuration applied\"}",
"TASK [Ensure firewall rules are in place] ********************************************************",
"changed: [web01] => {\"changed\": true, \"msg\": \"Firewall rules updated\"}"
]
},
{
"type": "output",
"command": "ansible-playbook --check --diff",
"stdout": "PLAY [Apply security hardening] *******************************************************************\n \nTASK [Ensure SSH is configured] ******************************************************************\nok: [web01]\n\nTASK [Ensure firewall rules are in place] ********************************************************\nok: [web01]\n\nPLAY RECAP ****************************************************************************************\nweb01 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0",
"stderr": "",
"exit_code": 0
},
{
"type": "inventory",
"name": "all-inventory",
"host_count": 12,
"hosts": [
"web01.example.com",
"web02.example.com",
"db01.example.com"
]
}
],
"metrics": {
"duration_seconds": 142,
"total_tasks": 24,
"changed_tasks": 8,
"failed_tasks": 0
}
}
A control is considered passing if the status field is "pass" and no evidence entries have status: "failed". A control fails if any task in the verification output reports exit_code: 1 or status: "failed".
To ensure that every system adheres to PCI DSS v4.0 requirements, engineering teams use pre-commit hooks to enforce control-specific checks. The pre-commit-config.yaml file defines rules that run before every Git commit:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
args: [--max-line-length=88]
- repo: https://github.com/PyCQA/isort
rev: 5.13.0
hooks:
- id: isort
- repo: https://github.com/ansible/ansible-lint
rev: 6.11.0
hooks:
- id: ansible-lint
args: [--profile, standard]
- repo: https://github.com/bokeh/bokeh
rev: 2.4.3
hooks:
- id: check-pci-dss
name: "PCI DSS v4 Control Validation"
description: "Verify that all control requirements are met in the current code state."
entry: "pci-validate"
language: python
types: [python]
pass_filenames: false
files: "controls.json"
args: ["--fail-on-uncertain"]
The pci-validate command checks that all control requirements in controls.json are satisfied in the current codebase. It reads each control’s verification.check field, runs it, and compares the output against expected values.
When a control requires specific configuration, pci-validate examines the actual configuration files. For example, for control 3.2.4, it checks that all EC2 instances have encryption_enabled: true and use an AWS KMS key with key_spec: SYMMETRIC_DEFAULT:
{
"control_id": "3.2.4",
"status": "pass",
"evidence": [
{
"type": "aws-ec2",
"instance_id": "i-1a2b3c4d5e6f",
"encryption_enabled": true,
"kms_key_id": "arn:aws:kms:us-east-1:123456789012:key/abcdef12-3456-7890-abcd-ef1234567890",
"key_spec": "SYMMETRIC_DEFAULT"
},
{
"type": "aws-kms",
"key_id": "arn:aws:kms:us-east-1:123456789012:key/abcdef12-3456-7890-abcd-ef1234567890",
"validation": "fips140-2",
"status": "valid"
}
]
}
If any instance lacks encryption, or the key spec is AWS_DEFAULT instead of SYMMETRIC_DEFAULT, the control fails.
Controls are not static. Their state evolves with system changes. Each control tracks its implementation, verification, and evidence over time. The controls.json file is updated after every successful pipeline run.
When a control fails, the system generates a ticket entry in the issue tracker. The ticket includes a link to the full report and a summary of the failure:
Title: PCI DSS v4 Control 1.1.1 Failed - SSH Configuration Mismatch
Status: Failed
Control ID: 1.1.1
Last Run: 2024-03-15T14:23:11Z
Report URL: https://ci.example.com/verify/1.1.1
Failure Summary:
- Task "Ensure SSH is configured" failed on host db01.example.com
- Error: "Failed to apply SSH configuration: /etc/ssh/sshd_config not found"
- Expected: /etc/ssh/sshd_config
- Actual: /etc/ssh/sshd_config.bak
Evidence:
- Log output:
TASK [Ensure SSH is configured] ******************************************************************
failed: [db01.example.com] (item=sshd_config) => {
"changed": false,
"failed": true,
"msg": "Failed to apply SSH configuration: /etc/ssh/sshd_config not found",
"item": "sshd_config"
}
- Host inventory:
- db01.example.com: SSH config file missing
- web01.example.com: OK
- web02.example.com: OK
The system automatically assigns the ticket to the team responsible for system hardening. When the fix is merged, the CI pipeline re-verifies the control and updates the report.
Some controls depend on others. For example, control 4.1.3 (Secure configuration of payment application) requires that control 3.2.4 (Encryption at rest) is active and passing.
The controls.json file supports a dependencies array:
{
"id": "4.1.3",
"name": "Secure configuration of payment application",
"dependencies": [
"3.2.4",
"1.2.5"
],
"verification": {
"check": "python3 check_payment_config.py",
"interval": "hourly",
"schedule": "0 * * * *"
},
"evidence": [
{
"type": "config",
"path": "/etc/payment-app/config.yaml",
"schema": "payment-app-schema.json",
"valid": true
}
]
}
The verification pipeline checks that all dependencies are passing before marking the control as active. If any dependency fails, the control is marked as pending and the report includes the dependency status.
Several silent failures are common in PCI DSS v4.0 implementations:
controls.json: The pipeline runs verify-controls.py, but the output file is not committed or not pushed. The control state becomes stale. The fix is to add a git add and git commit step in the CI workflow.schedule fields: The cron schedule for a control is set to 0 3 * * *, but the pipeline runs at 0 2 * * *. The control runs daily at 2 AM, not 3 AM. The error is not detected until the audit finds a 3 AM report missing.--fail-on-uncertain in pci-validate: Without this flag, pci-validate returns pass even if a control’s verification output contains status: "uncertain". In production, a control may pass with a warning but no failure, leading to false confidence.evidence in report: A control passes, but the report includes only the stdout of a command. The stderr, exit_code, and inventory are missing. The audit team asks for evidence, but the report is insufficient. The fix is to standardize the report schema and validate it with a schema-checker.At the end of each compliance cycle, the system generates a compliance audit package that includes:
controls.json filereport.json for each controlsummary.json file that aggregates all control statuses and evidencecompliance-log.json file that tracks all control verification runs over timeThe summary.json file is used to generate audit-ready dashboards and reports.
{
"cycle": "2024-Q1",
"start": "2024-01-01",
"end": "2024-03-31",
"total_controls": 42,
"passing_controls": 39,
"failing_controls": 3,
"control_summary": {
"1.1.1": "pass",
"3.2.4": "pass",
"4.1.3": "fail",
"5.3.7": "pending"
},
"failure_details": [
{
"control_id": "4.1.3",
"reason": "Dependency 3.2.4 failed",
"evidence": [
"https://ci.example.com/verify/3.2.4"
]
}
]
}
This package is stored in a versioned artifact repository and is the primary evidence for an external audit.
PCI DSS v4.0 is not a document to be read, but a set of engineering practices to be executed. Every control is a living artifact, verified automatically, tracked continuously, and documented precisely. The foundation is controls.json, the engine is the CI/CD pipeline, and the outcome is a system that not only meets but proves compliance. The reader knows exactly how to design, implement, verify, and maintain PCI DSS v4.0 engineering controls—not by example, but by pattern.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy