Initializing Enclave...

How to Fix Terraform 'Invalid Type Specification: type = string but list passed' Error

Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5 mins

TL;DR

  • What broke: A Terraform variable is declared type = string but the caller (.tfvars, module input, or -var flag) is passing a list literal — Terraform's type checker hard-fails at plan time, nothing deploys.
  • How to fix it: Change the variable's type constraint to list(string) (or the correct complex type), update the default value to a list, and add a validation block to enforce element constraints.
  • Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your broken variable block and the tool rewrites the type constraint and default in-browser without sending your config anywhere.

The Incident (What Does the Error Mean?)

Terraform's type system is evaluated at parse time, before any provider API call is made. When the runtime value does not satisfy the declared constraint, Terraform emits:

Error: Invalid value for input variable

  on terraform.tfvars line 4:
   4: allowed_cidrs = ["10.0.0.0/8", "192.168.1.0/24"]

The given value is not suitable for var.allowed_cidrs declared at
variables.tf:12,1-32: string required, but a list was provided.

Immediate consequence: terraform plan exits with code 1. No diff is computed. Every downstream resource, data, and module block that references this variable is dead. In a CI/CD pipeline this means the entire apply stage is gated and the deployment is blocked.


The Attack Vector / Blast Radius

This is a type contract violation — the variable declaration and the caller are out of sync. The blast radius depends on where the mismatch lives:

  • Root module .tfvars mismatch: Entire root module fails to plan. All resources in that workspace are undeployable until fixed.
  • Child module input mismatch: The parent module passes a list to a child module variable typed string. Every resource inside the child module is blocked. If the child module manages a security group, VPC, or IAM policy — that infrastructure is frozen in its last-applied state, which may be stale or insecure.
  • -var flag at CLI/CI level: An engineer passes --var 'subnets=["subnet-abc","subnet-def"]' against a type = string variable. The pipeline fails silently in some wrappers that swallow exit codes, meaning the deploy appears to succeed but no infrastructure was actually changed — a silent drift scenario.
  • Cascading for_each failure: If the string variable was being used in a for_each = toset(var.something) expression, the type mismatch prevents the resource graph from being constructed, wiping out the entire resource set from the plan.

How to Fix It (The Solution)

Basic Fix — Correct the Type Constraint

The caller is right; the declaration is wrong. Fix the variable block:

 variable "allowed_cidrs" {
-  type        = string
-  default     = "10.0.0.0/8"
+  type        = list(string)
+  default     = ["10.0.0.0/8"]
   description = "List of allowed CIDR blocks for ingress rules."
 }

And in terraform.tfvars, the caller syntax was already correct — no change needed there:

allowed_cidrs = ["10.0.0.0/8", "192.168.1.0/24"]

Enterprise Best Practice — Add a Validation Block and Use toset() for Deduplication

Never trust that callers will pass well-formed CIDRs. Lock it down:

 variable "allowed_cidrs" {
-  type    = string
-  default = "10.0.0.0/8"
+  type    = list(string)
+  default = ["10.0.0.0/8"]
+
+  validation {
+    condition = alltrue([
+      for cidr in var.allowed_cidrs :
+      can(cidrnetmask(cidr))
+    ])
+    error_message = "All entries in allowed_cidrs must be valid CIDR notation (e.g., 10.0.0.0/8)."
+  }
+
   description = "List of allowed CIDR blocks for ingress rules."
 }

In the resource block, use toset() to eliminate duplicate entries before iterating:

 resource "aws_security_group_rule" "allow_ingress" {
-  for_each    = var.allowed_cidrs
+  for_each    = toset(var.allowed_cidrs)
   type        = "ingress"
   from_port   = 443
   to_port     = 443
   protocol    = "tcp"
   cidr_blocks = [each.value]
   ...
 }

If you genuinely need a single string and the caller is wrong, coerce the list at the call site:

 module "network" {
   source       = "./modules/network"
-  primary_cidr = ["10.0.0.0/8"]
+  primary_cidr = "10.0.0.0/8"
 }

💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

This class of error should never reach a human in a pull request review. Automate it out:

1. terraform validate as a Pre-Commit Hook

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.92.0
    hooks:
      - id: terraform_validate
      - id: terraform_tflint

terraform validate catches type constraint violations without needing provider credentials — it's a pure static analysis pass. There is no excuse for not running this in every PR pipeline.

2. TFLint Rule — terraform_typed_variables

Add to .tflint.hcl:

rule "terraform_typed_variables" {
  enabled = true
}

This rule flags any variable missing an explicit type constraint, forcing engineers to be explicit before the mismatch can even occur.

3. Checkov Policy — Enforce Variable Type Declarations

checkov -d . --check CKV_TF_1 --framework terraform

For custom enforcement, write an OPA policy in your Atlantis or Spacelift workflow:

# policy/variable_types.rego
package terraform.variables

deny[msg] {
  variable := input.variables[name]
  not variable.type
  msg := sprintf("Variable '%v' must have an explicit type constraint.", [name])
}

4. GitHub Actions Gate

# .github/workflows/terraform-validate.yml
name: Terraform Validate
on: [pull_request]
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: "~1.9"
      - run: terraform init -backend=false
      - run: terraform validate
      - run: tflint --recursive

Block merges on this job. A failed terraform validate in CI is a 30-second fix caught before it becomes a 3 AM production incident.

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →