Compliance as Code: turning ISO 27001 controls into CI checks
· 2 min read

Most teams treat compliance as a thing that happens to them once a year: a spreadsheet appears, screenshots get collected, and everyone loses a week proving the system was configured the way it was always supposed to be. That ritual is expensive, and worse, it tells you nothing between audits.
The fix isn't more documentation. It's moving the controls that can be machine-checked into the pipeline, where they run on every change and produce evidence as a side effect.
What "as code" actually means here
Not every control is automatable — an access-review policy still needs a human. But a surprising share of an ISO 27001 Annex A control set maps cleanly onto checks you already have the tools for:
- A.8 (asset / config management) → infrastructure-as-code scanning (
tfsec,checkov). - A.8.28 (secure coding) → SAST + dependency audit in CI.
- A.8.24 (cryptography) → policy tests asserting TLS versions, KMS usage, no plaintext secrets.
- A.5.23 (cloud services) → cloud posture checks (CSPM) gating the merge.
The goal is a one-to-one trace: a control ID, the check that enforces it, and the run that proves it passed.
A control as a pipeline gate
Here's the shape I aim for — a policy check wired to a control, failing the build when the control is violated:
# .github/workflows/compliance.yml
name: compliance
on: [pull_request]
jobs:
iac-controls:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: A.8 — IaC misconfig scan (control-gated)
run: checkov -d ./infra --compact --quiet
- name: A.8.28 — dependency audit
run: pnpm audit --audit-level high
The win isn't the tool — it's that the control ID lives next to the check. When an auditor asks "show me A.8 is enforced," the answer is a green run on every merge, not a screenshot from March.
The part people get wrong
Compliance-as-code fails when it's bolted on as a blocking gate nobody understands. Two rules keep it from becoming the new paperwork:
- Every gate maps to a control, and every control maps to a real risk. If you can't name the risk, delete the check — it's just noise that teaches engineers to ignore red.
- Evidence is automatic or it doesn't count. Pipe each run's result to wherever your control register lives. If proving the control still takes manual effort, you've moved the work, not removed it.
Where the humans stay
The point of automating the mechanical controls is to buy attention for the ones that matter — threat modelling, access reviews, supplier risk, incident response. Those are judgement calls, and pretending a linter covers them is how you get a certificate that means nothing.
Done well, the auditor's questions stop being "can you prove it?" and become "why did you decide it that way?" — which is the conversation worth having.
Related reading

Kubernetes migrations without downtime: a project manager's runbook
Most 'big bang' platform migrations don't fail on the technology — they fail on coordination. Here's the runbook I use to move a live system to Kubernetes one slice at a time, with a rollback at every step.
· 1 min read

Least privilege without the friction
Everyone agrees with least privilege until it slows them down — then the wildcards creep back in. Here's how I make tight IAM the path of least resistance instead of a tax.
· 1 min read

An LLM eval harness your team will actually trust
Shipping an AI feature without evals is flying blind — you only learn it regressed when a user does. A small, boring evaluation harness in CI fixes that, and it's less work than the first incident.
· 1 min read