CSPM rule packs
Packs are YAML files in packs/. The Go embed loads every *.yaml file
at startup and appends those rules to the catalog. Editing a pack and restarting a previously
built binary does nothing until you rebuild.
The shipped pack is cis-aws-foundations. Control numbers are inspired by CIS AWS
Foundations and one Kubernetes check. They are not a certified CIS benchmark mapping.
File shape
pack: cis-aws-foundations
version: "0.1"
description: CIS AWS Foundations–inspired checks against graph properties.
rules:
- id: cis-s3-public-access
name: S3 bucket allows public access
description: Datastore is marked publicly accessible.
framework: CIS AWS
control: "2.1.5"
resource_type: Datastore
base_score: 80
match:
properties:
public_access: true
The block above is the shipped cis-s3-public-access rule. It has no
match.graph block. cis-ec2-public-ip and
k8s-public-loadbalancer are the rules that add
internet_reachable: true.
id and resource_type are required. resource_type is a
schema node type. match.properties is an AND filter: every listed key must equal
the node’s property. Extra properties on the node are ignored. A missing key does not match.
Bool values compare as bools, so the string "true" matches a stored
true. Other values compare as strings, so 80 matches
"80".
match.graph is optional and may set any of:
internet_reachablepath_to_datastoreadmin_can_access
Those three are evaluated when the rule runs. Collectors do not have to store them. A bool
in the pack must equal the computed value or the resource does not match. Omitting a graph
key does not require that signal to be false. Base score then receives the usual graph
boosts, including signals you did not put in match.graph. A rule that matches
only when internet_reachable is false still gains +25 if the node sits on a
path to a datastore.
Each pack rule lists at most 500 nodes of resource_type, ordered by name. Nodes
past that page are not evaluated until you split the graph or the engine grows a cursor.
framework and control are documentation on the YAML. They are not
copied onto the finding.
Checks in the embedded pack
| ID | Resource | Base | Property match |
|---|---|---|---|
cis-s3-public-access | Datastore | 80 | public_access: true |
cis-s3-public-access-block | Datastore | 75 | public_access_block: disabled |
cis-s3-encryption | Datastore | 70 | encryption: false and service: s3 |
cis-s3-versioning | Datastore | 40 | versioning: false and service: s3 |
cis-sg-open-ingress | Network | 70 | open_ingress: true |
cis-iam-admin | Identity | 60 | admin_access: true |
cis-iam-no-mfa | Identity | 55 | mfa: false |
cis-iam-unused-access-keys | Identity | 45 | unused_access_keys: true |
cis-ec2-imdsv1 | Workload | 50 | imdsv2: false |
cis-ec2-public-ip | Workload | 55 | public_ip: true and graph internet_reachable: true |
k8s-public-loadbalancer | Workload | 65 | k8s_service_type: LoadBalancer and graph internet_reachable: true |
The AWS collector fills the S3, security group, IAM, and EC2 properties this pack reads.
The Kubernetes collector records Service type on Network nodes as
service_type. It does not set k8s_service_type on pods, so
k8s-public-loadbalancer matches the demo workload and any plugin that sets that
property. It does not match a live om scan k8s batch by itself.
IAM user nodes include mfa: false when no MFA device is listed. IAM roles from
the AWS collector do not set mfa, so cis-iam-no-mfa does not match
those role nodes.
Copyright © 2026 OpenSourceOM. Licensed under Apache-2.0.