Write Custom Policies

Customize Policies with Overlays

Import an existing cnspec policy and override selected checks or policy settings without copying the base policy

A policy overlay lets you adapt an existing policy without copying and maintaining the entire policy. The overlay is a small policy that:

  1. Imports a base policy.
  2. Overrides only the checks or policy settings that your organization needs to change.

All other content continues to come from the base policy. When the base policy gains new checks or documentation, the overlay inherits those updates.

Use an overlay when you need to enforce a stricter check, put a base check into preview, deactivate a check that does not apply to your environment, or use a different scoring system.

How an overlay works

An overlay uses two types of policy groups:

  • An import group adds the base policy to the overlay.
  • An override group references checks from the imported policy and supplies the values that should change.

cnspec resolves the import and overrides into one effective policy at scan time. It does not change the base policy itself.

Create an overlay

The following self-contained bundle defines a base SSH policy and an overlay for a more restrictive production environment:

policies:
  # The policy that provides the original checks.
  - uid: example-linux-baseline
    name: Example Linux baseline
    version: 1.0.0
    scoring_system: highest impact
    tags:
      mondoo.com/category: security
      mondoo.com/platform: linux
    require:
      - provider: os
    groups:
      - title: SSH configuration
        filters:
          - mql: asset.family.contains("unix")
        checks:
          - uid: sshd-max-auth-tries
          - uid: sshd-use-pam
          - uid: sshd-port

  # The policy to assign for the production environment.
  - uid: production-linux-baseline
    name: Production Linux baseline
    version: 1.0.0
    scoring_system: highest impact
    tags:
      mondoo.com/category: security
      mondoo.com/platform: linux
    require:
      - provider: os
    groups:
      # Import every check from the base policy.
      - type: import
        policies:
          - uid: example-linux-baseline

      # Change only the checks that differ in production.
      - type: override
        title: Production requirements
        filters:
          - mql: asset.family.contains("unix")
        checks:
          - uid: sshd-max-auth-tries
            title: Limit unsuccessful SSH authentication attempts
            action: modify
            mql: sshd.config.params["MaxAuthTries"] <= 3
            impact: 90

          - uid: sshd-use-pam
            action: preview

          - uid: sshd-port
            action: deactivate

queries:
  - uid: sshd-max-auth-tries
    title: Limit unsuccessful SSH authentication attempts
    mql: sshd.config.params["MaxAuthTries"] <= 4
    impact: 70

  - uid: sshd-use-pam
    title: Enable PAM for SSH
    mql: sshd.config.params["UsePAM"] == "yes"
    impact: 60

  - uid: sshd-port
    title: Use the default SSH port
    mql: sshd.config.params["Port"] == 22
    impact: 30

The effective production-linux-baseline policy:

  • Requires no more than three authentication attempts instead of four and raises the check's impact to 90.
  • Runs and reports the PAM check without including it in the score.
  • Does not run the SSH port check.
  • Inherits every other field and any future checks from example-linux-baseline.

Assign the overlay policy instead of separately maintaining a production copy of the base policy.

You do not need to assign the base policy as well; the overlay already runs its checks. If both are assigned, each overridden check still runs once, with the override applied, and both policies report that same result. You simply see two policy entries on the asset instead of one.

Choose an override action

Each overridden check or data query references the original item by uid or mrn and uses an action to describe the change. Put scored checks under checks and data queries under queries in the override group.

ActionBehavior
modifyReplaces the fields supplied in the override and inherits fields that are omitted. Use it to change MQL or impact.
previewRuns and reports the check but excludes its result from scoring.
deactivatePrevents the check from running.
activateExplicitly activates the check.

To learn about preview behavior and remediation deadlines, read Preview Checks.

You can override check fields such as mql, impact, title, documentation, filters, properties, and variants. On a policy reference in an import group, you can override action, impact, or scoring_system:

- type: import
  policies:
    - uid: example-linux-baseline
      scoring_system: banded

When an override supplies a collection such as filters, props, tags, or variants, that collection replaces the inherited value; cnspec does not append the new entries. Include every entry that the effective check still needs.

Reference policy content outside the bundle

The first example uses UIDs because the base policy, overlay, and checks are in the same bundle. When the base policy already exists outside the overlay bundle, such as a Mondoo policy or a policy uploaded to your space, import it by its full MRN:

policies:
  - uid: overlay-test
    name: Overlay test
    version: 1.0.0
    tags:
      mondoo.com/category: security
      mondoo.com/platform: aws,cloud
    require:
      - provider: aws
    groups:
      # Import the base policy by MRN. It is not part of this bundle.
      - type: import
        policies:
          - mrn: //policy.api.mondoo.app/policies/mondoo-aws-security

      # Override one of its checks. Reference the check by UID.
      - type: override
        title: corrected checks
        filters:
          - mql: asset.platform == "terraform-plan"
        checks:
          - uid: mondoo-aws-security-kms-key-no-public-access-terraform-plan
            action: modify
            impact: 10
            mql: |
              true
    scoring_system: highest impact

Upload it and assign it to the space:

cnspec policy upload overlay-test.mql.yaml

The import group takes an MRN. A uid there means a policy defined in this same bundle.

Override checks take a UID. cnspec resolves the UID against the scopes of the policies your policy imports, so it lands on the check those policies own. You can also name a check by its full MRN if you prefer to be explicit:

- type: override
  checks:
    - mrn: //policy.api.mondoo.app/queries/CHECK_UID
      action: modify
      impact: 90

Use the identifiers from the base policy. You can download the policy to inspect its policy and check UIDs and MRNs.

Do not declare the base policy's checks in the bundle's top-level queries: block in order to reference them. A top-level entry defines a check, so an entry carrying a Mondoo-owned MRN is rejected at upload with query mrn ... is not in correct scope of owner mrn .... An override group only references a check, which is why it can name content your space does not own.

cnspec must be able to resolve every imported policy and overridden check. cnspec policy lint and offline scans have no access to Mondoo Platform, so they cannot see a policy imported by MRN. To validate an overlay entirely locally, keep the base content and overlay in the same bundle file.

Limit where or how long an override applies

Add asset filters to an override group to apply it only to matching assets. Add valid.until to stop applying the override after a date:

- type: override
  title: Ubuntu exception through October 2026
  filters:
    - mql: asset.platform == "ubuntu"
  valid:
    until: 2026-10-01
  checks:
    - uid: sshd-use-pam
      action: preview

Until October 1, 2026, the PAM check is in preview for Ubuntu assets. After that date, the override no longer applies and the imported check returns to its base behavior.

Validate and scan an overlay

If the base policy and overlay are in one file, lint and scan that bundle:

cnspec policy lint production-overlay.mql.yaml
cnspec scan local --policy-bundle production-overlay.mql.yaml

You can scan a directory when a bundle is split across files:

cnspec scan local --policy-bundle ./policies

cnspec policy lint validates each file independently. To lint cross-file references locally, first combine the base content and overlay into one self-contained bundle file.

In a self-contained bundle, linting reports an error when an override targets a check the bundle does not define:

override targets check 'sshd-prot', which this bundle does not define
(the policy imports no other policy that could own it)

When your policy imports a policy by MRN, linting cannot check the override targets: it has no access to Mondoo Platform and so cannot see which checks that policy owns. Those targets are validated when you upload the overlay, and an override that resolves to nothing is rejected there:

override targets check 'CHECK_UID', which is not defined in this bundle
and is not owned by any imported policy

Keep overlays maintainable

  • Keep the overlay small. Override only fields that intentionally differ from the base policy.
  • Use stable UIDs or MRNs. If an identifier changes in the base policy, update the overlay reference.
  • Avoid multiple matching overrides for the same field. Prefer one authoritative override so the effective behavior remains clear.
  • Lint and test the overlay after updating the base policy.

Next steps

On this page