skip to content

A Checkov scan of Terraform source passes a bucket whose `acl = var.acl` is public-read only in `prod.tfvars` — why, and how do you make Checkov see the production value?

level: seniorimportance: should knowfreq 15%

answer

  1. which values Checkov resolves
  2. Terraform's automatic tfvars files
  3. unresolved means UNKNOWN
  4. the plan already has values

basics

~10 s

Checkov renders variables only from defaults, TF_VAR_ variables and Terraform's automatic tfvars files, so it judged the default. Pass --var-file prod.tfvars, or scan the plan JSON with the terraform_plan framework.

solid answer

~40 s

Checkov evaluates variables and locals by default, but from the same automatic sources Terraform uses: variable defaults, `TF_VAR_` environment variables, `terraform.tfvars`, `terraform.tfvars.json` and `*.auto.tfvars`. A `prod.tfvars` that the pipeline passes to Terraform with `-var-file` is not read unless you give Checkov `--var-file prod.tfvars` too, which works only with `--directory`. So the bucket was judged against its default — `private` passes. With no default at all, a value check on that attribute returns UNKNOWN, which is recorded as neither passed nor failed, so the bucket silently vanishes from that check. The alternative is to scan `terraform show -json` output: Checkov's `terraform_plan` framework picks up the JSON, and `--repo-root-for-plan-enrichment` maps findings back to the HCL lines.

code

bash · 7 lines
bash
# source scan, rendered with the production variables
checkov -d infra/ --var-file infra/env/prod.tfvars -o cli -o junitxml --output-file-path console,checkov-prod.xml

# plan scan of the same configuration
terraform -chdir=infra plan -var-file=env/prod.tfvars -out tfplan.binary
terraform -chdir=infra show -json tfplan.binary > tfplan.json
checkov -f tfplan.json --repo-root-for-plan-enrichment infra/

go deeper

for a junior

Remember that Checkov fills in variable values itself when scanning .tf files, and that it does not know which environment file your pipeline uses.

for a middle

Explain the automatic tfvars sources, what --var-file adds, and why an unresolved value gives no result instead of a failure.

for a senior

Show you can make a source scan judge the production values, or move to the terraform_plan framework with enrichment, and say what each costs.

for a principal

Decide per estate whether environment-specific values are scanned at source, at plan time or both, given credentials, speed and who reads the findings.

## How Checkov treats a Terraform variable When Checkov scans Terraform **source** (`.tf` files), it does not run Terraform. It parses the HCL and then performs **variable rendering**: it replaces `var.` and `local.` references with values it can find, so that checks compare real values rather than expressions. This is on by default (`--evaluate-variables`, environment variable `CKV_EVAL_VARS`). The CLI output shows what it did, for example: ``` Variable acl (of /variables.tf) evaluated to value "private" in expression: acl = ${var.acl} ``` and the JSON output carries an `evaluations` block per result with the variable's file, value and the expressions that used it. ## Where the values come from Checkov mirrors Terraform's **automatic** variable sources, in this order of precedence (later wins): 1. `default` values in `variable` blocks; 2. `TF_VAR_<name>` environment variables; 3. `terraform.tfvars`, then `terraform.tfvars.json`; 4. `*.auto.tfvars` and `*.auto.tfvars.json`, in lexical order; 5. files named with `--var-file`. The last one is the trap. Many teams keep one file per environment — `dev.tfvars`, `prod.tfvars` — and pass it to Terraform with `-var-file`. Checkov has no idea which file your pipeline uses unless you tell it with **`--var-file`**, which can be repeated, needs `--directory` rather than `--file`, and works for Terraform source and Helm scans. ## Two ways the bucket slips through | `variable "acl"` | `prod.tfvars` | What Checkov sees without `--var-file` | Result | |---|---|---|---| | `default = "private"` | `acl = "public-read"` | `private` | the check **passes** against the wrong environment | | no default | `acl = "public-read"` | an unresolved `var.acl` | **UNKNOWN** — no result recorded | The second row deserves attention. When a value comparison meets an attribute that still depends on an unrendered Terraform variable, Checkov's check returns **UNKNOWN**. The report only records PASSED, FAILED and SKIPPED, so an UNKNOWN result simply does not appear: the bucket is missing from that check's results, the summary counts do not mention it, and the job stays green. ## Fix one: render per environment - Run Checkov once per environment with the same variable file Terraform uses: `checkov -d . --var-file prod.tfvars`. - Keep the variable file path in the pipeline definition next to the Terraform step, so the two cannot drift. - Read the `evaluations` data in the JSON output when a result surprises you; it says which file supplied each value. This stays a source scan: fast, no cloud credentials, findings point at your `.tf` lines. Common mistakes on this route: - passing `--var-file` together with `-f`, which the flag does not support — it needs `--directory`; - scanning only the development variables because that is what the pull-request job uses, while production values differ; - assuming an empty result for a check means a pass, when it can mean UNKNOWN. ## Fix two: scan the plan with `terraform_plan` A Terraform plan already contains resolved values. Checkov's **`terraform_plan`** framework scans the JSON form of a plan: ``` terraform plan -out tfplan.binary terraform show -json tfplan.binary > tfplan.json checkov -f tfplan.json ``` Points that matter in practice: - Checkov recognises a plan by its JSON content (a top-level `terraform_version` key); the binary plan file is not scanned. - `--repo-root-for-plan-enrichment <dir>` maps findings back to code blocks and resource IDs in the HCL and makes skip comments in the `.tf` files apply to the plan scan; `--deep-analysis` additionally combines the plan graph with the HCL graph to recover connections the plan lacks, such as those through locals. - A few checks that read the `lifecycle` block, which the plan does not store, are ignored for plans: `CKV_AWS_217`, `CKV_AWS_233`, `CKV_AWS_237` and `CKV_GCP_82`. - Values that are "known after apply" are not in the plan either; an experimental `EVAL_TF_PLAN_AFTER_UNKNOWN` setting reads the plan's `after_unknown` section. The plan route needs state access and provider credentials to produce, and the plan can contain secrets, which is why Checkov's documentation advises running it only in a secured pipeline. Choosing between the two routes as a testing strategy is a broader Terraform workflow decision; the Checkov-specific point is that **source scans only see the values you hand them**. ## What to say in the interview A strong answer names both failure modes — the wrong environment's value passing, and an unresolved value producing no result — then gives the two fixes with their costs: `--var-file` per environment keeps the scan cheap and credential-free, while the `terraform_plan` framework sees exactly what Terraform will do but needs a plan, its credentials and care with the secrets inside it.

  • How do you confirm which value Checkov used for a variable in a given result?
    The CLI output prints a line per evaluated variable — its file, the value and the expression — under each result, and `-o json` includes an `evaluations` object per check result with `var_file`, `value` and the `definitions` that referenced it. If the file shown is `variables.tf`, the default was used, not your environment file.
  • After switching to plan scanning, the team's inline skip comments in .tf files stop working. Why?
    A plain plan scan sees only the JSON, which carries no comments. Passing `--repo-root-for-plan-enrichment` with the directory of the HCL that produced the plan lets Checkov map plan resources back to source blocks, so code snippets and skip comments from the `.tf` files apply again.

saying these in an interview costs you the question

  • Checkov reads every .tfvars file in the directory automatically.
  • An unresolved variable makes the check fail, so nothing can slip through.
  • Checkov runs terraform plan internally to resolve variables.
  • You can scan the binary plan file Terraform writes with -out.
  • --var-file works the same with -f as with -d.