9.2 Sensitive Data Handling

Key Takeaways

  • sensitive = true on a variable or output redacts the value in the Terraform CLI and HCP Terraform UI as (sensitive value)
  • Marking a value sensitive does not encrypt it and does not omit it from state or plan files — anyone with those files can read the plaintext
  • Expressions that reference a sensitive value become sensitive; a root output that includes one must itself set sensitive = true
  • terraform output redacts by default, but terraform output -json and terraform output -raw print sensitive values in plain text
  • Do not commit terraform.tfstate or secret .tfvars; prefer TF_VAR_ environment variables, a secret manager, and provider credentials from the environment
Last updated: August 2026

9.2 Sensitive Data Handling

Quick Answer: sensitive = true on a variable or output redacts the value in CLI logs and the HCP Terraform UI. Terraform still writes that value to state and plan files. Marking sensitive does not encrypt state. Do not commit terraform.tfstate or secret .tfvars. Prefer environment variables and a secret manager. terraform output -json and -raw print the plaintext. Section 9.3 is the newer path that omits values from state entirely.

Objective 4h on Terraform Associate (004) is: understand best practices for managing sensitive data, including secrets management with Vault. This section is the classic half — sensitive = true — on Terraform 1.12. Official pages: Manage sensitive data, the variable sensitive argument, and the Protect sensitive input variables tutorial. HashiCorp is blunt: Terraform stores values with the sensitive argument in both state and plan files, and anyone who can access those files can access the sensitive values.

sensitive on variables and outputs shipped in 0.15. It is still the default exam answer for "hide this from the terminal." It is not the answer for "keep this out of state." That is ephemeral / write-only, next section.

What sensitive = true actually does

variable "database_password" {
  description = "Password for the database instance"
  type        = string
  sensitive   = true
}

output "connection_string" {
  description = "Database connection string"
  value       = "postgresql://${var.db_username}:${var.database_password}@${aws_db_instance.main.endpoint}/mydb"
  sensitive   = true
}

After apply, the CLI prints connection_string = (sensitive value), not the URL. Any expression that references a sensitive variable or output is itself treated as sensitive. If aws_db_instance.main.password = var.database_password, the plan shows password = (sensitive value) even on the resource argument.

If you forget sensitive = true on an output that includes a sensitive variable, Terraform errors: "Output refers to sensitive values" / expressions in outputs can only refer to sensitive values if the output's sensitive attribute is true. That is a confirmation prompt in configuration form — you must admit you are exporting a secret.

BehaviorHappens with sensitive = true?
Redacted in terraform plan / apply / destroy logsYes — (sensitive value)
Redacted in the HCP Terraform UIYes
Derived expressions also redactedYes
Root output must also be marked if it includes the valueYes, or Terraform errors
Omitted from terraform.tfstateNo
Omitted from saved plan filesNo
Encrypted in local stateNo — local state is plaintext
Encrypted just because you set the flagNo

Local state is a JSON file. HashiCorp's sensitive-variables tutorial has you grep the outputs and read "value": "notasecurepassword" sitting next to "sensitive": true. The flag is metadata. The secret is still there.

terraform output still needs care

Default terraform output (and terraform output connection_string) respects the flag and prints (sensitive value). Two flags punch through that protection, and the 1.12 manage-sensitive-data page names them:

  • terraform output -json — every output, including sensitive ones, as plain text JSON.
  • terraform output -raw NAME — the raw string, plain text.

terraform show -json has the same warning: sensitive values in state display in plain text. Automation that "just dumps outputs to the build log" is a leak. Treat those flags as explicit reveal, not as a safer machine format.

Do not commit state or secret tfvars

If you develop locally, Terraform writes terraform.tfstate (and terraform.tfstate.backup) as plaintext, including variable values you flagged sensitive. Official guidance: treat the state file as sensitive data, exclude it from Git, and follow state-security practice.

# typical Terraform .gitignore fragments
*.tfstate
*.tfstate.*
*.tfvars
*.tfvars.json
crash.log
.terraform/

GitHub's recommended Terraform gitignore ignores *.tfvars for this reason. A secret.tfvars that holds db_password = "..." is convenient and still a file on disk. Share it only with people who should see the secret, and never commit it. HashiCorp's tutorial uses -var-file="secret.tfvars" as a teaching step, then immediately warns you about version control.

Remote state changes the where, not the whether. With a remote backend, Terraform holds state in memory while a run is active. Encryption at rest is a property of the backend (HCP Terraform encrypts state at rest and uses TLS in transit; S3 depends on the bucket; Terraform itself did not encrypt the secret just because you set sensitive). Anyone who can terraform state pull still sees the plaintext values.

Prefer env vars and secret managers over plaintext in files

HashiCorp's decision tree on the 1.12 manage-sensitive-data page: first decide whether you only need to hide a value in the CLI/UI, or whether you need Terraform to omit it from state and plan. This section is hide. The operational habit is still: do not put the secret in .tf source.

How the secret arrivesExam reading
Hard-coded in a resource or provider blockWorst. It is in git history forever.
default on a variableStill in git. sensitive does not help.
secret.tfvars / *.auto.tfvars on diskBetter separation, still a file; gitignore it.
TF_VAR_db_password in the environmentOfficial tutorial pattern. Not in git. Still in your shell history and the process environment.
HCP Terraform workspace variable marked SensitiveEncrypted in HCP; still injected into the run and still lands in state if the resource stores it.
Vault / cloud secret manager (section 9.3)Preferred long-term. Read at runtime; pair with ephemeral / write-only so state never keeps a copy.
export TF_VAR_db_username=admin
export TF_VAR_db_password='a-long-random-secret'
terraform apply

Terraform looks for TF_VAR_<NAME> and assigns it to var.<NAME>. That keeps the password out of .tf and out of a committed tfvars file. It does not keep the password out of state once a managed resource such as aws_db_instance.password stores it.

Provider credentials belong in the environment, not in committed files

004 still loves a provider "aws" block with access_key and secret_key written in HCL. That is a failing answer. Provider plugins read standard environment variables:

ProviderTypical env vars — not committed files
AWSAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, or an instance/OIDC role
AzureARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_SUBSCRIPTION_ID, ARM_TENANT_ID
GoogleGOOGLE_CREDENTIALS / Application Default Credentials
VaultVAULT_ADDR, VAULT_TOKEN (HashiCorp's Vault-with-Terraform tutorials insist on this)

A provider block in git should name the region, the alias, maybe an assume-role ARN — not a secret. If you must pass a short-lived token into configuration, mark that variable sensitive = true and (on 1.12) prefer ephemeral = true so the token never hits state. That combination is the bridge into section 9.3.

Providers can also declare their own attributes as sensitive (the AWS provider already redacts aws_db_instance.password). You should still mark the variable, because the same value may appear in an output, a local, or another resource that the provider does not redact for you.

Contrast with ephemeral — what this section is not

Concernsensitive = true (this section)Ephemeral / write-only (9.3)
CLI / UI redactionYesCombine sensitive + ephemeral if you also want redaction
Written to stateYesNo
Written to plan filesYesNo
Encrypts local stateNoNo — it simply is not stored
Minimum Terraform0.151.10 (ephemeral), 1.11 (write-only args)
Legal destinationsAnywhere an expression is allowedOnly other ephemeral or write-only contexts

If the question says "redact from the terminal," sensitive is enough. If it says "do not persist in state," sensitive is the wrong tool. HashiCorp's 1.12 page opens with that fork on purpose.

004 traps for classic sensitive data

  • sensitive = true is not encryption and not omission from state.
  • Local terraform.tfstate is plaintext, including flagged values.
  • terraform output -json and -raw print secrets. Default terraform output does not.
  • An output that interpolates a sensitive variable must itself be sensitive.
  • Provider access keys in committed .tf are wrong even if you add sensitive on an unrelated variable.
  • Ignoring *.tfstate but committing secrets.auto.tfvars still leaks.
  • Remote state can encrypt at rest; that is the backend, not the sensitive flag.
Test Your Knowledge

You set sensitive = true on a Terraform 1.12 input variable that a managed resource then stores. Which statement is correct?

A
B
C
D
Test Your Knowledge

Which practice matches HashiCorp's sensitive-data guidance on Terraform 1.12?

A
B
C
D
Test Your Knowledge

A root module output is marked sensitive = true. How does terraform output behave on Terraform 1.12?

A
B
C
D