Compliance Requirements in Pull Requests, Not Spreadsheets
Most compliance programs prove a requirement twice a year, in a spreadsheet, long after the code that violated it has shipped. The auditor asks whether output is encoded for its context. Someone finds a scanner report, a policy document and a Jira ticket, pastes links into a cell, and the row turns green.
That is assertion, not evidence. The requirement itself is specific enough to check at the moment the code changes. Treating compliance requirements in pull requests as a gate, rather than as a question answered at audit time, is the difference between a program that reports and a program that holds.
This post is about that practice: which requirements a pull request gate can enforce, how to scope the gate to the requirement instead of to a pile of rule ids, and what has to be true around the gate (regression alerts, attestation history, non-code evidence) for the result to survive an audit.
Why compliance requirements in pull requests beat the audit spreadsheet
An audit runs on a calendar. Code runs on a merge queue. A control that is checked quarterly is unchecked for the other eighty-nine days, and every pull request in that window can regress it without anyone noticing.
The spreadsheet also records the wrong thing. It records that a control existed on a date, backed by a screenshot. It does not record that the control was applied to each change. When an auditor, a customer security review or a regulator asks for evidence that a requirement was met, "we have a scanner" is a weaker answer than "this gate fails the status check on every pull request that violates it, and here is the history."
The third problem is drift between the standard and the tooling. OWASP ASVS, the EU Cyber Resilience Act and DORA each say what they want in their own vocabulary. A SAST tool says what it found in rule ids and categories. The mapping between the two usually lives in someone's head or in a wiki page from two years ago.
If the mapping is wrong, the green cell is wrong, and nobody finds out until an incident.
What a requirement looks like when a gate can read it
Take a real one. ASVS 5.0.0 requirement 1.2.5 asks you to verify that output encoding for an HTTP response, HTML document or XML document is relevant to the context required. That is a claim a codebase can be held to.
A cross-site scripting finding on a changed line is a violation of it. A parameterized query that was replaced with string concatenation is a violation of an injection requirement in the same chapter.
For a gate to enforce a requirement, three things must be defined and stable:
- Which findings count. Not "all SAST findings" and not "rule ids 112, 118 and 204", but the set of weaknesses the requirement text actually covers.
- At what floor. A requirement at Level 1 and the same requirement at Level 3 do not deserve the same severity and confidence threshold. The floor belongs to the requirement, not to the guardrail.
- What the developer is told. A failed check that says "rule 118" sends the developer to a search engine. A failed check that says "V1.2.5 (L1), output encoding" sends them to the fix and tells the auditor what was enforced.
The common shortcut is to approximate a requirement with hand-picked rule ids and categories. It works on day one. It breaks the first time the scanner adds a rule, the standard publishes a new version, or a second standard (DORA, say) wants an overlapping but not identical set. At that point the approximation is a liability, because the audit evidence says "requirement enforced" and the gate is enforcing something else.
Which requirements a pull request gate can enforce
Not many, and that is fine. Most of a standard asks about things no gate can see. ASVS Level 2 has 253 requirements, and a large share of them concern session design, authorization logic, key management procedure and operational practice. A pull request check cannot verify that your session timeout is appropriate for the data you handle.
The enforceable subset is the part where a requirement maps to a weakness that static analysis, dependency analysis or secret detection can observe on a change:
- Injection and cross-site findings (encoding, sanitization, query construction).
- Cryptography findings (weak algorithms, hard-coded keys, bad modes).
- Credentials committed to the repository.
- Known-vulnerable dependencies above a severity floor, with a fix available.
- Findings past their remediation deadline, where the standard asks for timely vulnerability handling.
Everything else is either verified from evidence outside the pull request (cloud configuration, container images, infrastructure code) or attested by a person. A program that says "these five controls are enforced at the pull request, these twelve are verified from deployment evidence, and these forty are attested with a re-review date" is one an auditor can work with. The NIST Secure Software Development Framework makes the same split: some practices are automated checks on code, many are process.
Block needs the source control platform to agree
A failed status check only prevents a merge when branch protection says it must. The gate reports; the platform enforces. On GitHub that is a required status check on the protected branch, which you can set from the API:
gh api -X PATCH repos/ORG/REPO/branches/main/protection/required_status_checks -f strict=true -f 'contexts[]=YOUR-SECURITY-CHECK'
Without that setting, a "blocking" compliance guardrail is a warning with a red icon, and the audit trail will show merges that happened over a failed check.
Three things that go wrong after the gate is on
Turning on requirement-scoped enforcement is the easy half. These three failures show up in the months after.
The requirement regresses and nobody is told
A requirement that was verified last quarter can fall to violated this week. A new code root gets mapped to the application. A scan finds a flow the old scan missed. A guardrail is disabled during a release crunch and never re-enabled.
The score on the dashboard moves, and the dashboard is not where anyone is looking.
A regression from verified to violated is an event, and it should be routed like one: to the application's owner, as a ticket or a message, at the moment it happens, not at the next review.
Attestations change without a record
The non-enforceable majority of a standard rests on attestations: a person states that a requirement holds, with a reference and a re-review date. If an edit overwrites the previous statement, and a withdrawal deletes it, the attestation history is gone. The auditor's first question is "who said this was true, and when?"
Attestations need the same append-only treatment as a financial ledger. Every statement, edit and withdrawal, with the author and the time, kept even after the requirement returns to its evidence-based status.
The evidence lives outside the repository
Requirements about TLS at the edge, encryption of stored data, logging, backups and configuration baselines are not answered by code. They are answered by the load balancer policy, the database setting, the audit trail configuration and the container image. A standards program that only reads static analysis will answer those requirements by attestation, which means by hope. The evidence exists in the cloud account and the infrastructure code; the assessment has to read it there.
What to do: a checklist for enforcing standards at the pull request
- Adopt the standard explicitly and pick a target level per application. Assurance should follow criticality: a payments service and an internal tool should not share a level.
- Separate the enforceable subset from the rest. Write down which requirements the gate covers. Do not let the list be implied by whatever rules happen to be on.
- Scope guardrails to requirements, not to rule ids. The mapping from requirement to weakness should be maintained once, with the standard, and inherited by every gate that cites it.
- Roll out Observe, then Warn, then Block. Watch the violation volume on real pull requests before a failed check can stop a merge.
- Make Block real on the platform. Required status checks on GitHub, external status checks on GitLab, branch restrictions on Bitbucket.
- Route regressions to owners. A drop from verified to violated, or an expired attestation, should open a ticket or send a message the same day.
- Keep attestation history append-only. Who, when, what changed, including withdrawals.
- Feed non-code evidence into the assessment. Cloud checks, infrastructure-as-code findings and container image findings answer requirements that static analysis cannot.
- Generate the report from the live assessment. The document the auditor reads should be a point-in-time export of the same data the gate runs on, not a separate artifact.
How Heeler enforces compliance requirements in pull requests
Heeler assesses every application against the standards your organization adopts (OWASP ASVS 5.0.0, the EU Cyber Resilience Act Annex I, and DORA) and keeps the enforcement tied to the assessment. We wrote about the assessment side in OWASP ASVS Without the Spreadsheet. This section is the pull request side.
A static analysis guardrail rule can take a set of Standards requirements per framework. At the pull request gate it blocks only the findings the application's Standards tab lists under those requirements, at the severity and confidence floors each requirement defines. The check comment names the requirements each violation applies to, and the assessment records the guardrail as enforcement for the repositories in its scope. There is no hand-maintained list of rule ids standing in for the requirement.
The enforceable subset of each standard ships as a guardrail bundle: Enforce ASVS Level 1 is four controls (injection findings, cryptography findings, overdue vulnerability SLOs, and critical vulnerabilities with a fix available), Level 2 adds exposed secrets, and CRA and DORA have their own. Each control shows the requirement chips it credits, in the standard's own form, before you apply anything. Applying a bundle twice is safe: a control you already have is left alone.
A guardrail runs only when both it and its bundle are enabled, and the bundle's scope narrows the guardrail's own. Every PR guardrail runs as Observe, Warn or Block, and Block holds only when your source control platform requires the check.
Regressions, attestation history and non-code evidence
Around the gate, Heeler closes the three gaps above:
- Regressions. Each recompute compares every requirement with the previous run. The Standards Regression workflow trigger fires once per application and standard when a requirement drops, with the reason: a violation, an expired or withdrawn attestation, or lost coverage. Route it to a ticket or a message.
- Attestation history. Every attestation and not-applicable disposition is recorded in an append-only history with who stated it and when, including withdrawals. The requirement's evidence panel shows it, and a withdrawn disposition stays in the history.
- Non-code evidence. An assessment reads cloud compliance violations on the application's deployments, infrastructure-as-code misconfigurations, container image findings and agent-file findings alongside code evidence, so requirements on configuration baselines, encryption of stored data, logging, network traffic, backups and TLS at the edge are answered from evidence. Each CRA and DORA requirement also cites the CIS Controls v8.1 safeguard behind its evidence.
One rule underneath all of it: Heeler will not call a requirement verified because it failed to find a problem. Unscanned code is uncovered, not clean, and an attestation never masks a violation. That is what makes the compliance report worth handing to an auditor.
FAQ
Can a pull request gate enforce a whole compliance standard?
No. A pull request gate can enforce the subset of requirements that map to weaknesses observable on a code change, such as injection, cryptography, committed secrets, vulnerable dependencies and overdue fixes. The rest is verified from deployment evidence or attested by a person, and a credible program says which is which.
What is the difference between scoping a guardrail to a requirement and to rule ids?
A requirement-scoped guardrail inherits the weakness set and the severity floor from the standard, so it stays correct when rules or standard versions change. A rule-id list is a snapshot that drifts from the requirement without anyone noticing, and the audit evidence then overstates what is enforced.
Why does a blocking guardrail still let pull requests merge?
Because the gate reports a failed status check and the source control platform decides whether that check prevents a merge. Block holds only when branch protection on GitHub, external status checks on GitLab or branch restrictions on Bitbucket require the check.
Does an attestation count as compliance evidence?
An attestation is a person's recorded statement that a requirement holds, which is different from verification by evidence. It is appropriate for requirements no scanner can observe, provided it carries the author, the date, a reference and a re-review date, and provided the history of every edit and withdrawal is kept.
How do I know when a requirement regresses?
Treat a drop from verified to violated, or an expired attestation, as an event and route it to the application owner the moment the assessment recomputes. A regression that only shows as a changed score on a dashboard will wait until the next audit to be found.
Heeler enforces the enforceable part of OWASP ASVS, the EU Cyber Resilience Act and DORA on every pull request, routes regressions to owners, and keeps the attestation history an auditor will ask for. Get a demo


.jpg)

