Policy rules
The policy pack is the platform’s rules written as code, and this page lists every one of them: what it checks, whether it stops a change or only warns, and what its message tells you to do instead. When a merge request fails on a rule, find the rule here, read the alternative, and change the code to match.
The policy page explains where the rules run and what their two verdicts do. In short, the same rules run through airframe validate on a laptop, in the merge request’s validation, in the executor’s runs at approval, and in policy:<root> on the plan before every apply.
How to read a row
Section titled “How to read a row”Each row is one rule, with three facts and a source:
- Level is one of three:
- deny: the job fails, and GitLab merges a merge request only when its pipeline passes (enforced).
- warn: the job prints the finding and passes. The design asks for the platform’s approval of every warning; that approval is designed, so today a warning holds nothing back.
- warn in this release, deny in the next: a new rule that warns first, so teams can fix what it would refuse. That it then becomes a deny is a convention: nothing changes the level on its own, and some rules marked so are still warnings.
- What it checks says what the rule looks at. On the Terraform code the pack judges only values written out literally; on the plan it sees every value.
- What its message points to is the conforming alternative the message names, where it names one.
- Source is the file of the pack that holds the rule. The pack’s own list is
docs/policy-pack.md; a rule whose source says it is not listed there is in the code but missing from that list.
The tables describe the pack at release 2026.10.01-1, whose control-plane commit is c53c0bc.
Identity and access
Section titled “Identity and access”No public principal
- Level
- Deny
- What it checks
- A grant to
allUsersorallAuthenticatedUsersin any IAM member, binding or policy. - What its message points to
- Expose the service through external ingress behind IAP, which is designed; the built path is a route on the shared edge, behind single sign-on.
- Source
- control-plane
policy/airframe/iam.rego
No owner or editor role
- Level
- Deny
- What it checks
- The roles
roles/ownerandroles/editor, in any IAM shape. The viewer role is allowed: it is the designed standing read. - What its message points to
- Grant a predefined or custom role.
- Source
- control-plane
policy/airframe/iam.rego
No service account key
- Level
- Deny
- What it checks
- Any service account key resource.
- What its message points to
- Use workload identity federation.
- Source
- control-plane
policy/airframe/iam.rego
No IAM administration for an unlisted member
- Level
- Warn in this release, deny in the next
- What it checks
- A role that administers IAM, such as Project IAM Admin, bound at a project, folder or organization to a member the pack does not list for it.
- What its message points to
- None: a root's roles are the scope its README states, never IAM administration.
- Source
- control-plane
policy/airframe/iam.rego,input.rego; not listed indocs/policy-pack.md
Names, labels and region
Section titled “Names, labels and region”The platform's labels
- Level
- Deny
- What it checks
- A resource that declares labels must carry
managed_by,tenant,domain,product,envandcriticality. KMS keys, Secret Manager secrets and Cloud Identity groups are exempt. - What its message points to
- The message lists the missing labels. The golden path's modules set them all.
- Source
- control-plane
policy/airframe/naming.rego
Project ids follow the naming rules
- Level
- Deny
- What it checks
- A project id written out must be a product's
lk-<domain>-<product>[-np]-<rnd>, a platform project's or a playground's. - What its message points to
- The naming rules of the contracts, section 2.
- Source
- control-plane
policy/airframe/naming.rego
A raw resource needs a reason
- Level
- Warn
- What it checks
- Every resource a product root declares itself rather than through a golden path module.
- What its message points to
- Give a reason in the merge request, and use the golden path's modules where one covers the resource.
- Source
- control-plane
policy/airframe/naming.rego
A raw resource's name starts with the product's
- Level
- Warn in this release, deny in the next
- What it checks
- A raw resource whose name does not start with its
productlabel and a hyphen. - What its message points to
- Start the name with the product's slug, as the contracts' naming rules say.
- Source
- control-plane
policy/airframe/naming.rego
One region
- Level
- Deny
- What it checks
- Any region or location written out that is not
us-central1, a zone in it, orglobal. - What its message points to
- The platform's region,
us-central1, which the message names. - Source
- control-plane
policy/airframe/naming.rego
No project id or number written in the code
- Level
- Warn in this release, deny in the next
- What it checks
- Outside the project factories, any string with the shape of a project id or a project number, except the bootstrap project's own and the platform's bucket names of the same shape.
- What its message points to
- Take the project from the factory's outputs or the product's Ready output, because a rebuilt project changes both.
- Source
- control-plane
policy/airframe/literal_ids.rego; not listed indocs/policy-pack.md
Terraform code
Section titled “Terraform code”No external programs, web reads or provisioners
- Level
- Deny
- What it checks
- The
externaldata source; anhttpdata source to an address not on the allow-list, which is empty; and a provisioner on any resource. - What its message points to
- None.
- Source
- control-plane
policy/airframe/code.rego,data.json
Allowed providers and a pinned Terraform version
- Level
- Deny
- What it checks
- On the code, a provider from outside the allowed namespaces (
hashicorp/,gitlabhq/,grafana/,integrations/), and a root with norequired_version. Thatbackend.tfholds only the backend is in the pack's list but no rule checks it, so that part is designed. - What its message points to
- Pin the platform's Terraform version.
- Source
- control-plane
policy/airframe/code.rego,data.json
Remote state only between declared pairs
- Level
- Deny
- What it checks
- In a product root, reading another root's state, except a preview root reading its product's non-production state.
- What its message points to
- None beyond the rule: reading state is only for the declared producer and consumer pairs.
- Source
- control-plane
policy/airframe/code.rego,data.json
Services and data
Section titled “Services and data”Ingress only through single sign-on or the edge
- Level
- Deny
- What it checks
- A Cloud Run service open to all traffic without IAP, unless it is a preview's service or a named self-authenticating platform service; and load-balancer-only ingress on a service that is neither on the shared edge nor registered for external ingress.
- What its message points to
- A preview is reached through its router with the invoker check on; other services go on the shared edge.
- Source
- control-plane
policy/airframe/cloud_run.rego
The invoker check stays on
- Level
- Deny
- What it checks
- A Cloud Run service with its invoker check turned off, except a named self-authenticating service in a platform root or a service on the shared edge.
- What its message points to
- Only the named self-authenticating services, today the gate and the telemetry gateway, or a service on the edge may turn it off.
- Source
- control-plane
policy/airframe/cloud_run.rego,data.json
An edge service keeps its invoker check
- Level
- Warn in this release, deny in the next
- What it checks
- A service on the shared edge with its invoker check off, other than this website.
- What its message points to
- Keep the check on with IAP's service agent as the only invoker, by rendering the product again at the current golden path modules.
- Source
- control-plane
policy/airframe/cloud_run.rego; not listed indocs/policy-pack.md
A product backend has single sign-on
- Level
- Deny
- What it checks
- In a product root, a load balancer backend service without IAP.
- What its message points to
- A public route uses the external-ingress building block, which is designed.
- Source
- control-plane
policy/airframe/cloud_run.rego; not listed indocs/policy-pack.md
No public database address
- Level
- Deny
- What it checks
- A Cloud SQL instance with a public IPv4 address.
- What its message points to
- None.
- Source
- control-plane
policy/airframe/cloud_run.rego
Minimum instances above zero
- Level
- Warn
- What it checks
- A Cloud Run service that keeps instances running, apart from the sandbox's one warm instance.
- What its message points to
- Platform approval, outside criticality 1; the approval is designed.
- Source
- control-plane
policy/airframe/cloud_run.rego
A sidecar database is a preview's alone
- Level
- Warn in this release, deny in the next
- What it checks
- A container named
databasebeside a service that is not a preview's. - What its message points to
- Production and non-production use Cloud SQL.
- Source
- control-plane
policy/airframe/cloud_run.rego; not listed indocs/policy-pack.md
A service with a sidecar database runs one instance
- Level
- Warn in this release, deny in the next
- What it checks
- A service with a
databasecontainer that may scale to more than one instance. - What its message points to
- Run one instance at most, since its data lives in the instance.
- Source
- control-plane
policy/airframe/cloud_run.rego; not listed indocs/policy-pack.md
A preview creates no database instance
- Level
- Warn in this release, deny in the next
- What it checks
- A Cloud SQL, Redis or AlloyDB instance labeled with a preview's environment.
- What its message points to
- Use the shared instance of the preview substrate or a sidecar.
- Source
- control-plane
policy/airframe/cloud_run.rego; not listed indocs/policy-pack.md
Platform resources and secrets
Section titled “Platform resources and secrets”No platform-owned resource in a product root
- Level
- Deny
- What it checks
- In a product root, any of the types the platform owns, such as networks, subnets, firewalls, DNS zones, certificates, projects, folders, enabled APIs, organization policies, deny policies, perimeters, identity pools, routers and NAT.
- What its message points to
- None: these belong to the platform's roots.
- Source
- control-plane
policy/airframe/platform_owned.rego
Secret Manager is the only store, and no value passes through Terraform
- Level
- Deny
- What it checks
- A secret version with its value in
secret_data, and a secret in Vault, AWS or Azure. A version whose value is write-only,secret_data_wo, passes, because it stays out of the plan and the state. - What its message points to
- A maintainer adds the value in Secret Manager, or the value is write-only.
- Source
- control-plane
policy/airframe/platform_owned.rego
Destruction
Section titled “Destruction”No project, database, bucket or repository is destroyed outside a removal
- Level
- Warn in this release, deny in the next
- What it checks
- A plan that destroys or replaces a project, a Cloud SQL instance, a bucket or a GitLab repository, unless it belongs to a product the catalog says is removed with its date passed. The project factories' deletion protection on an active product's projects is the floor beneath it.
- What its message points to
- Set the product's lifecycle to removed, after
retire_after, in the catalog. - Source
- control-plane
policy/airframe/lifecycle.rego
The trusted runner is not replaced from its own pipeline
- Level
- Deny
- What it checks
- A plan that deletes or replaces the trusted runner's instance, which would end the job applying it.
- What its message points to
- Apply that plan from a laptop under the platform-apply grant.
- Source
- control-plane
policy/airframe/runner.rego; not listed indocs/policy-pack.md
The pipeline file
Section titled “The pipeline file”These rules read a repository’s .gitlab-ci.yml. Platform repositories are exempt from those about a product’s file.
A product's file keeps the platform's includes
- Level
- Warn in this release, deny in the next
- What it checks
- A product's file must include
tf-applyanddrift-report, must not include the injectedvalidateorbuild-testagain, must not name a job like a platform job, such asplan:orgate:, and must not declare the policy stages. - What its message points to
- Keep the includes the template rendered, remove an injected include or a policy stage, and give your own job another name.
- Source
- control-plane
policy/airframe/pipeline.rego
Components are pinned to a commit
- Level
- Warn in this release, deny in the next
- What it checks
- In every repository, a control-plane component included at a branch, a tag or a short commit.
- What its message points to
- Pin it to a full control-plane commit, as
airframe pin <sha>does. - Source
- control-plane
policy/airframe/pipeline.rego
A product's own job never deploys
- Level
- Deny
- What it checks
- A product's job that declares an
environment, directly or throughextends; an include that is not a component; and a job that triggers another pipeline. - What its message points to
- Remove the environment and deploy through the
tf-applycomponent. - Source
- control-plane
policy/airframe/pipeline.rego,docs/adr/0026-a-product-job-never-deploys.md
Only the platform's components
- Level
- Deny
- What it checks
- A product's include of a component that is not one of control-plane's own eight:
tf-apply,drift-report,image-build,promote-artifact,catalog-publish,reconcile,validateandbuild-test. - What its message points to
- The message lists the eight components a product may include.
- Source
- control-plane
policy/airframe/pipeline.rego
Dockerfiles
Section titled “Dockerfiles”Images start from a platform base image
- Level
- Deny
- What it checks
- In each Dockerfile
.airframe/images.yamllists, everyFROMand everyCOPY --fromorADD --fromthat is not a stage of the same file must name a platform base image, pinned by a tag other thanlatestor by a digest, and never computed from a build argument. - What its message points to
- Build from the platform's base image for the stack, named by its tag or its digest.
- Source
- control-plane
policy/airframe/dockerfile.rego,docs/adr/0014-approved-base-images-built-in-project-and-checked-on-every-from.md
These rules run in the executor, on the priced plans of an approved merge request, and decide the Airframe cost status check. A plan that has no price, for example without an Infracost key, is not judged.
A large cost increase
- Level
- Warn
- What it checks
- A change that adds more than 10 US dollars a month, or more than 25 percent.
- What its message points to
- Platform approval, which is designed.
- Source
- control-plane
policy/airframe/cost.rego; not listed indocs/policy-pack.md
The budget holds
- Level
- Deny
- What it checks
- A change whose projected monthly cost for the product is above its
monthly_budgetin the catalog. - What its message points to
- Raise the budget by a catalog merge request that the domain's leads approve, before this change can merge.
- Source
- control-plane
policy/airframe/cost.rego; not listed indocs/policy-pack.md
Known limits
Section titled “Known limits”These limits are open at this release, and the claims and mechanisms page lists them with the rest:
- A raw resource warns, never denies. A product may declare resources outside the golden path’s modules; the deny rules still apply to them, and so do the rules on platform-owned types.
- A resource with no labels attribute passes the label rule, which checks only the labels a resource declares.
- Dockerfiles are matched by file name. The merge request’s validation judges a change to a listed Dockerfile alone when the file is named
Dockerfile,Dockerfile.*,*.Dockerfileor*.dockerfile. A listed file under another name, such asContainerfile, is judged only when the merge request also changes another file the validation watches. - A warning holds nothing back, and nothing applies the
warnlabel or opens an exception: the approval and the exception register are designed.
Where it lives
In the control-plane repository:
