compliance.tf

Reference analyzers

ctfkit by compliance.tf ships a set of reference analyzers. Each one emits findings under stable rule IDs, at a fixed level, at one or more stages. This page lists every rule ID in v0.2.1, what makes it fire, and where it stops, so you can decide which findings to act on and which to leave to the tools you already run.

It is for platform engineers deciding which findings to enforce, and for anyone writing an OPA policy or an exception against a rule ID.

Get access

ctfkit is available to compliance.tf customers on request: see Get access.

  • scan runs five families: module source coverage, disabled controls, provider and module origin, tagging, and sensitive changes.
  • report (preview) runs the coverage family over the module cache.
  • Levels: error, warning, note. --fail-on (default error) decides which levels fail a run.
  • A value that is unknown until apply never fires a rule; tagging reports it explicitly as ctf.policy.tagging_unresolved.
  • Control ids and replacement module sources come from the catalog compiled into the binary (version 2026.9.3 in v0.2.1).

Rules at a glance

Rule IDLevelStageCommand
ctf.coverage.module_sourceerror or notepre_plan, post_planscan
ctf.coverage.module_source_unknown_registrywarningpre_plan, post_planscan
ctf.policy.disabled_controlerrorpre_planscan
ctf.policy.disabled_control_unknownwarningpre_planscan
ctf.origin.providerwarningpre_plan, post_planscan
ctf.origin.provider_resource_typeerrorpre_plan, post_planscan
ctf.origin.provider_unpinnedwarningpre_planscan
ctf.origin.provider_unverifiedwarningpre_planscan
ctf.policy.floating_refwarningpre_planscan
ctf.policy.floating_ref_unboundedwarningpre_planscan
ctf.policy.taggingwarningpost_planscan
ctf.policy.tagging_valuewarningpost_planscan
ctf.policy.tagging_unresolvednotepost_planscan
ctf.risk.sensitive_change_public_exposureerror, some cases warning or notepost_planscan
ctf.risk.sensitive_change_wildcard_iamerror or warningpost_planscan
ctf.risk.sensitive_change_audit_log_removalerror, one case warningpost_planscan
ctf.risk.sensitive_change_encryption_disablementerrorpost_planscan
ctf.risk.sensitive_change_destructive_statefulerror, some cases warningpost_planscan
ctf.coverage.manifest_missingerrorpost_initreport
ctf.coverage.posture_degradederrorpost_initreport
ctf.coverage.tests_failederrorpost_initreport
ctf.coverage.delegation_unresolvedwarningpost_initreport
ctf.coverage.obligation_unknownnotepost_initreport
ctf.coverage.resource_unattestednotepost_initreport

Module source coverage

Checks whether modules and resources come from compliance.tf, and names the compliance.tf module that replaces them. Runs at pre_plan and post_plan under the same rule ID.

Rule IDLevelFires when
ctf.coverage.module_sourceerrora module source is an upstream terraform-aws-modules/* registry module. The fix is the same path on the framework host that --remediation-host selects, at the same version
ctf.coverage.module_sourcenotea module source is any other public registry module. Listed for inventory only
ctf.coverage.module_sourceerrorpost_plan only: a planned aws_* managed resource whose type a compliance.tf module covers in the catalog
ctf.coverage.module_source_unknown_registrywarninga registry address on a host that is not one of your compliance.tf endpoints

Limits:

  • Only module calls in the root module are checked.
  • Local paths, git:: and s3:: sources, archive URLs and sources on a compliance.tf host stay silent. A compliance.tf source with ?disable= belongs to the disabled-control family, so it is not reported twice.
  • A host counts as compliance.tf when it is listed in allowed_endpoints in the settings file. Without that key, any host ending in .compliance.tf counts, including your organization's host such as bobthecorp.compliance.tf. With the key, list your organization's host too.
  • The embedded catalog in v0.2.1 maps two resource types to a compliance.tf module: aws_s3_bucket to terraform-aws-modules/s3-bucket/aws (~> 5.0) and aws_sqs_queue to terraform-aws-modules/sqs/aws (~> 4.0). Other raw resources are not reported by this rule. Resources already inside a compliance.tf module are skipped.
  • The suggested host comes from --remediation-host (default soc_2, giving soc2.compliance.tf). It is always a framework host; see Hosts and registry tokens.

Disabled controls

Checks for controls switched off on a compliance.tf module source. Runs at pre_plan.

Rule IDLevelFires when
ctf.policy.disabled_controlerrora token in ?disable= on a compliance.tf module source is a control id in the catalog
ctf.policy.disabled_control_unknownwarninga token in ?disable= is not in the catalog: a typo, or the catalog is behind the registry
module "s3_bucket" {
  source = "soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?disable=s3_bucket_versioning_enabled"
}

Limits:

  • Only sources whose host ends in .compliance.tf are checked.
  • ?disable= takes a comma-separated list and may repeat; each control is reported once per module call.
  • Every .tf, .tofu, .tf.json and .tofu.json file is read, root module or not.

Provider and module origin

Checks where providers and modules come from and how tightly they are pinned. These findings carry no control id; to waive one, write an exception with rule: instead of control: (see Exceptions).

Rule IDStageLevelFires when
ctf.origin.providerpre_plan, post_planwarninga provider source is not in allowed_providers. An empty list allows every provider
ctf.origin.provider_resource_typepre_plan, post_planerrora resource type is not in allowed_resources. Active only when that list is set; data sources are not checked
ctf.origin.provider_unpinnedpre_planwarninga required_providers entry has no version constraint, or none with an upper bound
ctf.origin.provider_unverifiedpre_planwarninga required_providers entry has no hashes in .terraform.lock.hcl. A configuration with no lock file at all is not reported
ctf.policy.floating_refpre_planwarninga Git module source (git::, git@, github.com/, bitbucket.org/) has no ?ref=, or a ref that is not a full 40- or 64-character commit SHA
ctf.policy.floating_ref_unboundedpre_planwarninga registry module source has no version constraint with an upper bound

A constraint is bounded when at least one of its comma-separated parts is an exact version or starts with =, ~>, < or <=. An empty constraint, or one made only of >, >= and != parts, is unbounded.

Tagging

Checks the effective tags on planned resources against the tags and required_tags settings. Runs at post_plan, and only when the settings file defines a tag policy.

Rule IDLevelFires when
ctf.policy.taggingwarninga required tag key is missing
ctf.policy.tagging_valuewarninga known tag value is not in allowed_values, or does not match pattern
ctf.policy.tagging_unresolvednotea constrained tag value, or the whole tags map, is unknown until apply

Limits:

  • Covers managed resources being created, updated or replaced. Resources with no tags or tags_all attribute are out of scope.
  • Effective tags are the provider's constant default_tags, then tags, then tags_all.
  • A rule with resource_types applies only to those types.
  • At most one finding per resource per rule. Messages never contain a tag value.

Sensitive changes

Checks the plan for changes that weaken security or destroy data. Runs at post_plan over managed resources being created, updated, deleted or replaced. An attribute that is unknown at plan time never fires. When the catalog maps the attribute to a compliance.tf control for that resource type, the finding carries the control and is marked as one a compliance.tf module prevents.

ctf.risk.sensitive_change_public_exposure

ChangeLevel
aws_s3_bucket_public_access_block deleted, or any of block_public_acls, block_public_policy, ignore_public_acls, restrict_public_buckets goes from true to falseerror
aws_s3_bucket_acl set to public-read or public-read-writeerror
a policy with principal * and no Condition in aws_s3_bucket_policy, aws_sns_topic_policy, aws_sqs_queue_policy, aws_ecr_repository_policy, aws_opensearch_domain_policy, or aws_iam_role assume_role_policyerror
aws_lambda_permission with principal = "*" and no source_arnerror
publicly_accessible turned on for aws_db_instance, aws_rds_cluster, aws_redshift_cluster or aws_dms_replication_instanceerror
aws_instance associate_public_ip_address turned onwarning
ingress from 0.0.0.0/0 or ::/0 on aws_security_group, aws_security_group_rule or aws_vpc_security_group_ingress_ruleerror; note when the port range is exactly 80 or exactly 443

ctf.risk.sensitive_change_wildcard_iam

Checks the policy of aws_iam_policy, aws_iam_role_policy, aws_iam_user_policy and aws_iam_group_policy, and aws_iam_role inline_policy, for an Allow statement with no Condition on Resource: "*":

ActionLevel
*error
a service-wide wildcard, such as s3:*warning

ctf.risk.sensitive_change_audit_log_removal

ChangeLevel
deleting aws_cloudtrail, aws_config_configuration_recorder, aws_config_delivery_channel, aws_flow_log or aws_s3_bucket_loggingerror
aws_cloudtrail enable_logging, include_global_service_events or is_multi_region_trail goes from true to falseerror
aws_guardduty_detector enable goes from true to falseerror
aws_eks_cluster enabled_cluster_log_types or aws_rds_cluster enabled_cloudwatch_logs_exports narrowed to a subset of its previous valueerror
aws_cloudwatch_log_group retention_in_days decreases, or changes from 0 (never expire) to a numberwarning

ctf.risk.sensitive_change_encryption_disablement

Every case is an error:

  • encrypted or storage_encrypted turned off or removed on aws_ebs_volume, aws_db_instance, aws_rds_cluster, aws_efs_file_system or aws_redshift_cluster.
  • aws_dynamodb_table server_side_encryption enabled removed.
  • aws_s3_bucket_server_side_encryption_configuration deleted, or its rule block removed.
  • aws_eks_cluster encryption_config removed.
  • kms_master_key_id emptied on aws_sqs_queue or aws_sns_topic.
  • aws_kms_key enable_key_rotation turned off.

ctf.risk.sensitive_change_destructive_stateful

Stateful types: aws_db_instance, aws_rds_cluster, aws_dynamodb_table, aws_s3_bucket, aws_efs_file_system, aws_elasticache_cluster, aws_elasticache_replication_group, aws_redshift_cluster, aws_docdb_cluster, aws_neptune_cluster, aws_opensearch_domain, aws_ebs_volume, aws_fsx_lustre_file_system, aws_fsx_windows_file_system, aws_msk_cluster, aws_kms_key, aws_secretsmanager_secret.

ChangeLevel
delete or replace of a stateful resource; for a replacement, the message names the attributes that force iterror
deletion_protection or skip_destroy goes from true to falsewarning
skip_final_snapshot turned on for aws_db_instance or aws_rds_clusterwarning

Sensitive-change rules are tied to AWS provider resource types and attribute names. Resources from other providers are not checked.

Coverage (report)

Preview. ctfkit report runs these at post_init, over the compliancetf-manifest.json file that each compliance.tf served module carries in .terraform/modules. The receipt it writes is covered under report.

Terms used below:

  • A served module is a module init downloaded from a compliance.tf host.
  • Its manifest, compliancetf-manifest.json, records which controls the build enforces and what it does not prove.
  • A degraded build is one where the manifest records that part of your organization's posture could not be applied, with the reasons.
  • A delegated control is one the module leaves to another scope, such as a separate module, to enforce.
  • An obligation is something the manifest says the module does not prove, together with what you must do instead.
Rule IDLevelFires when
ctf.coverage.manifest_missingerrora module instance has no compliancetf-manifest.json: it is not served by compliance.tf, or init fetched it from elsewhere
ctf.coverage.posture_degradederrora served module's manifest records that the build was degraded; one finding per build
ctf.coverage.tests_failederrorthe --tests-json input reports failed or errored tests
ctf.coverage.delegation_unresolvedwarninga control is delegated to another scope, and no module instance in the configuration governs that scope
ctf.coverage.obligation_unknownnotea manifest lists an obligation this ctfkit version does not know
ctf.coverage.resource_unattestednotethe root module declares resources of its own, which no served module's manifest records

Limits:

  • A module instance whose own directory declares no resource block, such as a helper of data sources, locals and outputs, is listed but not reported by manifest_missing.
  • For manifest_missing, the fix names a compliance.tf module when the instance's own resource types map to one in the catalog. A remote module that was never downloaded gets no fix.
  • A manifest in a schema major version this ctfkit does not support is an environment error, exit 2, not a finding.
  • No --tests-json input means no tests_failed finding.

What analyzers never do

  • Evaluate HCL. At pre_plan only literal values resolve.
  • Guess a value that is unknown until apply.
  • Read live cloud state or call a provider. post_apply is reserved and has no analyzers.
  • Run terraform or tofu, or open a network connection.

The analyzer SDK that these families are built on is in preview.

On this page

Ask AI about this

Help improve this page