19 September 2026

Policy as a Code

Reza Khosravi
No items found.

Table of Contents

Policy as a Code

A lot of teams arrive at policy as code after an uncomfortable meeting.

A security review starts with a simple control check. Then someone notices three production services have been running for months without the required logging path turned on. The control exists in a security wiki. There's a second version in an engineering standards page. A third copy lives in an onboarding doc that nobody has updated since the last platform migration. Every document says logging is required. Production says otherwise.

That mismatch is where policy as a code stops being an abstract governance idea and starts looking like an operational necessity. The problem usually isn't that people don't care. It's that manual reviews, team turnover, competing priorities, and vague phrases like “compliant enough” can't keep pace with modern delivery. When infrastructure changes in pull requests and deploys happen all day, written policy by itself becomes commentary.

The Moment Written Policy Stops Matching Reality

A pull request changes a Terraform module, passes review, and ships before anyone notices it removed the audit sink required for production. The policy still exists in the wiki. The system no longer follows it.

That gap is common in regulated environments because written controls depend on people catching drift by inspection. One team remembers the requirement because they built the original service. Another inherits an older pattern that was approved years ago under different assumptions. A third gets the design right, then changes the implementation later and never revisits the control. By the time audit asks for evidence, the organization can produce policy text, approval records, and screenshots, but not a reliable answer to a simple question: which services comply right now?

The failure is usually operational, not editorial. The sentence in the standard may be perfectly clear. The problem is that no mechanism evaluates the requirement every time code, infrastructure, or deployment settings change. Routine security policy updates help keep intent current, but they do not enforce the intent after merge.

Written controls decay when enforcement is separate from delivery and ownership is vague.

That is the pressure policy as code is responding to. The meaningful shift is not "we stored policy in Git." The shift is that controls move into the same control plane that already governs builds, infrastructure changes, admissions, and runtime decisions. A requirement stops being guidance that reviewers try to remember and becomes a rule the platform can evaluate consistently at the points where risk is introduced.

Recent industry reporting shows broad adoption, but uneven maturity. Many organizations now use policy as code somewhere in production, while far fewer apply it consistently across most systems or tie it cleanly into day-to-day authorization and delivery workflows (Styra 2023 report). That matches what shows up in real programs. Teams can get to "we have policies" fairly quickly. Getting to traceable enforcement, exception handling, and clear ownership takes longer.

The difference matters.

Teams reduce risk when policy as code changes how decisions are made and recorded:

  • Controls become executable. The requirement runs against a pull request, plan, manifest, or request instead of waiting for a reviewer to remember it.
  • Traceability becomes audit evidence. A deny result points to a specific rule, commit, and control statement.
  • Enforcement shows up in normal workflow. Developers see failures in PR checks, CI jobs, admission controllers, or deployment gates, not in a spreadsheet weeks later.
  • Ownership is explicit. Someone owns the rule, the exception path, test coverage, and the decision to tighten or relax enforcement.

Without those mechanics, policy as code becomes another documentation layer with better syntax. With them, it becomes part of the operating model.

What Policy as Code Actually Means

The shortest useful definition is this: policy as code expresses governance rules in machine-readable form so they can be versioned, tested, and enforced automatically where changes happen.

That sounds simple, but a lot of weak implementations stop at “we wrote some rules in YAML.” That isn't enough.

Three properties that make it real

First, policy as code is declarative. The rule states what must be true. It doesn't depend on a person manually reading a diff and deciding whether a resource “looks secure enough.” A good policy says a workload must expose required ownership metadata, a storage setting must use approved encryption settings, or a deployment must not create public exposure in a protected environment.

Second, it is version-controlled. Policies live in repositories, change through pull requests, and carry history. You can answer basic questions without archaeology. Who changed the rule. When did it tighten. Which services were evaluated against the previous version.

Third, it is testable. If a policy has no fixtures, no expected-pass inputs, and no expected-fail inputs, it's a fragile script wearing compliance language. Treat the rule like software. Feed it known-good and known-bad manifests, plans, or authorization requests and confirm it behaves predictably.

The linter analogy that usually lands

The easiest way to explain this to developers is to compare it to linting.

A linter encodes a coding standard and runs on every change. A policy rule encodes a control and runs on every change to infrastructure, identity, service configuration, or data flow. The difference is scope. Linting asks whether code style or code patterns meet a standard. Policy asks whether the shipped behavior meets a governance requirement.

Practical rule: If a control can't be evaluated repeatedly and predictably, it isn't operating as code yet.

More than security policy

Teams often start with security guardrails, but the same operating model covers more than that.

You'll usually see policy as code applied across:

  • Security policy such as encryption, exposure limits, secrets handling, and identity constraints
  • Compliance policy such as evidence collection, required approvals, and environment restrictions
  • Operational policy such as logging, backup configuration, and ownership metadata
  • Cost policy such as resource class restrictions, region controls, and spend-related tags

The important point is that these aren't separate practices. They're different rule sets enforced through the same discipline.

Common Languages and Formats for Policy as Code

Choose the language by asking a control-plane question first. Where will decisions be made, who owns the rules, and what evidence will exist when an auditor asks how a control was enforced?

That framing changes the selection process. Policy as code is not a search for the most elegant syntax. It is a decision about how controls move from written intent into repeatable decisions across pull requests, infrastructure plans, admission checks, and authorization calls.

Rego and general-purpose policy engines

Rego is the language many teams meet first because of Open Policy Agent, which was built to evaluate policy across different systems and input types (CNCF on OPA). It works well when one control has to show up in several places, such as a Terraform plan check in CI, a Kubernetes admission decision, and a periodic review of deployed state.

That portability is the advantage. A platform team can keep control logic in one policy model instead of rewriting the same rule for every scanner and gate in the pipeline.

The trade-off is authoring discipline. Rego gives security and platform engineers a lot of flexibility, but that flexibility can make review harder for developers and control owners who do not write policy every day. In regulated environments, that matters. If the people accountable for a control cannot understand what the rule is doing, traceability degrades fast.

Cedar, Sentinel, and YAML-first patterns

Cedar fits a narrower problem and does it cleanly. It is built for authorization questions involving principals, actions, resources, and context, which makes it a strong option for application permissions and service-to-service access decisions (Cedar documentation).

Sentinel is usually a practical choice when Terraform or Vault is already the center of enforcement. Teams with a heavy HashiCorp footprint often get value from staying close to that workflow. The limitation is portability. Sentinel can enforce well inside that boundary, but it is rarely the language that becomes the shared policy layer across application, platform, and runtime decisions.

YAML-first policy formats are common in Kubernetes and GitOps environments because they are easy to read in pull requests. That readability helps adoption. It also helps ownership, because application teams are more willing to review a policy change when the rule looks close to the configuration they already manage. The limit shows up later. As conditions, exceptions, and shared logic grow, YAML-based rules can turn into hard-to-review templates and copied fragments.

Policy as Code Languages at a Glance

Language / FormatBest FitStrengthsTrade-offs
RegoCross-platform enforcement across CI, Kubernetes, IaC, and custom workflowsFlexible, composable, broad ecosystemHarder for non-engineers to author and review
CedarAuthorization decisions involving principals, actions, and resourcesClear authorization model, good fit for app permissionsNarrower scope than general governance policy
SentinelTerraform and Vault-centered workflowsStrong fit inside HashiCorp toolingLess portable as a general policy layer
YAML DSLsKubernetes-native guardrails and simpler cloud controlsReadable in PRs, easy to adoptLimited expressiveness for complex logic

The pattern that holds up in practice is mixed, not pure. Rego or Sentinel may handle infrastructure controls before merge. YAML-based constraints may gate cluster configuration. Cedar may sit inside the application for fine-grained authorization. That is normal.

What fails is choosing a language because the demo looked clean, then discovering six months later that no one owns policy reviews, exceptions are handled in Slack, and the only evidence of enforcement is a passing build log nobody can map back to a control. Language matters. Ownership boundaries, reviewability, and traceable enforcement matter more.

Implementation Patterns from CI to Runtime

They don't need more policy documents. They need enforcement points.

The practical patterns are straightforward. Run policy before merge, at deployment admission, and after release in runtime observation paths. Each stage produces a different artifact, and each artifact proves something different.

A diagram illustrating policy implementation stages from CI checks to admission webhooks and runtime enforcement enforcement.

Pre-merge checks

Teams usually start here, and it's still the cheapest place to fix a violation.

A pull request can run Conftest with OPA, a Sentinel policy set, or repo-native checks against Terraform, Kubernetes manifests, API specs, or deployment descriptors. The output is a CI log, a status check, or a structured PR comment. For auditors, that artifact proves the proposed change was evaluated before merge.

What works here is fast feedback and explicit messaging. A failed check should name the violated rule, the control intent, and the likely remediation. If developers have to open raw logs and reverse-engineer the failure, the control will lose trust quickly.

Admission control at deployment

CI only evaluates what was proposed. Admission control evaluates what is trying to enter the cluster or protected environment.

Kubernetes admission webhooks and similar gatekeepers are useful because they sit at the boundary where intent becomes real state. They can reject a pod, service, or configuration object that violates policy even if some earlier check was skipped or misconfigured. The artifact here is typically a webhook decision trace or an API audit event. That's often stronger audit evidence because it's tied directly to deployment-time reality.

Runtime guardrails and observed state

Runtime checks catch what static validation can't.

Some controls depend on context that only exists after deployment. Service interaction patterns, live authorization decisions, or environment-derived configuration can't always be judged from a pull request alone. Runtime agents, sidecars, or downstream monitoring controls produce audit events and system logs that show policy was observed or enforced after release.

That creates a trade-off:

  • Earlier enforcement lowers remediation cost and reduces developer rework
  • Later enforcement gives evidence closer to production truth
  • Layered enforcement is what regulated teams usually need

Recent empirical work on policy reuse found that using the same policy logic across CI/CD checks and Kubernetes admission control improves governance consistency by keeping enforcement semantics aligned across layers, which lowers the risk of drift when teams would otherwise maintain separate rule sets (research on policy reuse across CI/CD and Kubernetes).

A useful survey of policy as code tools is worth reading if you're comparing where each engine sits in that pipeline.

Mapping Controls to Design Decisions

The strongest policy programs can replay a control decision in both directions.

Start from a merge event and work backward to the design rationale. Or start from a threat model and work forward to the runtime check that enforces it. That traceability is what turns policy as code from a blocking mechanism into an actual control plane.

A diagram illustrating the mapping of security controls to design decisions through an automated policy as code process.

A concrete trace

Take a control that requires encrypted storage with customer-managed keys for a regulated workload.

The requirement usually starts upstream. A threat model identifies data exposure risk. A design review records the decision that this class of data needs stronger key control. The policy repository then encodes that decision as an executable rule against the relevant infrastructure or service config. A CI test evaluates the pull request. A runtime or admission check confirms the resulting resource still meets the rule at deploy time.

That gives you a trace chain like this:

  • Threat model decision records why stronger encryption is required
  • Control statement expresses the organizational requirement
  • Policy rule encodes the requirement in machine-readable logic
  • Pull request evaluation proves the proposed change passed or failed
  • Deployment or runtime record shows whether the live state honored the same requirement

Why auditors care about this

Without traceability, audits turn into screenshot collection. Someone exports CI output, finds an old approval ticket, and hopes the story is coherent.

With traceability, the conversation changes. A reviewer can inspect the commit that changed the infrastructure, find the matching policy rule, and connect it back to the approved design rationale quickly. That's also why executable evidence matters. Industry guidance on DevGovOps emphasizes mapping requirements to automated checks and integrating build-time evidence collection such as SBOMs and audit logs so teams can produce machine-generated proof points that support controls in frameworks like SOC 2 and ISO-style reviews (DevGovOps guidance on automated evidence collection).

A practical way to tighten that loop is to automate security requirements from design docs so the design record and the later enforcement logic don't diverge by default.

Where Policy as Code Quietly Fails

Most failed policy programs don't fail because the syntax was wrong. They fail because ownership and lifecycle discipline were never designed.

The quiet failure modes

The first problem is policy drift. Someone updates a rule in Rego or a YAML policy object, but the source document, threat rationale, or exception record doesn't move with it. The policy is now enforceable, but no longer clearly justified or aligned.

The second is shadow exceptions. A rule blocks a release, someone opens a Slack thread, and a senior engineer says “approve it just this once.” That exception may be reasonable. The problem is that it isn't versioned, time-bound, or attributable. It becomes invisible governance debt.

The third is ownership concentration. One or two policy specialists become the only people trusted to change the rule library. Delivery teams stop understanding the system, security becomes a merge bottleneck, and developers route around the policy by changing workflows instead of fixing the issue.

If only two people can safely edit policy, you haven't created automation. You've created a new approval queue.

Common Policy-as-Code Failure Modes and Their Signals

Failure ModeObservable SignalStructural Fix
Policy driftDocs, threat models, and enforced rules describe the same control differentlyTie policy changes to control mappings and review both together
Shadow exceptionsWaivers happen in chat or tickets without expiry or ownerMake exceptions codified, attributable, and time-bound
Ownership bottlenecksA small specialist group becomes the only path for policy changeExpand authorship with templates, tests, and clear review boundaries
Over-strict enforcementDevelopers mute findings or treat bots as noiseUse graduated severity and improve remediation guidance

Where the risk can increase

Independent coverage has pointed out an under-discussed risk. Once policy authoring becomes a specialized discipline, only a small set of engineers may be able to change it safely, creating governance drag instead of speed gains. The same analysis notes that practitioners still lack strong evidence on how policies are introduced, what types can be expressed, and how they're maintained in practice, which is a warning that lifecycle advice often skips the hardest parts (analysis of when policy as code adds operational risk).

Recent guidance also stresses the harder long-term issues: unclear ownership, immature policies, exception handling, deprecation timelines, and ongoing review. The organizational challenge is keeping policies versioned, tested, explainable, and aligned with fast-moving systems over time (policy readiness and adoption risks).

Bringing Policy as Code into Developer Workflows

If developers only encounter policy in audit season, the program has already failed.

Policy has to live where code changes are proposed and reviewed. That usually means repositories, local feedback loops, pull requests, and merge gates.

A diagram illustrating Policy as a Peer Repository showing how services interact with policy repositories and code changes.

Treat policy like a peer production system

A strong pattern is to keep policies in a peer repository with the same protections applied to service code. Use code owners. Require pull request review. Protect the main branch. Test policy changes against fixtures before rollout.

That does two things. It makes policy changes visible, and it stops policy from becoming an undocumented admin layer that bypasses engineering discipline.

What developers should see day to day

Developers need feedback before CI, in CI, and on the pull request itself.

Useful workflow patterns include:

  • Local checks through pre-commit hooks or IDE integrations so obvious violations show up before a branch is pushed
  • PR-level comments that explain which rule failed, what control it maps to, and what to change
  • Hard and soft enforcement where some findings warn and some block merges based on risk and confidence
  • Exception flows that require justification, owner, and expiry instead of informal approval

The daily experience matters more than the policy engine brand. If the message is opaque, developers will see policy as arbitrary. If the message is specific, policy starts to feel like guardrails rather than bureaucracy.

Roll out enforcement gradually

Don't flip everything to blocking at once.

Start with rules that are high-confidence and easy to remediate. Keep the first set narrow. Make the failure text useful. Watch where teams get confused, where false positives show up, and where policy clashes with legitimate implementation patterns. Then tighten enforcement.

One workable pattern in pull-request-centric teams is to surface policy findings directly in code review. For example, DevArmor provides policy-as-code enforcement on pull requests, including optional merge blocking and traceable outcomes tied to design and review context. That model is useful because it keeps the control decision in the same place developers already resolve code issues.

What Good Policy as Code Looks Like in Practice

Good policy as code is usually quiet.

Developers see failures on the pull request, fix them, and move on. Security can trace a control to a rule without opening three wikis. Exceptions have owners and expiration dates. Audit requests become evidence lookups, not evidence reconstruction. The control plane exists, and they only notice it when something violates it.

The better implementations also stay connected to real governance language, not just technical syntax. If you need concrete examples of how organizations phrase and structure governance expectations, these real-world data governance policies are useful reference material for turning broad policy intent into enforceable patterns.

The commitment is larger than writing rules. Teams need ownership, tests, control mapping, exception discipline, and a way to keep design intent aligned with shipped behavior. Treat policy as code as a long-term engineering system, and it reduces risk while improving traceability. Treat it like a compliance side project, and it becomes one more queue everyone resents.


DevArmor helps teams connect threat models, design reviews, and policy enforcement inside the delivery workflow instead of managing them as separate documents. If you need pull-request enforcement, traceable control mapping, and audit-friendly review evidence in a regulated environment, visit DevArmor.

Table of Contents

Subscribe