Where Secrets Travel
Scenario: A database password never once appears in the team's Git history - and yet it turns out to be sitting, in plaintext, in Terraform state, inside a saved plan file, inside a debug log, and inside a downloadable CI artifact, all at the same time.
This module ties together sensitivity warnings you've seen scattered across the whole track (Modules 3, 4, and 6) into one complete mental model: to actually protect a secret, you have to trace every place it can travel, not just the one place you were originally worried about. A secret's full journey through Terraform can include: its original source (an environment variable, a .tfvars file, a secrets manager), the variable it's injected into, any expression that touches it, the request sent to a provider's real API, that provider's own remote logs, Terraform state (which records the value as part of a resource's attributes), any output built from it, a saved plan file, debug logs (TF_LOG), local caches, and backups of any of the above. sensitive = true only redacts the CLI's own presentation at a couple of those points - it does nothing for the rest.
Some newer Terraform capabilities around ephemeral values can go further, genuinely omitting certain sensitive values from being written into state or plan output at all, when both the provider and the surrounding context support it - a real step beyond mere display redaction. But the most reliable general strategy, wherever it's available, is to minimize how many of these places a secret ever needs to visit in the first place: prefer having the consuming system fetch a reference to a secret-manager object and a short-lived credential directly, rather than having the raw secret value itself flow through Terraform's state and outputs at all.
Analogy: sensitive = true is covering the shipping label on a parcel so a passerby glancing at it in transit can't read what's inside. Genuine data minimization is never putting the sensitive item in the parcel to begin with, and instead sending the recipient a separate, tightly controlled way to retrieve it directly.
A worked configuration
variable "token" {
type = string
sensitive = true
}
# Prefer a provider data source or workload identity when possible.
Given how many places a secret can end up, the safest default is to classify all Terraform state and all saved plan files as secret-bearing, full stop, rather than trying to remember which specific ones happen to contain something sensitive this time. Encrypt both at rest and in transit, scope read access as tightly as you scope code-review access (they are not the same privilege level - Module 4 covered this), set a short retention window, actively sanitize logs before they're shared anywhere, and make sure your CI configuration never publishes plan artifacts to pull requests coming from untrusted external forks.
Common mistakes in production
- Assuming a secret is "safe" because it never appears in Git, while ignoring that state, plans, and logs can all carry the same value in full.
- Publishing plan artifacts as a downloadable CI output on pull requests from untrusted forks - anyone who can open a PR can potentially read a plan containing sensitive values.
Goal: Treat the plan as a change contract: understand every create, update, replacement, and destroy before approving it. Reinforce this lesson in the tf-sensitive-outputs Terraform lab. Open/labs/terraformand choose slugtf-sensitive-outputs; the lab runs real Terraform against the offline FakeCloud provider.