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
outputblock references a value marked sensitive (e.g., fromrandom_password,aws_secretsmanager_secret_version, or a variable withsensitive = true) but the output block itself is missing thesensitive = trueattribute — Terraform refuses to proceed. - How to fix it: Add
sensitive = trueto everyoutputblock that surfaces a sensitive resource attribute. - Action: Use our Client-Side Sandbox above to auto-refactor this — paste your
outputs.tfand 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.