What does the plan file produced by `terraform plan -out=tfplan` actually contain, and why does that make it sensitive?
answer
- not a report, an archive
- prior state travels with it
- sensitive only hides it on screen
- binary is not encrypted
- protect it like the state file
basics
~20 sA saved plan is an opaque binary archive holding the configuration, a snapshot of prior state and every planned attribute value — including values marked sensitive, which are only masked in the printed output, not removed from the file. Protect it exactly like state.
solid answer
~40 sThe file is not a diff report; it is an archive containing the configuration, a snapshot of the state the plan was computed against, the variable values in use, and the full planned values for every resource. Marking an argument `sensitive` only redacts it from the terminal output — the real value is still inside the file, and anyone holding it can run `terraform show -json tfplan` and read database passwords, generated keys and the entire prior state. Being binary is not protection; it is not encrypted. So a saved plan gets the same handling as a state file: restricted access, short retention, encryption at rest, never a publicly downloadable build artefact, and never produced by a pipeline that untrusted pull requests can trigger. Treat leaking one as a credential exposure.
code
bash · 7 linesterraform plan -input=false -out=tfplan
# publish only the redacted human diff for reviewers
terraform show -no-color tfplan > plan.txt
# this reveals prior state and planned values in full - keep it out of logs
terraform show -json tfplan > plan.jsongo deeper
Know that a saved plan file is not a text report and should never be committed or shared casually — it can contain real secret values.
Explain what the archive holds — configuration, prior state, variable values, planned attribute values — and that marking something sensitive redacts output rather than removing anything from the file.
Show the operational handling you would insist on: restricted artefact access, short retention, encryption, no fork-triggered plan jobs, and redacted diffs rather than raw files in review threads.
Own the policy tradeoff — whether diff equality justifies holding a state-grade artefact in the CI system, who classifies it, and what an exposed plan file triggers in your incident process.
## What people assume, and what is true The assumption is that a plan file is a saved version of the pretty diff Terraform printed — a report. It is not. It is a binary archive that carries everything the apply phase needs to run without thinking again: - the configuration the plan was built from, - a snapshot of the **prior state**, - the variable values that were in effect, - the planned action and the planned attribute values for every affected object. That last item is the sting. Planned values are the concrete values that are about to be written — the database master password that came from a variable, a generated key, a token being injected into an environment variable. They sit in the file in the clear. ## `sensitive` is a display feature When you mark a variable or output `sensitive`, Terraform prints `(sensitive value)` instead of the content in the plan output and in `terraform output`. It is a shoulder-surfing and log-scraping control. It does not encrypt anything, and it does not omit anything from the state file or the plan file. Both still carry the real value, which is why the documentation says to treat state — and saved plans — as sensitive data. A candidate who says "we mark it sensitive so it is safe to publish the plan" has misunderstood the feature completely, and that is exactly the misconception this question is asked to surface. ## Binary is not encryption The format is opaque only in the sense of not being human-readable in a text editor. Terraform itself will render it for anyone who has it: ```bash terraform show tfplan # the human diff terraform show -json tfplan # everything, machine-readable ``` The JSON form deliberately exposes prior state, planned values and configuration, because that is what policy engines need to evaluate. Whoever can download the artefact gets that. ## Where it leaks in real pipelines The standard two-job pipeline — plan in one job, apply in another — has to move the file between them, and the obvious mechanism is the CI system's build-artefact store. That store is often readable by everyone in the organisation, retained for weeks by default, and sometimes readable without authentication for public repositories. The same mistake appears in a few other shapes: - posting the full `terraform show -json` output into a pull request comment, - running the plan job on pull requests from forks, where an attacker controls the code that runs with your credentials and can print or exfiltrate the plan, - keeping plan files in a shared scratch bucket with no lifecycle rule, - committing a `tfplan` to git because it was not in `.gitignore`. ## Handling it properly The controls follow from calling it what it is — a state snapshot plus secrets: 1. **Restrict who can download it.** Artefact access should match who is allowed to see state, not who can see the repository. 2. **Keep it short-lived.** Retention of hours, not the default weeks. A plan more than a few minutes old will usually be rejected as stale anyway, so long retention buys nothing but risk. 3. **Encrypt at rest and in transit**, wherever it is stored between jobs. 4. **Never expose it to untrusted triggers.** Plans run with credentials; fork pull requests must not get them. 5. **Publish the redacted human diff for review, not the raw file.** The `terraform show -no-color tfplan` output is what humans need in the review thread; the binary stays in restricted storage. ## The tradeoff to be able to argue The alternative is not saving a plan at all and re-planning inside the apply job behind an approval gate. That removes the artefact and therefore the exposure, but it also removes the guarantee that what was reviewed is what runs. Neither answer is universally right, and an interviewer at senior level is listening for you to name both sides: diff equality costs you a sensitive artefact to protect, and skipping the artefact costs you diff equality. What is not acceptable is holding the artefact while pretending it is harmless. ## A related habit The same reasoning applies to plan output in logs. CI logs are searchable, long-lived and widely readable, and a plan that dumps resource attributes into them is a slower version of the same leak. Redaction in the printed diff helps here — which is the one place `sensitive` genuinely earns its keep.
- If a variable is marked `sensitive`, is its value still in the plan file?Yes. `sensitive` is a display control: Terraform prints `(sensitive value)` in plan output and in outputs, but the real value is stored in both state and the saved plan in the clear. It protects against logs and shoulder-surfing, never against someone who obtains the file. Anyone with the plan can render it and read the value.
- How long should a plan artefact be retained in CI?Hours at most — long enough for the approval step it exists for. A plan that sits for days will usually be rejected as stale when applied anyway, so extended retention adds exposure without adding capability. Set an explicit short retention on the artefact rather than accepting the platform default, and restrict download rights to the people allowed to see state.
- What is the argument for not saving a plan file at all?It removes a sensitive artefact and the machinery to protect it: the apply job re-plans behind an approval gate instead. The cost is real — you lose the guarantee that the reviewed diff is the executed one, because apply computes a fresh plan. It is a defensible tradeoff for low-risk estates; for anything where an unexpected destroy is expensive, diff equality usually wins.
saying these in an interview costs you the question
- Says the plan file only contains the printed diff
- Thinks binary format means it is encrypted or obfuscated
- Believes marking values sensitive keeps them out of the file
- Publishes plan artefacts with default CI retention and permissions
- Runs plan jobs on pull requests from forks