Documentsatlantis
Policy Checks
Policy Checks
Type
Topic
Status
Published
Created
Jul 9, 2026
Updated
Jul 9, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Policy Checks#

Policy checks allow Atlantis to run user-defined OPA/Conftest policies against Terraform plans before allowing an apply. They are enabled via --enable-policy-checks (env: ATLANTIS_ENABLE_POLICY_CHECKS=true) and are disabled by default . When enabled, the policies_passed requirement is automatically appended to apply_requirements and is treated as non-overridable .

Configuration#

Enabling#

Set --enable-policy-checks on the server or ATLANTIS_ENABLE_POLICY_CHECKS=true. This sets PolicyCheckEnabled in GlobalCfgArgs, which propagates to every project's MergedProjectCfg.PolicyCheck .

How policies_passed Is Injected#

MergeProjectCfg injects policies_passed into apply requirements at merge time. If a repo block explicitly overrides apply_requirements, the requirement is re-appended unless that repo has PolicyCheck: false .

raw.Repo.ToValid performs an analogous merge during raw-config-to-valid-config conversion: it appends any global-level apply_requirements (including policies_passed) to each repo block's requirements, skipping the append only if the repo explicitly disables policy checking.

Known Bugs#

1. apply_requirements silently reduced to [policies_passed] with multiple matching repos blocks (Issue #6623)#

Affected versions: v0.42.0–v0.46.0

Symptom: When multiple server-side repos blocks match the same repository (e.g., a catch-all /.*/ block plus a repo-specific block), the more-specific block silently overwrites apply_requirements from the broader block, leaving only [policies_passed]. An unapproved PR can then apply without triggering the approved or mergeable requirements.

Root cause — two interacting issues:

  1. getMatchingCfg uses last-match-wins. The function iterates through all matching repos and does a plain assignment (applyReqs = repo.ApplyRequirements) on every match . Earlier, more-general blocks are silently overwritten by later, more-specific ones.

  2. raw.Repo.ToValid makes every repo block's ApplyRequirements non-nil. Because it always merges global requirements, the second repo block ends up with a non-nil ApplyRequirements: [policies_passed] even though no explicit apply_requirements was set in YAML. This satisfies the if repo.ApplyRequirements != nil guard in getMatchingCfg, triggering the overwrite .

Workaround: Explicitly repeat the full requirement list on every more-specific repos block:

repos:
  - id: /.*/
    apply_requirements: [approved, mergeable, undiverged]
  - id: github.com/myorg/myrepo
    allow_custom_workflows: true
    apply_requirements: [approved, mergeable, undiverged] # must be repeated

Note: This bug is distinct from the previously fixed inheritance issues #3908 and #3960, which patched MergeProjectCfg for repo-level atlantis.yaml config but left getMatchingCfg (the server-side path) untouched .


2. POST /api/plan panics and loses policy approvals when policy checks are enabled (PR #6484)#

Symptom: With ATLANTIS_ENABLE_POLICY_CHECKS=true, calling POST /api/plan causes a panic. Policy check contexts were also incorrectly routed to the plan runner, producing PolicyCheckResults=nil errors, and existing policy approvals were not preserved across API calls .

Root cause:

  • The code tracked a separate policyCC (comment-commands) slice for policy checks but indexed into it using indices from the full policyCmds slice. When plan inputs expanded into multiple command types, this caused an out-of-bounds panic .
  • Existing PullStatus was not being fetched for API-backed plan requests, so sticky policy approvals from prior runs were lost .

Fix (merged 2026-06-30): PR #6484 made three changes across api_controller.go and server.go:

  1. Removed the mismatched policyCC index tracking; policy-check commands are now passed directly to ProjectPolicyCheckCommandRunner.
  2. Added a PullStatusFetcher field to APIController and a populatePullStatus method that fetches existing pull status from the database when a PR number is present.
  3. apiParseAndValidate now calls populatePullStatus so policy-check contexts carry the correct prior approval state. Failures are non-fatal: the method logs a warning and continues .

PullStatus and Policy State Persistence#

Policy check results are stored as PolicyStatus []PolicySetStatus within ProjectStatus. The relevant status constants are PassedPolicyCheckStatus ("policy_check_passed") and ErroredPolicyCheckStatus ("policy_check_errored") . Persistence goes through BoltDB.UpdatePullWithResults, which merges new results with existing status to preserve approvals across separate plan and policy-check DB writes.

Key Source Files#

FilePurpose
server/core/config/valid/global_cfg.goMergeProjectCfg, getMatchingCfg, NonOverridableApplyReqs
server/core/config/raw/global_cfg.goraw.Repo.ToValid — merges global requirements into repo blocks
server/controllers/api_controller.goAPIController, populatePullStatus, apiParseAndValidate
server/events/models/models.goProjectStatus, PolicySetStatus, status constants
server/db/boltdb.goUpdatePullWithResults — policy state persistence
cmd/server.goEnablePolicyChecksFlag definition
Policy Checks | Dosu