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 = stringbut the caller (.tfvars, module input, or-varflag) is passing a list literal — Terraform's type checker hard-fails atplantime, nothing deploys. - How to fix it: Change the variable's
typeconstraint tolist(string)(or the correct complex type), update thedefaultvalue to a list, and add avalidationblock to enforce element constraints. - Fast path: Use our Client-Side Sandbox above 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.
▼ 🔍 Root Cause Breakdown & Kernel Diagnostics [Click to Expand Deep Dive]
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.
▼ 🔍 Detailed Impact & Blast Radius Analysis [Click to Expand Deep Dive]
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
.tfvarsmismatch: Entire root module fails to plan. All resources in that workspace are undeployable until fixed. - Child module input mismatch: The parent module passes a
listto a child module variable typedstring. 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. -varflag at CLI/CI level: An engineer passes--var 'subnets=["subnet-abc","subnet-def"]'against atype = stringvariable. 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_eachfailure: If the string variable was being used in afor_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 infrastructure configs into public LLMs? Generic AI interfaces log your company's ARNs, connection strings, and private keys. StackEngine enforces client-side zero-trust boundaries—the sandbox above executes 100% locally in your browser. Drop your broken manifest above to redact secrets locally and synthesize deterministic patch drafts with your own API key.
(For CI/CD pipeline automation, install via
npm i -g stackengine).
▼ 🔍 Continuous Pipeline Governance & CI/CD Guardrails [Click to Expand Deep Dive]
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_tflintterraform 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 terraformFor 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 --recursiveBlock merges on this job. A failed terraform validate in CI is a 30-second fix caught before it becomes a 3 AM production incident.
Official Grounding & Normative Specifications
The technical diagnostics, error recovery protocols, and prevention patterns in this specification are grounded in official vendor documentation and standards:
Core concepts governing declarative infrastructure lifecycle, state lock contention, and provider graph synthesis.
Federal security guidelines for secret exfiltration prevention, automated integrity checks, and peer sign-offs.
Core memory subsystem controls, pressure stall information (PSI), and kernel OOM invocation mechanics.
Industry-standard specification for container execution boundaries and POSIX namespace isolation.