skip to content

Why do teams run Checkov or Trivy against `terraform show -json` plan output instead of the raw HCL, and what does that cost them?

level: seniorimportance: should knowfreq 44%

answer

  1. expressions versus decided facts
  2. the variable is still a reference in HCL
  3. registry-module resources appear in the plan
  4. the accuracy arrives late and credentialed
  5. plan JSON holds real values — protect it

basics

~20 s

Plan JSON contains resolved values, so a scanner sees the attribute a resource will really have instead of an unresolved var or module reference. The cost is that it needs init, credentials and a successful plan, so it runs late and can never be a pre-commit check.

solid answer

~50 s

Scanning HCL means reading configuration full of indirection: an encryption setting may be `var.encrypted`, a CIDR may come from a local, a whole resource may live inside a registry module the scanner never expanded. The scanner has to guess, which produces both false positives and — worse — false negatives on the exact controls you care about. Plan JSON, produced by `terraform plan -out=tfplan` then `terraform show -json tfplan > tfplan.json`, has all of that resolved: variables applied, locals evaluated, `for_each` expanded, module resources present with their effective attributes. Checkov consumes it with `checkov -f tfplan.json`, and Trivy can scan plan JSON too. The costs are real: you need an initialized directory, credentials and a plan that succeeds, so this cannot be a pre-commit hook; values still unknown at plan time cannot be evaluated; findings point at plan addresses rather than source lines; and the JSON contains sensitive values, so the artifact needs protecting.

code

bash · 9 lines
bash
terraform init -input=false
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json

# scan the resolved plan rather than the source files
checkov -f tfplan.json

# the plan JSON contains real attribute values - do not publish it
rm -f tfplan tfplan.json

go deeper

for a junior

Know that scanners like Checkov and Trivy can read either Terraform source files or the JSON form of a saved plan, and that the plan is produced with terraform show -json.

for a middle

Explain why resolved values matter: in HCL an attribute may still be a variable or come from a registry module, while the plan document holds the effective value the scanner can actually test.

for a senior

Weigh the trade in pipeline terms — the accurate scan needs init, credentials and a successful plan, so it lands late — and handle the consequences: unknown values, findings keyed to plan addresses, and a JSON artifact containing sensitive data.

for a principal

Own where the enforcement point sits and what it is evidence for: a plan-stage gate that blocks a deploy has different failure costs and different auditor value than an advisory source scan, and the two are usually both worth paying for.

## Two different documents A static scanner such as Checkov or Trivy can point at either of two things, and the difference decides how much you can trust a green result. **The HCL** is what a human wrote. It is full of indirection by design: `var.`, `local.`, `for_each`, `dynamic` blocks, and `module` blocks pulling code from a registry. A scanner parsing it has to resolve as much of that as it can and then guess about the rest. **The JSON plan** is what Terraform decided will happen: ```bash terraform plan -out=tfplan terraform show -json tfplan > tfplan.json checkov -f tfplan.json ``` By this point every variable has a value, every local is evaluated, every `for_each` is expanded into concrete instances, and every module's resources are present with their effective attributes. The scanner reads facts rather than expressions. A note on the commands, because it is a common mix-up: `terraform plan -json` streams machine-readable *log* events, not a plan document. The document comes from `terraform show -json` applied to a saved plan file. ## Why the resolved view matters The canonical miss: a module takes `encrypted = var.encrypt_volumes` with a default of `false`, and a root module that never sets it. Scanning the module's HCL, the encryption attribute is an unresolved reference; a scanner that cannot evaluate it either stays quiet or fires on every caller. In the plan JSON the attribute is simply `false`, and the check is a straightforward comparison. The same argument applies to third-party modules. Most real estates get their networking and databases from registry modules, and the control-relevant attributes are set deep inside code the team did not write. Those resources appear in the plan document like any other — which means plan scanning covers the parts of your infrastructure an HCL scan of your own repository never looks at. Structurally, `terraform show -json` gives you `planned_values` — the full desired state of every resource in the configuration, changing or not — alongside `resource_changes`, which lists only what will change, plus the `configuration` and prior state. Scanning `planned_values` therefore evaluates your whole estate on every run, not just the diff, which is what you want for compliance evidence. ## What it costs 1. **Position in the pipeline.** Plan JSON requires `terraform init`, provider credentials, backend access and a plan that actually succeeds. It cannot be a pre-commit hook or a check on an untrusted fork. HCL scanning runs in a second, anywhere, with nothing configured — which is exactly why teams keep both rather than replacing one with the other. 2. **Unknown values.** Anything Terraform marks as known only after apply is opaque in the plan. A rule that depends on such an attribute cannot be evaluated, and how a given scanner treats that — skip or flag — determines whether you get a blind spot or noise. 3. **Traceability.** Findings arrive keyed to plan addresses like `module.network.aws_security_group.this[0]`, not to a file and line. Mapping them back to reviewable source takes extra configuration, and without it developers get findings they cannot act on. 4. **The artifact is sensitive.** JSON plan output carries the concrete values of resource attributes, including ones your configuration treated as sensitive. Treat `tfplan.json` as a secret: do not publish it as a world-readable build artifact, and delete it with the workspace. 5. **Coverage differences.** Not every rule a scanner ships works on both targets. Some checks are written against configuration structure and simply do not fire against a plan document, so switching targets silently changes which rules run. ## The tools themselves Checkov scans directories of HCL with `-d` and plan files with `-f`. Trivy's misconfiguration scanning covers Terraform configuration and plan JSON as well; tfsec, which many teams still name in interviews, has had its checks consolidated into Trivy by its maintainers, so a modern answer should mention that rather than proposing tfsec as a new adoption. ## How to answer Lead with the one-sentence reason — resolved values versus unresolved references — then show you know it is a trade rather than an upgrade: HCL scanning is early, free and credential-free but partially blind; plan scanning is accurate and complete but late, credentialed, and produces an artifact you have to protect. Teams that care run both, at different stages, for different reasons.

  • You add plan-JSON scanning and the finding count triples, mostly from modules nobody in your team wrote. Is that a bug?
    No — it is the point. Registry and shared modules contribute most of the control-relevant resources in a real estate, and an HCL scan of your own repository never saw them. Triage by severity, decide which of those you can actually influence through module inputs or a module version bump, and treat the rest as accepted risk recorded somewhere visible.
  • Would you drop HCL scanning once plan scanning is in place?
    No. They sit at different stages. HCL scanning is instant, needs no credentials and runs on any contribution, so it gives fast feedback before anyone has cloud access. Plan scanning is the accurate one but arrives after init, credentials and a successful plan. Keeping both means obvious mistakes die early and subtle ones still get caught before apply.
  • What should happen to tfplan.json after the scan?
    Delete it with the workspace, and keep it out of publicly readable build artifacts. The JSON contains concrete resource attribute values, including ones the configuration treated as sensitive, so it deserves the same handling as any other secret-bearing file the pipeline produces.

saying these in an interview costs you the question

  • Says terraform plan -json produces the document scanners read
  • Thinks plan scanning only covers resources that are changing
  • Assumes a clean HCL scan proves module-provided resources are safe
  • Publishes tfplan.json as a public build artifact
  • Treats plan scanning as a drop-in replacement for HCL scanning

context