A Trivy 0.74 config scan reports nothing on a Terraform security group whose CIDR comes from prod.tfvars — why, and what does --tf-vars change?
answer
- static evaluation, no plan
- where variable values come from
- unknown is not a finding
- the warning in the log
- one tfvars file per environment
basics
~20 sTrivy evaluates HCL without running Terraform and reads root variable values only from defaults, TF_VAR_ variables and files passed with --tf-vars. Unset variables become unknown, so a check cannot see 0.0.0.0/0. Passing --tf-vars prod.tfvars supplies the real value.
solid answer
~40 sTrivy evaluates Terraform statically: it resolves variables, locals, functions and module inputs, but it never runs `terraform plan` or calls a provider. In 0.74 a root-module variable's value comes from its `default`, from `TF_VAR_` environment variables, or from files you name with `--tf-vars`; Trivy does not pick up `terraform.tfvars` or `*.auto.tfvars` on its own. A variable with no default and no supplied value becomes *unknown*, Trivy logs a warning that variable values were not found, and the security-group check sees an unknown CIDR rather than `0.0.0.0/0`, so it stays quiet. `trivy config --tf-vars prod.tfvars ./infra` gives it the value production uses, and the open ingress appears. Run one scan per environment, since each tfvars file can produce different findings. Values resolved from `data` sources or computed attributes stay unknown even then.
code
hcl · 12 linesvariable "ingress_cidr" {
type = string
}
resource "aws_security_group_rule" "https_in" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [var.ingress_cidr]
security_group_id = aws_security_group.web.id
}go deeper
Remember that Trivy reads Terraform files without running Terraform, so it only knows variable values you give it.
Explain the three value sources, the unknown fallback and its log warning, and why an unknown CIDR produces no finding rather than a failure.
Show you would scan per environment with the deployed tfvars, read the warning as a coverage gap, and know which values stay unknown regardless.
Weigh HCL scans with tfvars against scanning plans in the pipeline: speed and early feedback versus resolved values, and who owns keeping inputs aligned.
## How Trivy reads Terraform Trivy's Terraform scanner is a **static evaluator**. It parses the HCL, builds the module tree, and evaluates expressions (variables, locals, functions such as `cidrsubnet()`, `for_each`, module inputs) into concrete attribute values. Checks from the trivy-checks bundle then inspect those values: is `cidr_blocks` `0.0.0.0/0`, is encryption enabled, is logging configured. What it does **not** do is run Terraform. There is no `terraform plan`, no provider call, no read of remote state. Anything Terraform would only learn at plan or apply time is invisible to it. ## Where variable values come from In Trivy 0.74 the Terraform parser fills the root module's input variables from three places (a child module gets its inputs from the calling `module` block): | Source | How Trivy gets it | |---|---| | `default` in the `variable` block | read from the HCL itself | | `TF_VAR_<name>` environment variables | read from the scanning process's environment | | tfvars files | only the files named with `--tf-vars` (repeatable; `.tfvars` or `.tfvars.json`) | Two details matter in practice: - Trivy does **not** auto-load `terraform.tfvars` or `*.auto.tfvars` the way `terraform plan` does. If the value lives there, you must name the file. - Files are applied in the order given, after the environment variables, so with `--tf-vars common.tfvars --tf-vars prod.tfvars` a key set in both takes the `prod.tfvars` value. ## What happens to a variable nobody set If a variable has no `default` and no value from the environment or a named file, Trivy marks it **unknown** (typed if the block declares a `type`) and logs a warning: *"Variable values were not found in the environment or variable files. Evaluating may not work correctly."* The scan continues. An unknown value is not a violation. A check that asks "is this CIDR `0.0.0.0/0`?" cannot answer yes for an unknown, so the usual outcome is silence: a **false negative**. Trivy's own documentation warns that unknowns, and fallbacks such as `try()` defaults, can produce false positives as well as false negatives, depending on how a check is written. ## The scenario, step by step 1. `variables.tf` declares `variable "ingress_cidr" { type = string }` with no default. 2. `prod.tfvars` sets `ingress_cidr = "0.0.0.0/0"`. 3. CI runs `trivy config ./infra`. The variable is unknown, the warning is logged, the security-group check passes. 4. Production is applied with `prod.tfvars` and the port is open to the internet. The fix is to scan what you deploy: `trivy config --tf-vars prod.tfvars ./infra`. Run one scan per environment's tfvars file, because staging and production can legitimately differ. ## Related inputs and limits - **Downloaded modules** under `.terraform` are scanned by default; `--tf-exclude-downloaded-modules` drops their findings when a third-party module's noise drowns yours. - **CloudFormation** has the equivalent `--cf-params` for parameter files; **Helm** has `--helm-values` and `--helm-set`. - **Data sources and computed attributes** (IDs, IP addresses, DNS names known only after apply) stay unknown whatever you pass. Feeding the scanner a plan instead of HCL closes that gap at a cost, and is a separate Terraform workflow decision. - **File functions** such as `file()` can only read inside the scan root; scanning a subdirectory that references `../shared/` leaves those values unresolved. ## What to watch in CI - Treat the "Variable values were not found" warning as a coverage signal, not log noise; it names the variables Trivy could not resolve. - Keep the tfvars file the pipeline applies and the one it scans the same file, ideally the same path variable in the job. - Never put real secrets in the tfvars you hand Trivy just to make the scan resolve; the checks only need the security-relevant values. ## Confirming a value actually resolved A silent pass and a genuine pass look the same in a default report, so make the difference visible: - `--include-non-failures` lists the checks that passed, so you can see the security-group check was evaluated against that resource at all. - For checks that do fire, `--render-cause terraform` prints the rendered cause in the table, showing the value Trivy computed rather than the raw `var.ingress_cidr` expression. - Plant the bad value deliberately once (a throwaway tfvars with `0.0.0.0/0`) and confirm the scan fails; that proves the variable path works before you rely on it. If the warning names variables you consider irrelevant, give them harmless defaults in a scan-only tfvars rather than ignoring the line, so the next genuinely missing value stands out.
- The same module is applied with dev.tfvars and prod.tfvars. One scan or two?Two. Each `--tf-vars` file can resolve variables to different values, so a check may pass for dev and fail for prod. Scanning once without either file evaluates neither environment; scanning with both merges them, and the later file's values win where keys overlap.
- Would setting TF_VAR_ingress_cidr in the CI job work instead of --tf-vars?Yes for that variable: Trivy reads `TF_VAR_` environment variables into its inputs. It does not scale well, though, and a `--tf-vars` file passed on the same run overrides the environment value for any key both define.
saying these in an interview costs you the question
- Trivy picks up terraform.tfvars automatically like terraform plan does
- An unresolved variable makes the Terraform scan fail outright
- Trivy runs terraform plan under the hood to get real values
- With --tf-vars set, data sources and computed IDs are resolved too
- A clean scan without tfvars means every environment is clean