Skip to main content

Control Schema — ctrl.v1

Schema ID: urn:stave:schema:control:v0.1

A control defines a safety rule that infrastructure assets must satisfy. Controls are written in YAML and evaluated against observation snapshots.

Top-Level Structure

dsl_version: ctrl.v1
id: CTL.S3.PUBLIC.001
name: No Public S3 Buckets
description: >
S3 buckets must not allow public read or list access.
domain: exposure
scope_tags: [aws, s3]
type: unsafe_state
severity: critical
compliance:
cis_aws_v1.4.0: "2.1.5"
pci_dss_v3.2.1: "1.2.1"
soc2: "CC6.1"
params: {}
unsafe_predicate_alias: s3.is_public_readable
remediation:
description: Bucket has public read access enabled.
action: Enable S3 Public Access Block.
FieldTypeRequiredDescription
dsl_versionstringYesMust be "ctrl.v1"
idstringYesControl ID matching ^CTL\.[A-Z0-9]+\.[A-Z0-9]+(\.[A-Z0-9]+)*\.[0-9]+$
namestringYesHuman-readable name
descriptionstringYesWhat the control checks
domainenumNoexposure, identity, storage, platforms, third_party
scope_tagsarray of stringsNoVendor/technology tags (e.g., aws, s3)
versionstringNoVersion identifier
typestringNoControl type (e.g., unsafe_state, unsafe_duration)
severityenumNocritical, high, medium, low, info
complianceobjectNoCompliance framework mappings (framework name → control ID)
paramsobjectNoConfigurable parameters
unsafe_predicatepredicateConditionallyConditions that make an asset unsafe
unsafe_predicate_aliasstringConditionallyBuilt-in semantic alias expanded during load (for example s3.is_public_readable)
scopeobjectNoScope rules to limit which assets are evaluated
scope.excludepredicateNoResources matching this predicate are excluded
remediationremediationNoRemediation guidance

ID Convention

Control IDs follow the pattern CTL.<VENDOR>.<CATEGORY>.<SEQ>:

CTL.S3.PUBLIC.001
│ │ │ └── Sequence number
│ │ └──────── Category (PUBLIC, ENCRYPT, ACCESS, etc.)
│ └───────────── Vendor/service (S3, IAM, etc.)
└───────────────── Prefix (always CTL)

Multi-segment categories are allowed: CTL.S3.PUBLIC.PREFIX.001, CTL.S3.ACL.ESCALATION.001.

Predicate

A predicate defines unsafe conditions using boolean logic. It contains either any (OR) or all (AND), each holding an array of rules.

# OR: any one match triggers
unsafe_predicate:
any:
- field: properties.storage.access.public_read
op: eq
value: true
- field: properties.storage.access.public_list
op: eq
value: true

# AND: all must match
unsafe_predicate:
all:
- field: properties.storage.kind
op: eq
value: bucket
- field: properties.storage.access.has_external_access
op: eq
value: true

Predicates can be nested — rules may contain their own any/all blocks for complex logic.

You can also use a built-in alias instead of writing predicate rules manually:

unsafe_predicate_alias: s3.is_public_writable

Exactly one of unsafe_predicate or unsafe_predicate_alias must be present.

Predicate Operators

The DSL supports 15 operators:

OperatorDescriptionValue type
eqEqualsstring, bool, numeric
neNot equals (missing fields match)string, bool, numeric
gtGreater thannumeric
ltLess thannumeric
gteGreater than or equalnumeric
lteLess than or equalnumeric
inValue in listarray
missingField absent, nil, or emptyboolean (true)
presentField exists and non-emptyboolean (true)
containsString contains substringstring
any_matchAny array element matches nested predicatenested predicate
neq_fieldValue not equal to another fieldfield path
not_in_fieldValue not in another field's listfield path
list_emptyList field is empty or nilboolean (true)
not_subset_of_fieldList has elements not in another fieldfield path

Predicate semantics to note:

  • Missing fields do not match eq false — only explicitly set false triggers eq false.
  • Missing fields do match ne "value" — absence counts as "not equal."
  • value_from_param can reference a key in the control's params section instead of a literal value.

Remediation

FieldTypeRequiredDescription
descriptionstringYesWhat the violation means
actionstringYesHow to remediate
examplestringNoExample safe configuration

Control Types

TypeBehavior
unsafe_stateViolation when predicate matches in any snapshot
unsafe_durationViolation when asset is unsafe longer than --max-unsafe threshold
unsafe_recurrenceViolation when exposure window count exceeds limit within window
prefix_exposureViolation when protected prefixes are publicly readable

File Layout

Controls are organized by vendor and category:

controls/s3/
├── public/ # Public exposure checks
├── access/ # Cross-account access
├── acl/ # ACL privilege checks
├── encrypt/ # Encryption requirements
├── versioning/ # Versioning requirements
├── logging/ # Access logging
├── lifecycle/ # Lifecycle rules
├── lock/ # Object lock
├── network/ # Network conditions
├── governance/ # Tagging and governance
├── write_scope/ # Upload policy scoping
├── tenant/ # Tenant isolation
├── takeover/ # Bucket takeover
├── artifacts/ # Repository artifacts
└── misc/ # Controls and completeness

Validation

stave validate performs full JSON Schema Draft 2020-12 validation using the santhosh-tekuri/jsonschema library. Control YAML is converted to JSON internally before schema validation runs. The schema is embedded in the binary — no external files or network access needed.

Validation also runs automatically at the start of stave apply, so controls are always checked before evaluation begins.

What the schema enforces

ConstraintDetail
additionalProperties: falseExtra fields are rejected at every level — typos in field names cause immediate failure
Required fieldsdsl_version, id, name, description, and one of unsafe_predicate/unsafe_predicate_alias
const versiondsl_version must be exactly "ctrl.v1"
ID patternMust match ^CTL\.[A-Z0-9]+\.[A-Z0-9]+(\.[A-Z0-9]+)*\.[0-9]+$
enum for domainexposure, identity, storage, platforms, third_party
enum for severitycritical, high, medium, low, info
enum for op15 allowed operators: eq, ne, gt, lt, gte, lte, in, missing, present, contains, any_match, neq_field, not_in_field, list_empty, not_subset_of_field
String minimumsid, name, description must be non-empty (minLength: 1)
Remediation required fieldsIf remediation is present, description and action are required
Predicate structureunsafe_predicate must contain any or all arrays of predicate rules

Commands

# Validate a single control
stave validate --in controls/s3/public/CTL.S3.PUBLIC.001.yaml

# Validate all controls in a directory
stave validate --controls controls/s3/

# JSON output for programmatic use
stave validate --controls controls/s3/ --format json

Exit codes: 0 = valid, 2 = validation errors found.

Schema Source

The canonical schema file is schemas/ctrl.v1.schema.json.