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.
scanruns 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(defaulterror) 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.3in v0.2.1).
Rules at a glance
| Rule ID | Level | Stage | Command |
|---|---|---|---|
ctf.coverage.module_source | error or note | pre_plan, post_plan | scan |
ctf.coverage.module_source_unknown_registry | warning | pre_plan, post_plan | scan |
ctf.policy.disabled_control | error | pre_plan | scan |
ctf.policy.disabled_control_unknown | warning | pre_plan | scan |
ctf.origin.provider | warning | pre_plan, post_plan | scan |
ctf.origin.provider_resource_type | error | pre_plan, post_plan | scan |
ctf.origin.provider_unpinned | warning | pre_plan | scan |
ctf.origin.provider_unverified | warning | pre_plan | scan |
ctf.policy.floating_ref | warning | pre_plan | scan |
ctf.policy.floating_ref_unbounded | warning | pre_plan | scan |
ctf.policy.tagging | warning | post_plan | scan |
ctf.policy.tagging_value | warning | post_plan | scan |
ctf.policy.tagging_unresolved | note | post_plan | scan |
ctf.risk.sensitive_change_public_exposure | error, some cases warning or note | post_plan | scan |
ctf.risk.sensitive_change_wildcard_iam | error or warning | post_plan | scan |
ctf.risk.sensitive_change_audit_log_removal | error, one case warning | post_plan | scan |
ctf.risk.sensitive_change_encryption_disablement | error | post_plan | scan |
ctf.risk.sensitive_change_destructive_stateful | error, some cases warning | post_plan | scan |
ctf.coverage.manifest_missing | error | post_init | report |
ctf.coverage.posture_degraded | error | post_init | report |
ctf.coverage.tests_failed | error | post_init | report |
ctf.coverage.delegation_unresolved | warning | post_init | report |
ctf.coverage.obligation_unknown | note | post_init | report |
ctf.coverage.resource_unattested | note | post_init | report |
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 ID | Level | Fires when |
|---|---|---|
ctf.coverage.module_source | error | a 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_source | note | a module source is any other public registry module. Listed for inventory only |
ctf.coverage.module_source | error | post_plan only: a planned aws_* managed resource whose type a compliance.tf module covers in the catalog |
ctf.coverage.module_source_unknown_registry | warning | a 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::ands3::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_endpointsin the settings file. Without that key, any host ending in.compliance.tfcounts, including your organization's host such asbobthecorp.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_buckettoterraform-aws-modules/s3-bucket/aws(~> 5.0) andaws_sqs_queuetoterraform-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(defaultsoc_2, givingsoc2.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 ID | Level | Fires when |
|---|---|---|
ctf.policy.disabled_control | error | a token in ?disable= on a compliance.tf module source is a control id in the catalog |
ctf.policy.disabled_control_unknown | warning | a 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.tfare checked. ?disable=takes a comma-separated list and may repeat; each control is reported once per module call.- Every
.tf,.tofu,.tf.jsonand.tofu.jsonfile 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 ID | Stage | Level | Fires when |
|---|---|---|---|
ctf.origin.provider | pre_plan, post_plan | warning | a provider source is not in allowed_providers. An empty list allows every provider |
ctf.origin.provider_resource_type | pre_plan, post_plan | error | a resource type is not in allowed_resources. Active only when that list is set; data sources are not checked |
ctf.origin.provider_unpinned | pre_plan | warning | a required_providers entry has no version constraint, or none with an upper bound |
ctf.origin.provider_unverified | pre_plan | warning | a required_providers entry has no hashes in .terraform.lock.hcl. A configuration with no lock file at all is not reported |
ctf.policy.floating_ref | pre_plan | warning | a 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_unbounded | pre_plan | warning | a 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 ID | Level | Fires when |
|---|---|---|
ctf.policy.tagging | warning | a required tag key is missing |
ctf.policy.tagging_value | warning | a known tag value is not in allowed_values, or does not match pattern |
ctf.policy.tagging_unresolved | note | a constrained tag value, or the whole tags map, is unknown until apply |
Limits:
- Covers managed resources being created, updated or replaced. Resources with no
tagsortags_allattribute are out of scope. - Effective tags are the provider's constant
default_tags, thentags, thentags_all. - A rule with
resource_typesapplies 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
| Change | Level |
|---|---|
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 false | error |
aws_s3_bucket_acl set to public-read or public-read-write | error |
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_policy | error |
aws_lambda_permission with principal = "*" and no source_arn | error |
publicly_accessible turned on for aws_db_instance, aws_rds_cluster, aws_redshift_cluster or aws_dms_replication_instance | error |
aws_instance associate_public_ip_address turned on | warning |
ingress from 0.0.0.0/0 or ::/0 on aws_security_group, aws_security_group_rule or aws_vpc_security_group_ingress_rule | error; 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: "*":
| Action | Level |
|---|---|
* | error |
a service-wide wildcard, such as s3:* | warning |
ctf.risk.sensitive_change_audit_log_removal
| Change | Level |
|---|---|
deleting aws_cloudtrail, aws_config_configuration_recorder, aws_config_delivery_channel, aws_flow_log or aws_s3_bucket_logging | error |
aws_cloudtrail enable_logging, include_global_service_events or is_multi_region_trail goes from true to false | error |
aws_guardduty_detector enable goes from true to false | error |
aws_eks_cluster enabled_cluster_log_types or aws_rds_cluster enabled_cloudwatch_logs_exports narrowed to a subset of its previous value | error |
aws_cloudwatch_log_group retention_in_days decreases, or changes from 0 (never expire) to a number | warning |
ctf.risk.sensitive_change_encryption_disablement
Every case is an error:
encryptedorstorage_encryptedturned off or removed onaws_ebs_volume,aws_db_instance,aws_rds_cluster,aws_efs_file_systemoraws_redshift_cluster.aws_dynamodb_tableserver_side_encryptionenabledremoved.aws_s3_bucket_server_side_encryption_configurationdeleted, or itsruleblock removed.aws_eks_clusterencryption_configremoved.kms_master_key_idemptied onaws_sqs_queueoraws_sns_topic.aws_kms_keyenable_key_rotationturned 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.
| Change | Level |
|---|---|
| delete or replace of a stateful resource; for a replacement, the message names the attributes that force it | error |
deletion_protection or skip_destroy goes from true to false | warning |
skip_final_snapshot turned on for aws_db_instance or aws_rds_cluster | warning |
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
initdownloaded 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 ID | Level | Fires when |
|---|---|---|
ctf.coverage.manifest_missing | error | a module instance has no compliancetf-manifest.json: it is not served by compliance.tf, or init fetched it from elsewhere |
ctf.coverage.posture_degraded | error | a served module's manifest records that the build was degraded; one finding per build |
ctf.coverage.tests_failed | error | the --tests-json input reports failed or errored tests |
ctf.coverage.delegation_unresolved | warning | a control is delegated to another scope, and no module instance in the configuration governs that scope |
ctf.coverage.obligation_unknown | note | a manifest lists an obligation this ctfkit version does not know |
ctf.coverage.resource_unattested | note | the root module declares resources of its own, which no served module's manifest records |
Limits:
- A module instance whose own directory declares no
resourceblock, such as a helper of data sources, locals and outputs, is listed but not reported bymanifest_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-jsoninput means notests_failedfinding.
What analyzers never do
- Evaluate HCL. At
pre_planonly literal values resolve. - Guess a value that is unknown until apply.
- Read live cloud state or call a provider.
post_applyis reserved and has no analyzers. - Run
terraformortofu, or open a network connection.
The analyzer SDK that these families are built on is in preview.