Skip to content

Writing controls

Profile layout, control syntax, and the check blocks that make up a rule.

On this page

This page defines the files in a profile and the HCL blocks inside them: manifest, control, check, precondition, alternative group, and dependency.

Files and layout

my-profile/
├── profile.rpp          manifest, marks the directory as a profile root
└── controls/
    ├── ssh.rpc
    └── users.rpc        every *.rpc file in controls/ is loaded
Extension Role
.rpp Profile manifest
.rpc Control file. Files under controls/ without this extension are ignored

Editors do not highlight these extensions as HCL until a file type association is added.

Profile manifest

A profile is the unit of distribution and execution. It is a directory with a manifest and a set of control files, and it is what refpoint run is given. It carries the metadata, platform constraint, variables, and dependencies shared by every control inside it.

profile "linux-baseline" {
  title       = "Linux Baseline"
  description = "Server hardening baseline"
  version     = "1.2.0"
  author      = "Platform Team"

  platform = ["linux"]

  allowed_commands = ["systemctl", "getent"]

  dependency {
    path    = "../shared/iso27001-annex-a"
    include = ["^ssh-", "^audit-"]
    exclude = ["ssh-legacy-*"]
  }
}
Attribute Required Notes
label yes Profile ID
title yes
description no
version no
author no
platform no OS or distro tokens, applied to every control
allowed_commands no Executables this profile’s command checks need
depends_on no List of paths, all controls taken
dependency block no Path plus include and exclude patterns
variable block no Repeatable

Only the profile block is read from this file. Any other block is rejected. It would parse as valid HCL and have no effect, so rejection is explicit.

Control

A control is one requirement with a stable ID, such as “SSH root login is disabled”. It groups the checks that prove the requirement, carries the impact and tags used for scoring and filtering, and is the unit that receives a status in the report.

control "ssh-01" {
  title       = "SSH root login is disabled"
  description = "Direct root login over SSH removes accountability."
  impact      = 0.9
  remediation = "Set PermitRootLogin no in /etc/ssh/sshd_config and reload sshd."

  platform = ["linux"]

  tags = {
    category = "ssh"
    iso27001 = "A.8.2"
    nis2     = "21(2)(i)"
  }

  ref {
    title = "ISO/IEC 27001:2022 Annex A 8.2 Privileged access rights"
    url   = "https://example.org/policy/iso27001-a-8-2"
  }

  only_if "package" {
    name      = "openssh-server"
    installed = true
  }

  check "root-login" "sshd_config" {
    option "PermitRootLogin" {
      value = "no"
    }
  }

  any_of "time-daemon" {
    check "chrony" "service" {
      name    = "chronyd"
      running = true
    }
    check "timesyncd" "service" {
      name    = "systemd-timesyncd"
      running = true
    }
  }
}
Attribute Required Notes
label yes Control ID
title yes
description no
remediation no Free text, carried into results and reports
impact no 0.0 to 1.0. Used for weighting and filtering
tags no Object of string values. A value may be a comma-separated list
platform no Overrides the profile-level platform when non-empty
ref block no Repeatable. title and url
check block no Repeatable. Two labels: name, resource type
only_if block no At most one. One label: resource type
any_of block no Repeatable. One label: group name

Attributes and blocks outside the schema are rejected. This includes meta-arguments such as for_each, count, and dynamic, which would otherwise parse and do nothing.

Check syntax

A check is one assertion against one resource on the target: a file, a service, a package, a config option. It is the smallest unit evaluated, and a control passes only when every check in it passes.

A check has two labels, a name and a resource type, and a body. The body holds an address for addressed resource types, followed by assertions. Assertions are defined per resource type in the resource reference.

Addressed resource type:

check "<name>" "<resource_type>" {
  <address_attribute> = "<value>"
  <assertion>         = <value>
}

Label-only resource type. No address attribute. The check name is the report label:

check "<name>" "<resource_type>" {
  <assertion_block> "<label>" {
    <assertion> = <value>
  }
}

In the control above, sshd_config is label-only. Its assertion is the option "<Name>" block, and value is one of that block’s attributes.

Element Rule
Address attribute One of path, cmd, name, port, param, key, url, fixed per resource type
Address evaluation Evaluated with the profile’s variable context. Variables may be interpolated
Assertions Attributes or blocks specific to the resource type. Decoded by the matching provider
Reserved names An address attribute name cannot also be an assertion attribute for that type
Address length 4096 characters maximum
Block label length 128 characters maximum
Control characters Not permitted in addresses or labels. Both appear verbatim in logs and reports

Preconditions: only_if

A precondition is a check that gates the control. It decides whether the control applies to this target at all, so a control is skipped rather than failed on a host where it is irrelevant.

only_if outcome Control status
Passes Control runs
Does not pass skipped. Reason names the resource and the first failing assertion
Errors error, not skipped. A broken precondition must not hide a control on every run

Alternatives: any_of

An alternative group holds checks for the different ways a requirement can be met. The group counts as one check of the control and passes when any member passes.

Rule Detail
Members At least two
Satisfied when Any member passes
Execution Every member runs. No short-circuit. Each member’s state is reported
Use case A requirement with more than one acceptable implementation, such as a choice of time daemon

Dependencies

A dependency pulls another profile’s controls into this one. It lets a shared baseline be maintained once and reused, with include and exclude selecting which of its controls apply.

Rule Detail
Path resolution Relative to the profile directory
Confinement None. ../shared-baseline is valid. A dependency can pull in controls from outside the tree under review
Cycles Detected and refused. A diamond is not a cycle
Order Dependency controls first. The depending profile’s own controls override on an ID conflict
Filtering include applied first, then exclude. Same pattern syntax as --control and --skip
Platform A dependency’s profile-level platform is baked into each of its controls that declares none. The constraint survives a merge into a parent with a different platform

Parser limits

Profiles are loaded by check and list before any run. Loading must stay cheap for hostile input.

Limit Value
Single HCL file 4 MiB
Bracket nesting depth 200
Evaluated string length 256 KiB

The nesting limit guards the parser’s per-level recursion. Exceeding the stack is a fatal error, not a recoverable panic.