Initializing Enclave...

How to Fix Terraform 'Cannot Output Sensitive Value Without sensitive = true' Error

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

TL;DR

  • What broke: A Terraform output block references a value marked sensitive (e.g., from random_password, aws_secretsmanager_secret_version, or a variable with sensitive = true) but the output block itself is missing the sensitive = true attribute — Terraform refuses to proceed.
  • How to fix it: Add sensitive = true to every output block that surfaces a sensitive resource attribute.
  • Action: Use our Client-Side Sandbox above to auto-refactor this — paste your outputs.tf and get corrected HCL instantly without leaking values to a third-party server.

The Incident (What Does the Error Mean?)

Raw error from terraform plan or terraform apply:

╷
│ Error: Output refers to sensitive values
│
│   on outputs.tf line 3, in output "db_password":
│    3:   value = aws_db_instance.main.password
│
│ To reduce the risk of accidentally exporting sensitive data that was not
│ intended to be exported, Terraform requires that any root module output
│ containing sensitive data be explicitly marked as sensitive, to confirm
│ your intent.
│
│ If you do intend to export this data, annotate the output value as sensitive
│ by adding `sensitive = true` to the output block.
╵

Terraform tracks a sensitivity taint on values. When any upstream resource attribute is flagged sensitive (built-in to providers like aws_db_instance.password, random_password.result, or any var declared with sensitive = true), that taint propagates through expressions. An output block consuming a tainted value must explicitly acknowledge it. Without sensitive = true, Terraform hard-stops — it will not plan, apply, or generate a plan file.


The Attack Vector / Blast Radius

This isn't bureaucratic pedantry. The enforcement exists because Terraform state and CI output are primary secret exfiltration vectors:

1. Terraform State File (terraform.tfstate) Output values are stored in plaintext in state. If your backend is an S3 bucket without strict bucket policies, or a local file committed to git, every output value is readable by anyone with state access. Sensitive outputs are still stored in state, but the sensitive = true flag signals downstream tooling (Atlantis, Spacelift, TFC) to redact the value from logs and plan UI.

2. CI/CD Log Leakage Without the flag, terraform output db_password prints the raw value to stdout. In GitHub Actions, GitLab CI, or Jenkins, this lands in build logs — often retained for 90+ days, accessible to every developer with repo read access.

3. Lateral Movement via Exposed Credentials A leaked RDS password or API token in a CI log is a direct lateral movement path. Attackers scraping exposed CI logs (a documented technique) can pivot from a compromised developer account to production databases within minutes.

4. Blast Radius A single unguarded output block for a master DB password can expose: the database itself, any service using that shared credential, and potentially the entire VPC if the credential is reused.


How to Fix It

Basic Fix

Add sensitive = true to the offending output block.

 output "db_password" {
   description = "RDS master password"
   value       = aws_db_instance.main.password
+  sensitive   = true
 }

After this change, terraform plan and terraform output will display (sensitive value) instead of the raw string.


Enterprise Best Practice

Marking an output sensitive is the minimum. In a hardened pipeline, you should also:

1. Never output raw secrets at the root module level. Consume them directly in dependent resources.

- output "db_connection_string" {
-   value     = "postgresql://${var.db_user}:${aws_db_instance.main.password}@${aws_db_instance.main.endpoint}/prod"
-   sensitive = true
- }
+
+ # Preferred: write the connection string directly to AWS Secrets Manager
+ # and output only the secret ARN — never the value itself.
+ resource "aws_secretsmanager_secret_version" "db_conn" {
+   secret_id     = aws_secretsmanager_secret.db_conn.id
+   secret_string = jsonencode({
+     url      = "postgresql://${var.db_user}:${aws_db_instance.main.password}@${aws_db_instance.main.endpoint}/prod"
+   })
+ }
+
+ output "db_secret_arn" {
+   description = "ARN of the Secrets Manager secret containing DB credentials"
+   value       = aws_secretsmanager_secret.db_conn.arn
+   # ARN is not sensitive — the secret value never leaves AWS.
+ }

2. Use remote state with encryption and strict IAM.

 terraform {
   backend "s3" {
     bucket         = "my-tfstate-bucket"
     key            = "prod/terraform.tfstate"
     region         = "us-east-1"
+    encrypt        = true
+    kms_key_id     = "arn:aws:kms:us-east-1:123456789012:key/your-kms-key-id"
   }
 }

3. Audit all outputs in a module tree. Run this before every PR merge:

grep -rn 'output' . --include='*.tf' | grep -v 'sensitive = true'

Any output referencing a password, token, key, or secret that appears in this list is a finding.


💡 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

1. Checkov — catches missing sensitive = true pre-merge:

# .github/workflows/tf-security.yml
- name: Run Checkov
  uses: bridgecrewio/checkov-action@master
  with:
    directory: ./terraform
    check: CKV_TF_1  # also add custom checks for sensitive outputs
    soft_fail: false

2. OPA / Conftest policy to enforce the flag on known-sensitive output names:

# policy/sensitive_outputs.rego
package terraform.outputs

deny[msg] {
  output := input.configuration.root_module.outputs[name]
  sensitive_pattern(name)
  not output.sensitive
  msg := sprintf("Output '%v' appears sensitive but is missing sensitive = true", [name])
}

sensitive_pattern(name) {
  patterns := ["password", "secret", "token", "key", "credential", "private"]
  p := patterns[_]
  contains(lower(name), p)
}
terraform show -json plan.tfplan | conftest test --policy policy/ -

3. tfsec inline scan:

tfsec . --minimum-severity HIGH

tfsec rule GEN006 flags outputs that reference sensitive resource attributes without the flag.

4. Pre-commit hook:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.83.5
    hooks:
      - id: terraform_tfsec
      - id: terraform_checkov

Ship none of these fixes manually. The pre-commit hook and CI gate together mean this class of misconfiguration never reaches a plan file in the first place.

Related Diagnostics

"Part of the Security Utility Matrix."

View all 140 Security Tools →