Compliance as Code: How to Enforce Rules on Every Pull Request
TL;DR
Compliance breaks at scale when it lives in docs: Rules drift across services, violations slip through manual code review, and auditors find gaps months later. You can’t enforce what people have to remember.
The fix: encode rules as automated PR checks. No hardcoded secrets, RBAC on protected routes, idempotent payment retries, turn policies into executable checks that block violations before merge, not after deployment.
This guide shows you how: 8 patterns that cause 90% of incidents, how to implement compliance across your org-repos with Qodo, and why teams like Monday.com stop 800+ violations monthly with automated enforcement.
Compliance breaks when it lives in documents. Rules drift across services, violations slip through manual review, and auditors find gaps months later not because reviewers are careless, but because no human can apply the same rule identically across every PR under deadline pressure. According to EMA’s 2025 AI in DevOps report, 62% of IT leaders cite security and privacy risks as their top AI concern, governance gaps in the CI/CD pipeline are now a board-level problem, not just an engineering one.
This guide covers the 8 patterns behind 90% of compliance incidents, how to implement automated enforcement across multiple repos, and how teams like Monday.com stop 800+ violations monthly without adding manual review load.
What does Compliance as Code mean?
Compliance as Code means expressing compliance requirements, controls, and checks in machine-readable form, so they can be tested, monitored, and enforced automatically as part of software delivery, rather than living in a reviewer’s memory or static documentation.
The difference is enforcement. When a SOC 2 auditor asks “how do you ensure RBAC is enforced across services?”, a 47-page compliance document is not an answer; it’s a policy. An answer is a PR where the check ran, a violation it flagged, and a commit showing it was fixed before merge.
| Approach | How rules are enforced | When violations surface |
| Policy in docs | Reviewer memory | Audit, 3 months later |
| Compliance as Code | Automated PR check | Before merge |
At 10 engineers, one senior reviewer holds the standard in their head. At 50 engineers across 20 microservices, that doesn’t scale, middleware gets refactored out of order, context leaves with engineers, and retry logic ships without idempotency. Compliance as Code is the system that runs what the reviewer would have caught.
The 8 Compliance Patterns Behind 90% of Production Incidents

These 8 patterns appear across the majority of SOC 2, PCI, and ISO 27001 audit findings. Each one can be encoded as an automated PR check.
| # | Pattern | Core risk | What the check enforces |
| 1 | Hardcoded secrets | Credentials in version history | Block secrets, tokens, or unsafe defaults in source |
| 2 | Missing auth on protected routes | Unauthorized access to sensitive endpoints | Require auth and RBAC middleware on sensitive routes |
| 3 | Non-idempotent retries | Double charges on payment failures | Require idempotency keys in financial retry logic |
| 4 | Unvalidated input | SQL injection, XSS, OWASP Top 10 | Require schema validation before input reaches a query |
| 5 | Stack traces in error logs | Secrets and tokens leaked to log providers | Flag err.stack and raw request objects in log calls |
| 6 | Rate limiter registered after routes | Throttling never actually runs | Detect middleware order violations before merge |
| 7 | No ticket linked to PR | Change management gaps in audits | Block PRs without a linked issue or design doc |
| 8 | Unencrypted infrastructure | Customer data exposed in storage | Flag Terraform resources with encryption disabled |
Each pattern is explained below with the failure mode, a code example, and the enforcement rule.
1. Stop Hardcoding Secrets
The failure happens every time: the developer hard-codes a test key to move fast, means to remove it, forgets, and it ships. Or a JWT secret gets assigned “dev-secret” for local development, and nobody validates the environment variable before going to production.
const jwtSecret = "dev-secret"; const stripeKey = "sk_live_abc123xyz";
The rule: Never put secrets in source code. Load them at runtime from environment variables or a secrets manager like AWS Secrets Manager or HashiCorp Vault, and never give them defaults that could work in production. Anything in source code gets committed to version history, cloned to every developer’s machine, and logged in CI output. Once a secret lands in the repo, it’s effectively everywhere and permanent, so the only safe place for it is outside the code entirely. If a PR contains secret patterns or unsafe defaults, it should be blocked before review even starts.
2. Make Sure Every Protected Route Checks Authorization, Not Just Authentication▎
Someone adds a new route while moving fast, wires up requireAuth, and ships it. The endpoint correctly rejects logged-out users — but never checks whether the logged-in user is allowed to touch this resource. So any authenticated user can read or modify data that belongs to someone else, just by changing an ID in the request.
// Authenticated, but not authorized:
app.get(‘/api/orders/:id’, requireAuth, (req, res) => {
const order = db.orders.find(req.params.id); // whose order? nobody checks
res.json(order);
});
The rule: authentication proves who the user is; authorization proves they’re allowed to act on this specific resource. Every route touching sensitive data must verify ownership or role — not just that someone is logged in. The fixed version scopes the lookup to the caller (db.orders.find({ id: req.params.id, userId: req.user.id })) or rejects when the resource isn’t theirs. Catching this requires understanding the codebase; you need to know that :id references another user’s data, not just scan for a requireAuth middleware.
3. Don’t Let Retries Double-Charge Your Users
Your service tries to charge a customer, the request times out, the retry logic kicks in, but the payment provider has already processed the first request. The customer gets charged twice.
async function chargeCustomer(amount, customerId) {
for (let i = 0; i < 3; i++) {
try {
return await stripe.charges.create({ amount, customer: customerId });
} catch (err) {
continue;
}
}
}
The rule: retry logic for financial or external operations must be idempotent. Automated checks should detect when retry logic is added to payment flows without idempotency safeguards and block the PR.
4. Validate Input Before You Use It (Or Get SQL Injected)
Someone adds an endpoint that takes req.body.query and passes it directly into a database query. No validation, no sanitization.
app.post('/api/search', (req, res) => {
const query = req.body.query;
db.execute(`SELECT * FROM products WHERE name LIKE '%${query}%'`);
});
The rule: never trust external input. All input should be validated against a schema, sanitized before use in queries, and bounded with max length and allowed values. Passing unvalidated user input directly into SQL queries opens the endpoint to injection attacks, the same class of vulnerability that covers XSS and the rest of the OWASP Top 10.
5. Don’t Leak Stack Traces and Secrets in Error Messages
Returning raw errors to clients or logging full error stacks exposing file paths, environment variables, database connection strings, and authorization headers, which then appear in Datadog, Splunk, or any third-party log aggregator with access to the stream
app.use((err, req, res, next) => {
logger.error(err.stack, { headers: req.headers });
res.status(500).json({ error: err });
});
The rule: clients get generic structured error messages, logs don’t contain secrets or unmasked personal data, and stack traces stay internal. Automated checks should flag res.json(err) patterns and logging of sensitive headers like Authorization.
6. Put Rate Limiting BEFORE Your Business Logic (Not After)
This one is easy to miss. The code looks configured, but middleware ordering means protection never actually runs.
app.use(routes);
app.use(rateLimit({ windowMs: 60000, max: 100 }));
The rule: protection middleware executes before business logic. An attacker can brute-force login endpoints and DDoS your service if rate limiting runs after the routes. Detecting this requires understanding middleware execution order, not just checking whether rate limiting exists.
7. Link Every PR to a Ticket (So Audits Don’t Destroy You)
When an auditor asks for evidence of controlled change management, “fixes bug” in the PR description isn’t evidence. Neither is a PR with no ticket linked, no context, and no justification for why the code changed.
The rule: every PR must link to a ticket or design doc. SOC 2, ISO 27001, PCI, and HIPAA all require change traceability. For example:
## PR template with traceability ## Description Fixes payment timeout in checkout flow Ticket: JIRA-1234 Root cause: Missing retry timeout on Stripe API calls
Automated checks should validate that the ticket link exists and block PRs that are missing it.
8. Make Sure Your Terraform Actually Encrypts Things
Compliance doesn’t stop at application code. Small Terraform changes can deploy successfully while violating security policies.
# What slips through without policy-as-code
resource "aws_s3_bucket" "data" {
bucket = "customer-data"
acl = "public-read"
}
resource "aws_db_instance" "main" {
storage_encrypted = false
}
The rule: all storage must be encrypted, no public access on sensitive resources, security groups must restrict IP ranges, and TLS must be enforced. Policy-as-code checks should block Terraform PRs that violate these requirements before infrastructure is ever deployed.
5 Ways Compliance Drifts Silently Across Microservices
At 30 microservices, each owned by a different team, compliance enforcement breaks in five predictable ways:
1. Auth enforcement varies by team, not by rule
Team A wires requireAuth with requireRole(‘admin’) and a handler into their middleware stack. Team B copies the route handler from a different repo, skips the middleware block, and ships. Both repos pass CI. Neither reviewer notices because they’re reviewing their own service in isolation.Each team builds its service independently. One team does it right. Another copies the route handler but skips the middleware. A third adds auth but forgets role checks. Six months later, you have no idea which services are actually compliant.
Service A: requireAuth → requireRole('admin') → handler
Service B: handler (auth was "added later"--never was)
Service C: requireAuth → handler (role check missing)
Service D-Z: unknown
The fix: Define rules once in a central pr-agent-settings repo. Every repo inherits them automatically, and when a PR violates a rule, the check fails and blocks merge, regardless of which service or team owns the branch.
2. Config files exist but checks never run
The config file is there. The checks aren’t running. This happens when the file is in the wrong directory, named incorrectly, or a local setting overrides it without anyone noticing.
repo-a/ - .pr_agent.toml - pr_compliance_checklist.yaml → enforcement active repo-b/ - .pr_agent.toml - compliance/checklist.yaml → wrong path, never loaded repo-c/ - (no config) → no enforcement at all
The fix: Use a central pr-agent-settings repo that all service repos inherit from automatically. Services can add stricter rules locally, but can’t disable the baseline.
3. Custom rules are defined but never evaluated
Your team defines a rule: “Rate limiter must be registered before routes.” The tool runs on every PR. But only the built-in security checks are running; your custom rules are never evaluated because the config path isn’t configured correctly.
# Checklist path not set, only default checks run [pr_compliance] # Checklist path set explicitly, custom rules run [pr_compliance] checklist_path = "pr_compliance_checklist.yaml"
The fix: Check your PR output to confirm which rules ran. If your custom rule isn’t listed, it isn’t running.
4. Alert volume causes developers to bypass enforcement
When blocking violations, warnings, and style suggestions all carry the same weight, developers stop trusting the system and start working around it. Severity separation is what keeps enforcement useful.
| Violation | Without severity tiers | With severity tiers |
| Hardcoded secret | BLOCKED | BLOCKED |
| Missing ticket link | BLOCKED (too strict) | WARNING |
| Logging style suggestion | BLOCKED (way too strict) | ADVISORY |
Rollout approach: Start all rules in warning mode. Run for 2 to 4 weeks to identify false positives. Only promote rules to blocking, secrets, missing auth, financial safety, once you’ve confirmed accuracy. A rule that blocks incorrectly erodes developer trust faster than no enforcement at all.
5. Error logging leaks tokens and customer data
This looks like responsible error logging. It’s actually leaking sensitive data into your log provider.
// Logs more than you think
logger.error('Payment failed', {
error: err.stack, // contains DB connection strings, API keys
request: req // contains Authorization header, payment data
});
// Logs only what you need
logger.error('Payment failed', {
errorCode: err.code,
orderId: req.params.orderId
});
The fix: Automated checks should flag logging of full error stacks, request objects, and sensitive headers. Each service handles logging differently, so this needs enforcement at the PR level, not just a team convention.
How Qodo Turns Compliance Rules into Automated PR Enforcement

You’ve seen why compliance breaks: rules drift across services, config files exist but don’t run, custom checks never execute, false positives create developer friction, and logging leaks happen silently. These aren’t process problems you can fix with better documentation. They’re infrastructure problems that require automated enforcement with full codebase context.
Most teams build custom shell scripts that break when repo structure changes, or run SAST scanners that flag vulnerable patterns in isolation but miss architectural violations like rate limiter middleware registered after routes. Qodo, the AI Code Review Platform, enforces compliance rules at PR time using full codebase context, tracing middleware ordering, RBAC chains, and retry logic across files rather than pattern-matching within the diff.
Setting Up Compliance Checks That Run Automatically
Configure Qodo once via .pr_agent.toml, and compliance checks run automatically on every PR across every repo, 20K pull requests scanned daily, no commands required. Here’s how it works:
- Define your compliance rules in pr_compliance_checklist.yaml:
pr_compliances:
- title: "Rate Limiter Must Be Registered Before Routes"
compliance_label: true
objective: "Rate limiting middleware must be applied before route handlers"
- title: "JWT Secret Must Not Be Default"
compliance_label: true
objective: "Production must not use default JWT secrets like 'dev-secret'"
- title: "Payment Retry Must Be Idempotent"
compliance_label: true
objective: "Retry logic for financial operations must include idempotency keys"
- title: "RBAC Middleware Must Be Applied"
compliance_label: true
objective: "Protected routes handling sensitive operations must enforce role-based access control"
- Configure automatic enforcement in .pr_agent.toml:
[github_app]
pr_commands = [
"/compliance"
]
handle_push_trigger = true
push_commands = [
"/compliance"
]
[pr_compliance_prompt]
# Controls output verbosity
minimal_output = false
[config]
# Post results in PR
publish_output = true
The .pr_agent.toml configuration above triggers compliance checks automatically on PR open, on each new commit push, and on every PR update.
- Compliance runs and blocks violations before merge:
When a developer opens a PR, Qodo immediately evaluates four compliance dimensions:
Security Compliance: Scans for hardcoded secrets, missing authentication on protected routes, logging of sensitive data, unsafe retry logic in financial operations, and weak input validation patterns.
Ticket Compliance: Validates that a ticket is linked, checks if changes match the ticket scope, and verifies requirements appear satisfied (required for SOC 2, ISO 27001 audit traceability).
Code Duplication Compliance: Identifies if the PR introduces duplicated logic instead of reusing existing components, which helps avoid compliance drift across microservices.
Custom Compliance: Evaluates your organization-specific rules defined in pr_compliance_checklist.yaml, like middleware ordering, RBAC enforcement, idempotency requirements, and secrets validation.
Example: How Qodo Catches Compliance Violations Before Merge

Here’s the actual output from a PR where Qodo caught multiple compliance issues:
What passed: The rate limiter is correctly registered before route handlers, avoiding requests from hitting business logic before throttling is enforced. This validates an actual architectural decision specific to this service.
What’s flagged for review: The error handler logs err.message and err.stack, which might contain tokens, headers, raw request data, or downstream error payloads.

Even if the API response is sanitized, full stack traces in logs create data leak risk. This requires human verification but doesn’t block the merge immediately.
What’s blocking the merge: No ticket is linked (audit traceability requirement), JWT secret validation is missing, so “dev-secret” could reach production; retry logic was added without idempotency protection, creating a risk of duplicate charges; and protected routes lack clear RBAC middleware enforcement. These are security and compliance boundaries that must be fixed before the merge.
This output shows compliance checks against actual changes, not theoretical risks. If the enforcement logic is missing in the PR, the check fails. Developers see issues immediately, fix them before merging, and compliance evidence stays attached to the PR permanently.
How This Ensures Production Code Stays Compliant
The enforcement flow guarantees compliance violations never reach production:
| Step | What happens | Compliance outcome |
| 1. PR opened | Compliance checks trigger automatically | No violation reaches review undetected |
| 2. Violations flagged | Security issues, missing RBAC, and custom rule failures appear within seconds | Developers fix issues before reviewers see the PR |
| 3. Blocking vs advisory is clear | Critical violations block merge; advisory items don’t | High-risk code cannot ship; low-risk feedback doesn’t slow teams |
| 4. Developer fixes and pushes | Checks re-run automatically on the updated commit | Every commit is verified, not just the first push |
| 5. Merge only when compliant | Non-compliant code cannot reach production | Compliance is structural, not dependent on memory |
| 6. Audit trail generated | Every PR contains structured compliance status, what ran, what failed, who approved | Evidence exists without manual collection |
This shifts compliance from “hope reviewers remember” to “automated enforcement that runs before code ships.” The check happens before human review starts, violations are visible in the PR where developers work, and evidence is generated automatically without manual documentation.
How Monday.com Prevents 800+ Compliance Issues Monthly with Automated PR Checks
Monday.com’s 500-developer engineering org deployed Qodo across their entire stack. Qodo now prevents an average of 800 potential issues from reaching production every month while saving developers approximately one hour per pull requests. Results included:
- 800+ potential issues stopped monthly: automated detection caught security violations, missing RBAC, unsafe retry logic, and configuration drift before merge, directly improving overall code quality
- ~1 hour saved per pull request: developers spent less time hunting for compliance violations and more time on feature development
- Consistent enforcement across teams: the same compliance standards applied to every PR, regardless of which team or repository
| By incorporating our org-specific requirements, Qodo acts as an intelligent reviewer that captures institutional knowledge and ensures consistency across our entire engineering organization.”
– Liran Brimer, Senior Tech Lead, Monday.com |
How Qodo Differs from Generic Security Scanners
SAST tools operate on file-local patterns, they flag known vulnerability syntax within a single file. Qodo enforces compliance using a repository-wide context, which means it catches violations that only become visible when you understand how the codebase fits together.
| Capability | Generic SAST scanner | Qodo |
| Detects secrets in source code | Yes | Yes |
| Checks middleware ordering across files | No | Yes |
| Validates RBAC chain on a specific route | No | Yes |
| Enforces org-specific custom rules | No | Yes |
| Generates per-PR audit evidence automatically | No | Yes |
| Understands financial retry logic in context | No | Yes |
A rule like “rate limiter must be registered before routes” or “RBAC middleware must appear before the handler” requires understanding execution order across files, not pattern matching within the diff. That’s the class of violation generic scanners miss and the class Qodo is built to catch.
FAQ
What is Compliance as Code and how does it differ from traditional compliance?
Compliance as Code means encoding compliance rules as executable checks that run automatically on every PR, rather than documenting them in wiki pages or PDFs. Traditional compliance depends on reviewers remembering rules during manual review, which fails at scale. Compliance as Code turns rules like “no hardcoded secrets” or “all protected routes must have RBAC” into automated checks that block PRs before merge. The shift is from reactive (finding violations after deployment) to preventive (blocking violations before they reach production).
How do I implement Compliance as Code for my engineering team?
Start by identifying your top 5 compliance pain points from recent incidents, audit findings, or security team concerns. Create a pr_compliance_checklist.yaml file defining these rules with clear objectives. Configure a .pr_agent.toml file to run compliance checks automatically on PR open and update. Begin in “signal only” mode for 2-4 weeks to tune false positives, then make critical rules like hardcoded secrets and missing auth blocking. Tools like Qodo automate this entire enforcement layer with full codebase context.
What compliance rules should I automate first?
Focus on the 8 patterns that cause 90% of incidents and audit failures: no hardcoded secrets in source code, all protected routes enforce authentication and RBAC, payment retries are idempotent to avoid double-charging, input validation on all external data, no stack traces or secrets in error logs, rate limiting registered before business logic, ticket linkage for audit traceability, and infrastructure encryption requirements. These rules have the highest impact on security and auditability.
Can Compliance as Code work for SOC 2, ISO 27001, and PCI compliance?
Yes. SOC 2, ISO 27001, and PCI auditors ask for evidence that a control ran, not the policy document. With Qodo, every PR contains a structured compliance record: which rules ran, which violations were flagged, and which commits resolved them before merge. That’s the evidence trail. Compliance as Code provides exactly this: automated enforcement on every PR with audit trails showing when rules ran, what violations were caught, and how they were resolved before merge. When auditors ask, “How do you ensure RBAC is enforced?” you show them the rule definition in your repo, PRs where it flagged violations, and evidence that it runs on every change. This replaces manual audit preparation with automatically generated evidence.
How does Qodo enforce Compliance as Code differently from generic security scanners?
Qodo uses repository-wide context to catch architectural violations, middleware ordering, RBAC chains, financial retry logic, that file-local SAST scanners miss entirely. See the full comparison table above.
