Initializing Enclave...

How to Fix Terraform 'Error: Unsupported attribute' for Module Output References

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


TL;DR

  • What broke: A resource or root module is referencing module.<name>.<attribute> but that attribute is not declared in the called module's outputs.tf, causing terraform plan to hard-fail.
  • How to fix it: Declare the missing output in the child module's outputs.tf, or correct the attribute name in the reference to match an existing output.
  • Shortcut: Use our Client-Side Sandbox below to auto-refactor this — paste your module source and the referencing resource, and get the corrected output block generated instantly.

The Incident (What Does the Error Mean?)

Raw error from terraform plan:

Error: Unsupported attribute

  on main.tf line 22, in resource "aws_security_group_rule" "allow":
  22:   source_security_group_id = module.vpc.security_group_id

This object does not have an attribute named "security_group_id".

Terraform's module system is strict at plan time. When you reference module.vpc.security_group_id, Terraform looks up the outputs.tf of the ./modules/vpc directory for an output "security_group_id" block. If it doesn't exist — whether because it was never written, was renamed, or the wrong module source is pinned — the entire plan aborts. No partial plan is generated. All downstream resources dependent on this value are also blocked.


The Attack Vector / Blast Radius

This is not just a syntax annoyance. The blast radius is significant in team environments:

  • CI/CD pipelines fail hard. A terraform plan exit code 1 blocks merges, deployments, and automated apply workflows in Atlantis, Terraform Cloud, or GitHub Actions.
  • Module versioning drift. The most dangerous variant: your root module pins source = "git::...?ref=v2.1.0" but the team upgraded the child module to v3.0.0 where an output was renamed or removed. The reference silently breaks only when someone runs plan against a new workspace or after a terraform init -upgrade.
  • Cascading dependency failure. If module.vpc.security_group_id feeds into 6 downstream resources, all 6 are unplannable. Terraform does not gracefully skip them — the entire graph halts.
  • Refactoring without output hygiene. Engineers frequently refactor internal module resources (e.g., splitting one SG into multiple) and forget that external callers depend on specific output names. There is no compile-time contract enforcement by default.

How to Fix It (The Solution)

Basic Fix: Declare the Missing Output in the Child Module

If the attribute genuinely exists as a resource attribute inside the module but was never exported:

# modules/vpc/outputs.tf

+ output "security_group_id" {
+   description = "The ID of the primary VPC security group."
+   value       = aws_security_group.main.id
+ }

If the output was renamed in the module (e.g., from security_group_id to primary_sg_id), fix the caller:

# root/main.tf

  resource "aws_security_group_rule" "allow" {
-   source_security_group_id = module.vpc.security_group_id
+   source_security_group_id = module.vpc.primary_sg_id
  }

Enterprise Best Practice: Output Contract Validation with terraform-docs and Variable Validation

The root problem is no enforced contract between module producer and consumer. Fix this structurally:

Step 1 — Use explicit output descriptions and enforce them in code review via terraform-docs:

# modules/vpc/outputs.tf

- output "sg_id" {
-   value = aws_security_group.main.id
- }

+ output "security_group_id" {
+   description = "Primary security group ID. Stable interface — do not rename without major version bump."
+   value       = aws_security_group.main.id
+   precondition {
+     condition     = aws_security_group.main.id != ""
+     error_message = "Security group was not created successfully."
+   }
+ }

Step 2 — Pin module versions explicitly and document breaking output changes in CHANGELOG:

# root/main.tf

  module "vpc" {
-   source = "git::https://github.com/org/terraform-aws-vpc.git"
+   source  = "git::https://github.com/org/terraform-aws-vpc.git?ref=v2.1.0"
+   # OUTPUT CONTRACT: expects security_group_id (added v1.4.0)
  }

Step 3 — Add a terraform validate gate in CI before plan:

# .github/workflows/terraform.yml

  steps:
+   - name: Terraform Validate
+     run: terraform validate
    - name: Terraform Plan
      run: terraform plan

terraform validate catches unsupported attribute references without needing cloud credentials, making it a fast, cheap pre-flight check.


💡 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

Don't rely on engineers remembering to check output names. Enforce it in the pipeline:

1. terraform validate as a required pre-plan step Runs the full type-checker and reference resolver without hitting the API. Zero-cost, catches this exact error class in under 2 seconds.

2. Checkov — scan for undeclared outputs in module calls

checkov -d . --check CKV_TF_1

Checkov's CKV_TF_1 enforces module source pinning, reducing version-drift-induced output breakage.

3. tflint with the terraform ruleset

tflint --enable-rule=terraform_module_pinned_source

Install the tflint-ruleset-terraform plugin. The terraform_deprecated_interpolation and module reference rules will flag mismatched output references statically.

4. OPA/Conftest policy for output naming conventions

# policy/module_outputs.rego
package terraform

deny[msg] {
  output := input.configuration.root_module.module_calls[_]
  # Enforce that any module producing a security group exports a canonical name
  not output.outputs["security_group_id"]
  msg := "Module must export 'security_group_id' as a stable output interface."
}

Run with conftest test --policy policy/ plan.json against the JSON plan output.

5. terraform-docs in pre-commit hooks Auto-generates README tables of all inputs/outputs. Forces engineers to see the full output surface area before committing module changes, making accidental renames visible in PR diffs.

# .pre-commit-config.yaml
  - repo: https://github.com/terraform-docs/terraform-docs
    hooks:
      - id: terraform-docs-go
        args: ["markdown", "--output-file", "README.md", "."]

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →