compliance.tf

No Provisioners

Removes all provisioner blocks from resources. Provisioners bypass the declarative model and are a supply chain risk in shared modules.

When to use this rule

Use this when: You use community Terraform modules from public registries and want to ensure no arbitrary code runs during terraform apply. Or your platform team has banned provisioners as a policy.

Do not use this when: Your modules intentionally use provisioners for bootstrapping that cannot be replaced with user_data, cloud-init, or configuration management tools. This is rare in terraform-aws-modules.


Why this rule exists

Terraform provisioners run arbitrary commands during terraform apply. They don't appear in terraform plan output. In a module sourced from a public registry, provisioner blocks are a supply chain risk: local-exec can modify local files, call APIs, or exfiltrate data.

HashiCorp's own documentation says "provisioners are a last resort." Many platform teams ban them entirely. This rule enforces that ban at the module level by removing all provisioner blocks before the module reaches the developer.


Affected resources

ResourceServiceWhy
*All resourcesAny resource that contains provisioner blocks (local-exec, remote-exec, file)

What this rule does

Removes every provisioner block from every resource in the module source. The resource itself is kept; only the block is deleted.


Before and after

Before (module with provisioners):

resource "aws_instance" "this" {
  ami           = var.ami_id
  instance_type = var.instance_type

  provisioner "local-exec" {
    command = "echo ${self.private_ip} >> hosts.txt"
  }

  provisioner "remote-exec" {
    inline = ["sudo apt-get update"]
  }
}

After (with No Provisioners applied):

resource "aws_instance" "this" {
  ami           = var.ami_id
  instance_type = var.instance_type
}

The only change is the rule transformation. All existing arguments, outputs, and module behavior remain identical.

Real-world scenario

A community module included a local-exec provisioner that ran curl to an external API during terraform apply. The API was decommissioned, causing every apply to hang for 30 seconds before timing out. The provisioner was not visible in terraform plan output.


Known limits

  • Removes the provisioner block, not the resource holding it. A null_resource that existed only to run provisioners is left behind as an empty resource that still appears in your plan and state.
  • Removes provisioner blocks only. A connection block that configured a remote-exec provisioner stays in the resource, where it now has nothing to configure.
  • Does not affect local-exec or remote-exec in root module code — only in the downloaded module source.
  • Does not detect or remove provisioner-like behavior implemented through external data sources or Lambda functions.
  • Only .tf and .tofu files are read. Modules written in JSON syntax (.tf.json, .tofu.json) are left untouched.
  • Only the module's own directory and its first-level modules/* submodules are processed. Modules that this module calls by source URL are separate downloads and are handled on their own request.

Compliance framework support

This rule is not a compliance control. It supports these framework objectives as an operational safeguard:

FrameworkControlsRole
SOC 2CC8.1Supports change management by removing unreviewed imperative code from modules
NIST 800-53CM-3, SI-7Supports configuration management and software integrity

Default configuration

These are the values the rule ships with. They are not settable from a module source URL: ?rules= carries rule names only, so a per-request enable uses exactly the defaults below. Your organization sets them per rule, in its Baseline or in a ruleset, from the Operational Rules page; see Baseline rules and rulesets.

ParameterTypeDefaultDescription
resource_typeslist(string)["*"]Target resource type patterns

How to enable

On a framework host, per request:

Add ?rules=provisioner_remove_blocks to your HTTPS module source. Use the full namespaced id. A bare provisioner_remove_blocks matches no rule: it is skipped server-side and your terraform init still succeeds, without the rule.

module "example" {
  source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&rules=provisioner_remove_blocks"
}

On your organization's host:

Add the rule to the organization's Baseline, which applies to every module served from the bare organization form <alias>.compliance.tf/<namespace>/<name>/<provider>, or to a named ruleset that one module selects with ?ruleset=<name> on the HTTPS form of that address. A project environment (<alias>.compliance.tf/<project>/<env>/<module>) serves the rules frozen in its bound snapshot instead: there the rule goes into the config draft and reaches the environment by promotion. On both organization forms ?rules= is refused with a 400 — including the subtractive - prefix — rather than dropped, so you cannot mistake an ignored override for an applied one. Change the configuration, not the URL. See Baseline rules and rulesets for the configuration flow, and registry resolution for how the address forms differ.


Failure modes

ScenarioResult
Module relies on a provisioner for required setupThe module may not function correctly after provisioner removal. This is rare in terraform-aws-modules. Test in non-production first.
A null_resource exists only to run a provisionerThe provisioner is removed; the null_resource is not. It survives as an empty resource in your plan and state. Removing the resource itself needs a different transformer, which this rule does not use.

Terraform and OpenTofu compatible

This rule works with both Terraform (1.x+) and OpenTofu (1.6+). It reads ordinary HCL, and anything it changes stays ordinary HCL, so the module behaves the same under either tool.

Help us improve this page

Operational Rules are a new feature. We'd love your feedback on this rule page — what's useful, what's missing, what's confusing. Share feedback.

On this page

Ask AI about this

Help improve this page