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
9.2 Sensitive Data Handling
Quick Answer:
sensitive = trueon avariableoroutputredacts 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 committerraform.tfstateor secret.tfvars. Prefer environment variables and a secret manager.terraform output -jsonand-rawprint 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.
| Behavior | Happens with sensitive = true? |
|---|---|
Redacted in terraform plan / apply / destroy logs | Yes — (sensitive value) |
| Redacted in the HCP Terraform UI | Yes |
| Derived expressions also redacted | Yes |
| Root output must also be marked if it includes the value | Yes, or Terraform errors |
Omitted from terraform.tfstate | No |
| Omitted from saved plan files | No |
| Encrypted in local state | No — local state is plaintext |
| Encrypted just because you set the flag | No |
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 arrives | Exam reading |
|---|---|
Hard-coded in a resource or provider block | Worst. It is in git history forever. |
default on a variable | Still in git. sensitive does not help. |
secret.tfvars / *.auto.tfvars on disk | Better separation, still a file; gitignore it. |
TF_VAR_db_password in the environment | Official tutorial pattern. Not in git. Still in your shell history and the process environment. |
| HCP Terraform workspace variable marked Sensitive | Encrypted 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:
| Provider | Typical env vars — not committed files |
|---|---|
| AWS | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, or an instance/OIDC role |
| Azure | ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_SUBSCRIPTION_ID, ARM_TENANT_ID |
GOOGLE_CREDENTIALS / Application Default Credentials | |
| Vault | VAULT_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
| Concern | sensitive = true (this section) | Ephemeral / write-only (9.3) |
|---|---|---|
| CLI / UI redaction | Yes | Combine sensitive + ephemeral if you also want redaction |
| Written to state | Yes | No |
| Written to plan files | Yes | No |
| Encrypts local state | No | No — it simply is not stored |
| Minimum Terraform | 0.15 | 1.10 (ephemeral), 1.11 (write-only args) |
| Legal destinations | Anywhere an expression is allowed | Only 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 = trueis not encryption and not omission from state.- Local
terraform.tfstateis plaintext, including flagged values. terraform output -jsonand-rawprint secrets. Defaultterraform outputdoes not.- An output that interpolates a sensitive variable must itself be
sensitive. - Provider access keys in committed
.tfare wrong even if you addsensitiveon an unrelated variable. - Ignoring
*.tfstatebut committingsecrets.auto.tfvarsstill leaks. - Remote state can encrypt at rest; that is the backend, not the
sensitiveflag.
You set sensitive = true on a Terraform 1.12 input variable that a managed resource then stores. Which statement is correct?
Which practice matches HashiCorp's sensitive-data guidance on Terraform 1.12?
A root module output is marked sensitive = true. How does terraform output behave on Terraform 1.12?