Get started

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_reachable
  • path_to_datastore
  • admin_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.