Policies

Declarative guardrails evaluated at specific lifecycle phases before or after infrastructure changes are applied.

Built-in types: customer_zone | required_tags | naming_pattern | script | sbom_pinned_versions | sbom_allowed_registries | sbom_denied_packages | sbom_max_components | sbom_license | Phases: validate / build / plan / deploy | Enforcement: deny / warn / audit | Declared in: configuration.spec.policies


Overview

Policies let you declare constraints that strata enforces automatically as part of its standard commands — no extra scripts or wrappers required.

Unlike lifecycle hooks, which run arbitrary scripts at named points in the pipeline, policies evaluate structured data against a rule and produce a pass / fail result. The enforcement level determines what happens on failure: block the pipeline, emit a warning, or silently record the result.

Feature

Lifecycle hooks

Policies

What they do

Run arbitrary scripts

Evaluate structured rules against config/plans

Where declared

lifecycle: on any YAML document

configuration.spec.policies

Failure mode

Non-zero exit code stops the pipeline

Configurable: deny, warn, or audit

Scope

Single document (workspace/namespace/…)

Whole deployment (cross-document visibility)

Example use case

Run a backup before apply

Prevent resources in disallowed regions

The practical difference: if you need to enforce something about the infrastructure plan or configuration values, use a policy. If you need to do something at a lifecycle point (download a file, rotate a secret, send a notification), use a lifecycle hook.


Configuration

Policies are declared in your configuration.yaml under spec.policies. Each policy is an item in the list.

apiVersion: strata.huybrechts.xyz/v1
kind: configuration
meta:
  name: my-configuration
spec:
  policies:
    - name: zone_enforcement
      type: customer_zone
      phase: plan
      enforcement: deny
      description: "Reject any Terraform resource targeting a disallowed region"

Fields

Field

Required

Type

Description

name

Yes

string

Unique identifier for this policy. Used in logs and manifest records.

type

Yes

string

Policy implementation to use. See Policy types.

phase

Yes

string

Lifecycle phase when this policy runs. See Phases.

enforcement

No

string

What happens on failure: deny, warn, or audit. Default: deny.

enabled

No

bool

Set to false to temporarily disable without removing the entry. Default: true.

description

No

string

Human-readable note recorded in logs and the deployment manifest.

configuration

No

mapping

Type-specific settings. Required by some policy types (e.g. script).


Policy Types

Built-in types

Type

Phase

What it checks

tenant_zone

plan

Terraform resource regions vs. the tenant’s allowed zones

resource_type_restrictions

plan

Allow or deny Terraform resource types (allowlist or blocklist)

required_tags

build

Required tags/labels present on all resources in the platform artifact

naming_pattern

validate

meta.name fields match a configured regex pattern

ref_convention

validate

Remote references follow configured tag naming conventions

script

any

Delegates to an external command (OPA, Checkov, custom script)

sbom_pinned_versions

build

SBOM components have pinned, non-floating version tags

sbom_allowed_registries

build

Container images originate only from approved registries

sbom_denied_packages

build

No SBOM component matches a purl/name blocklist pattern

sbom_max_components

build

Total SBOM component count stays within a configured budget

sbom_license

build

SBOM component licenses match an allow/deny list (via strata:license property)

cve_max_severity

build

CVE vulnerability findings stay within a configured severity threshold

cost_threshold

plan

Estimated monthly cost (from cost.json) stays within a configured maximum

tenant_zone

Evaluates at the plan phase, after terraform plan produces a plan JSON and before terraform apply runs.

It reads:

  1. The tenant’s allowed zones from tenant.auto.tfvars.json in the working directory.

  2. The zone-to-region mapping from configuration.spec.zones.

  3. Every create and update resource change in the plan JSON.

For each resource change, it checks whether the target region (from location or region in the plan) falls within the tenant’s allowed regions. Any resource targeting a disallowed region is reported as a violation.

No configuration block is needed — the policy reads zone data from the configuration file automatically.

resource_type_restrictions

Evaluates at the plan phase. Reads every create and update resource change from the Terraform plan JSON and checks the resource type against a configured list.

Two modes:

  • deny (default) — the policy fails if any planned resource has a type on the denied list. Use this to block specific resource types outright.

  • allow — the policy fails if any planned resource has a type that is not on the allowed list. Use this for a strict allowlist.

# Block bare VMs — use managed node pools instead
- name: no_bare_vms
  type: resource_type_restrictions
  phase: plan
  enforcement: deny
  configuration:
    mode: deny
    types:
      - azurerm_virtual_machine
      - aws_instance
      - google_compute_instance

# Strict allowlist
- name: approved_types_only
  type: resource_type_restrictions
  phase: plan
  enforcement: deny
  configuration:
    mode: allow
    types:
      - azurerm_kubernetes_cluster
      - azurerm_storage_account

Configuration fields:

Field

Type

Default

Description

mode

string

deny

Operating mode: deny (block listed types) or allow (permit only listed types).

types

list of string

required

Terraform resource type strings (e.g. azurerm_virtual_machine, aws_instance).

actions

list of string

[create, update]

Which plan actions trigger the check (create, update, delete, replace).

Skipped gracefully when no plan data is available or types is empty.

required_tags

Evaluates at the build phase, after all builders have completed and the platform artifact has been generated.

It checks that every namespace in the built platform artifact has all labels listed in configuration.required_labels. Any namespace missing a required label is reported as a violation.

Skipped gracefully when no platform artifact has been generated, or when required_labels is not configured.

- name: require_standard_labels
  type: required_tags
  phase: build
  enforcement: deny
  configuration:
    required_labels:
      - environment
      - project
      - owner

naming_pattern

Evaluates at the validate phase, after structural validation completes. Requires the --deep flag on strata validate to load the configuration service.

It checks whether the meta.name field in the loaded configuration file matches the regex in configuration.pattern, using a full-string match.

Skipped gracefully when no configuration service has been loaded (i.e. strata validate was run without --deep).

- name: naming_convention
  type: naming_pattern
  phase: validate
  enforcement: warn
  configuration:
    pattern: "^[a-z][a-z0-9-]*$"

script

Delegates policy evaluation to an external command — OPA, Checkov, a custom shell script, or any executable that can read JSON from stdin.

strata launches the command, writes a JSON context object to its standard input, and waits for it to exit. Exit code 0 is a pass; any non-zero exit code is a failure. Stdout is captured as the violation message.

Context sent on stdin:

{"phase": "plan", "work_path": "/path/to/workspace"}

The script type can run at any phase — set phase to whichever phase you want the evaluation to occur. Timeout defaults to 30 seconds. Skipped gracefully when no command is configured.

- name: opa_check
  type: script
  phase: plan
  enforcement: deny
  configuration:
    command: "opa eval --data policy.rego --input /dev/stdin -"
    timeout: 30

ref_convention

Evaluates at the validate phase. Checks that remote references (git tags, branches, or commit SHAs) in deployments and environments follow declared naming conventions.

Useful for enforcing that production environments only pin to semantic version tags (e.g., v1.2.0), not branch names like main or raw commit SHAs. Works in tandem with ADR 0017 (tag-based release workflows) to validate release discipline and detect accidental manual pins.

Skipped gracefully when no deployments/environments are found, or when no remote patterns are configured.

- name: release_tag_conventions
  type: ref_convention
  phase: validate
  enforcement: warn
  configuration:
    remotes:
      - name: my-service
        release_pattern: "^v\\d+\\.\\d+\\.\\d+$"     # semver only
        quality_pattern: "^tested(-\\d+)?$"           # quality gate tags
      - name: tf-landscape
        release_pattern: "^v\\d+\\.\\d+\\.\\d+$"

Configuration fields:

Field

Type

Description

remotes

list

List of remote repository configurations

remotes[].name

string

Remote repository name (from solution.remotes)

remotes[].release_pattern

string

Regex pattern for release tags (e.g., "^v\\d+\\.\\d+\\.\\d+$" for semver)

remotes[].quality_pattern

string

Regex pattern for quality-gate tags (e.g., "^tested(-\\d+)?$")

At least one pattern (release_pattern or quality_pattern) must be configured per remote.

Validation logic:

  1. For each deployment and environment in the workspace

  2. For each spec.overrides.remotes[] reference

  3. If the remote is configured in the policy, check its reference against the declared patterns

  4. Report a violation if the reference matches neither pattern

Example violations:

environment 'production' → remote 'my-service' reference 'main' 
  does not match expected pattern (release: ^v\d+\.\d+\.\d+$ or quality: ^tested(-\d+)?$)

deployment 'acme' → remote 'tf-landscape' reference 'abc1234f' 
  does not match expected pattern (release: ^v\d+\.\d+\.\d+$)

sbom_pinned_versions

Evaluates at the build phase, after the SBOM has been generated. Checks that every SBOM component from the selected collectors has an explicit, deterministic version — no missing versions, no floating tags (e.g. :latest, any tag marked strata:tag-stability=floating).

Skipped gracefully when no SBOM components are available.

configuration key

Type

Default

Description

collectors

list of string

all

Restrict checks to these collector names (image, compose, deps, …)

allow_latest

bool

false

When true, the literal tag latest is not treated as a violation

require_digest

bool

false

When true, every component’s purl must include a @sha256:… digest

- name: pin_all_images
  type: sbom_pinned_versions
  phase: build
  enforcement: deny
  configuration:
    collectors: [image, compose]
    allow_latest: false
    require_digest: false

sbom_allowed_registries

Evaluates at the build phase. Checks that every container image component (pkg:docker/… or pkg:oci/…) originates from one of the configured registry prefixes. Non-docker/OCI purls (Helm charts, Python packages, etc.) are ignored.

Skipped gracefully when no SBOM components are available or when allowed is not configured.

configuration key

Type

Default

Description

allowed

list of string

(required)

Registry prefix strings — e.g. ghcr.io/myorg, mcr.microsoft.com

collectors

list of string

[image, compose]

Restrict checks to these collector names

Single-segment image names (e.g. pkg:docker/nginx@1.25) are resolved to docker.io/library.

- name: trusted_registries
  type: sbom_allowed_registries
  phase: build
  enforcement: deny
  configuration:
    allowed:
      - ghcr.io/myorg
      - mcr.microsoft.com
      - docker.io/library

sbom_denied_packages

Evaluates at the build phase. Blocks any SBOM component whose purl or name matches one of the configured glob patterns (Python fnmatch syntax). Use this for emergency CVE responses, deprecated package blocks, or license violations.

Skipped gracefully when no SBOM components are available or when denied is not configured.

configuration key

Type

Default

Description

denied

list of string

(required)

purl glob patterns (pkg:pypi/bad-pkg@*) or name globs (log4j*)

reason

string

"Package is on the deny list"

Custom message appended to each violation

- name: block_vulnerable
  type: sbom_denied_packages
  phase: build
  enforcement: deny
  configuration:
    denied:
      - "pkg:pypi/setuptools@<70.0.0"
      - "pkg:npm/event-stream@*"
      - "log4j*"
    reason: "Blocked by security policy SEC-2026-041"

sbom_max_components

Evaluates at the build phase. Enforces an upper bound on the total number of SBOM components and optionally per-collector limits. Useful as a complexity budget to detect dependency sprawl before it ships.

Skipped gracefully when no SBOM components are available or when neither max_count nor per_collector is configured.

configuration key

Type

Default

Description

max_count

int

none

Maximum total component count across all collectors

per_collector

map string → int

none

Per-collector limits keyed by collector name

- name: complexity_budget
  type: sbom_max_components
  phase: build
  enforcement: warn
  configuration:
    max_count: 200
    per_collector:
      image: 50
      deps: 150

sbom_license

Evaluates at the build phase. Checks each component’s strata:license property against configurable allow and deny lists using fnmatch glob patterns. Useful for enforcing open-source license compliance without external API calls — works entirely offline with data already in the SBOM.

Collectors or lockfile parsers that capture license metadata set properties["strata:license"] = "MIT" on their SbomComponentModel. Components without this property are governed by the unknown_action setting.

Skipped gracefully when no SBOM components are available or when neither allowed nor denied is configured.

configuration key

Type

Default

Description

allowed

list of string

none

SPDX license globs — only these are permitted (e.g. MIT, BSD-*)

denied

list of string

none

SPDX license globs — these are always blocked (e.g. GPL-*, AGPL-*)

unknown_action

string

"warn"

What to do when strata:license is missing: allow, warn, or deny

When both allowed and denied are configured, denied is checked first — an explicit deny always wins.

- name: license_compliance
  type: sbom_license
  phase: build
  enforcement: deny
  configuration:
    allowed:
      - MIT
      - Apache-2.0
      - BSD-*
      - ISC
    denied:
      - GPL-3.0-only
      - AGPL-*
    unknown_action: warn

cost_threshold

Evaluates at the plan phase. Reads cost.json from the deployment build directory (generated by strata cost show) and checks that the total estimated monthly cost does not exceed a configured maximum.

Skipped gracefully when:

  • cost.json does not exist (run strata cost show -f deploy.yaml first)

  • max_monthly is not configured

  • The total cost is zero or unparseable

When environment_pattern is set, the policy only activates for environments whose name matches the glob pattern — useful for applying different thresholds per tier.

configuration key

Type

Default

Description

max_monthly

number

Maximum allowed monthly cost. Required.

currency

string

USD

Currency label used in violation messages (informational — not used for conversion).

environment_pattern

string

none

Glob pattern for environment names. Policy is skipped for non-matching environments.

Prerequisites: strata cost show must run before strata deploy to generate cost.json. The policy reads the pre-computed cost file — it does not call Infracost itself.

# Block production deployments over €10,000/month
- name: prod_cost_gate
  type: cost_threshold
  phase: plan
  enforcement: deny
  description: "Block deployments with monthly cost over €10,000"
  configuration:
    max_monthly: 10000
    currency: EUR

# Warn when dev environments exceed €500/month
- name: dev_cost_cap
  type: cost_threshold
  phase: plan
  enforcement: warn
  configuration:
    max_monthly: 500
    environment_pattern: "dev*"
    currency: EUR

Enforcement Levels

Level

Behaviour on failure

deny

Stops the pipeline immediately. Exit code 3 (validation failure). No apply is run.

warn

Logs a warning to the console and continues. The pipeline completes normally.

audit

Records the result in the deployment manifest only. No console output unless --verbose. Pipeline continues.

Use deny for hard constraints (compliance, security, data residency). Use warn for advisory checks during a rollout period. Use audit to collect baseline data before deciding whether to enforce.


Phases

Phase

Triggered by

When it runs

validate

strata validate -f

After Pydantic structural validation and cross-reference checks pass

build

strata build run

After the platform artifact is generated

plan

strata deploy run

After terraform plan, before terraform apply

deploy

strata deploy run

After terraform apply completes, before the manifest is persisted

The plan phase has the highest impact: it sits between planning and applying, giving policies access to the full Terraform plan JSON. A deny at this phase prevents any infrastructure change from being made.


Example Configuration

Single policy — zone enforcement

apiVersion: strata.huybrechts.xyz/v1
kind: configuration
meta:
  name: production
  annotations:
    description: Production environment configuration
spec:
  zones:
    eu-west:
      regions:
        - westeurope
        - northeurope
    eu-central:
      regions:
        - germanywestcentral
        - swedencentral

  policies:
    - name: zone_enforcement
      type: customer_zone
      phase: plan
      enforcement: deny
      description: "All provisioned resources must be in customer-allowed zones"

With this configuration, running strata deploy run will evaluate every resource in the Terraform plan against the customer’s declared zones. If any resource targets us-east-1 or another region outside the allowed set, the deploy stops before apply with exit code 3.

Two policies — zone enforcement + required tags

  policies:
    - name: zone_enforcement
      type: customer_zone
      phase: plan
      enforcement: deny
      description: "Block resources outside customer zones"

    - name: require_standard_labels
      type: required_tags
      phase: build
      enforcement: warn
      description: "Warn when required labels are missing"
      configuration:
        required_labels:
          - environment
          - cost_center

Complete example — naming, tags, SBOM, zone, and OPA

The following configuration shows all built-in policy types working together on a single deployment. Policies are evaluated in declaration order within each phase.

apiVersion: strata.huybrechts.xyz/v1
kind: configuration
meta:
  name: production
spec:
  zones:
    eu-west:
      regions:
        - westeurope
        - northeurope

  policies:
    # validate phase: checked during `strata validate --deep`
    - name: naming_convention
      type: naming_pattern
      phase: validate
      enforcement: warn
      description: "Names must be lowercase, start with a letter"
      configuration:
        pattern: "^[a-z][a-z0-9-]*$"

    # build phase: checked after `strata build run` completes
    - name: require_standard_labels
      type: required_tags
      phase: build
      enforcement: deny
      description: "All namespaces must carry standard cost-tracking labels"
      configuration:
        required_labels:
          - environment
          - project
          - owner

    - name: pin_all_images
      type: sbom_pinned_versions
      phase: build
      enforcement: deny
      description: "Reject floating image tags  all images must be pinned"
      configuration:
        collectors: [image, compose]
        allow_latest: false

    - name: trusted_registries
      type: sbom_allowed_registries
      phase: build
      enforcement: deny
      description: "Images must come from approved registries"
      configuration:
        allowed:
          - ghcr.io/myorg
          - mcr.microsoft.com
          - docker.io/library

    - name: block_vulnerable
      type: sbom_denied_packages
      phase: build
      enforcement: deny
      description: "Block packages on the security blocklist"
      configuration:
        denied:
          - "pkg:npm/event-stream@*"
        reason: "Supply-chain attack risk"

    - name: complexity_budget
      type: sbom_max_components
      phase: build
      enforcement: warn
      description: "Alert when component count exceeds budget"
      configuration:
        max_count: 200

    - name: license_compliance
      type: sbom_license
      phase: build
      enforcement: deny
      description: "Only permissive open-source licenses allowed"
      configuration:
        allowed: [MIT, Apache-2.0, "BSD-*", ISC, Unlicense]
        denied: ["GPL-*", "AGPL-*"]
        unknown_action: warn

    # plan phase: checked after `terraform plan`, before `terraform apply`
    - name: zone_enforcement
      type: customer_zone
      phase: plan
      enforcement: deny
      description: "Resources must stay within customer-allowed regions"

    # plan phase: delegate to OPA for additional compliance checks
    - name: opa_compliance
      type: script
      phase: plan
      enforcement: deny
      description: "OPA baseline security policy"
      configuration:
        command: "opa eval --data policy.rego --input /dev/stdin -"
        timeout: 30

With this setup:

  • strata validate --deep flags configuration names that don’t follow the naming convention.

  • strata build run stops if any namespace is missing required labels, finds a floating image tag, an unapproved registry, or a blocked package. A warning is emitted if the component budget is exceeded.

  • strata deploy run runs zone enforcement and the OPA check in sequence against the Terraform plan. Both must pass before terraform apply is invoked.


See Also

  • Lifecycles — lifecycle hooks for running scripts at named pipeline points

  • Validators — structural validation of individual YAML files

  • Exit Codes3 = validation failure (policy deny failure)

  • ADR 0006 — design decisions behind the policy engine