How to Scan Git-Sourced Terraform Modules That Checkov, Trivy, and KICS Miss by Default

Proactive Security for the AI Era
NodeZero continuously and autonomously pentests infrastructure, identity, cloud, and now web applications, chaining weaknesses across every domain the way real attackers do. Every finding ships with replayable proof showing exploitable business impact, not theoretical risk.
The direct answer: by default, Checkov does not download or scan the content of a git-sourced Terraform module unless you pass --download-external-modules true or enable an experimental environment variable; Trivy generally does scan already-resolved module files it finds on disk, with a flag to turn that off; and KICS structurally cannot scan most git-sourced modules at all because its module support is limited to a curated allow-list of official registry modules. The fix is to run terraform init first so modules are actually resolved to disk, then invoke each scanner with the specific flags or workflow it needs to see that resolved code, and finally prove the coverage works by deliberately planting a known-bad resource inside a test module and confirming the scanner catches it.
The mechanism behind the gap is simple and easy to miss. A module block whose source points at a git repository does not contain any resource definitions itself, it is just a pointer. Terraform only fetches the pointed-to code during terraform init, writing it into a local .terraform/modules/ cache. A scanner invoked against the root configuration before that resolution step, or invoked in a way that does not account for how it happened, never sees the aws_iam_policy, aws_s3_bucket, or aws_security_group resources that the module actually creates. Because every major scanner handles this differently, and none of them fail loudly when they miss module content (they just report fewer findings than the codebase actually has), teams routinely ship a green pipeline against infrastructure that a scanner never really looked at. This is a natural extension of the broader discipline covered in our guide to infrastructure as code security scanning; this article goes one level deeper into the specific git-module blind spot and how to close it.
Why terraform init Creates a Scanning Blind Spot
A Terraform module block can source its code from a remote git repository instead of a local path or the public registry:
module "vpc" {
source = "git::https://github.com/example-org/terraform-vpc-module.git?ref=v4.2.0"
cidr_block = "10.0.0.0/16"
}
or the shorthand GitHub form Terraform also accepts:
module "vpc" {
source = "github.com/example-org/terraform-vpc-module"
}
Neither of these blocks contains a single resource definition. The actual aws_vpc, aws_subnet, aws_security_group, and IAM resources that get created live inside the cloned repository, not in the root configuration. Terraform does not fetch that code until terraform init runs, which clones the repository (checking out the ref in the ?ref= query string, or the default branch if none is given) into a local cache at .terraform/modules/<name>/, and records the resolved source and version in .terraform/modules/modules.json.
That resolution step is exactly where scanning gets complicated, because most IaC scanners were originally built to walk a directory of .tf files, and a scan run before terraform init never sees the module's real content at all. Here is what each scanner actually does by default, verified against its own documentation and issue tracker as of mid-2026 rather than assumed from general reputation:
Checkov does not download or scan git-sourced module content by default. Pointing checkov -d . at a root configuration checks the module block itself (the source string, version pinning) but not the resources the module defines, unless you pass --download-external-modules true, which clones the module into a .external_modules directory and scans it, or set the experimental CHECKOV_EXPERIMENTAL_TERRAFORM_MANAGED_MODULES=True environment variable, which tells Checkov to reuse whatever terraform init already downloaded into .terraform/modules. That experimental mode only works when Checkov's scan target is the same root directory where init ran; point it at a different working copy and it will not find the resolved modules. Checkov also has a documented limitation (tracked in its issue tracker) downloading modules pinned to a git ref that is a raw commit SHA rather than a branch or tag, because its git client first tries to resolve the ref as a branch and fails.
Trivy, the tool that absorbed tfsec's entire check library after Aqua Security moved tfsec into maintenance-only status, behaves differently. trivy config recursively walks whatever directory you point it at and scans every .tf file it finds, including files already sitting in .terraform/modules from an earlier terraform init. In practice, running trivy config . after init does pick up resolved module resources by default, and a --tf-exclude-downloaded-modules flag exists if you want to turn that scanning back off. That flag's exact behavior has had its own documentation corrections and inconsistent-results reports on Trivy's issue tracker across recent versions, so confirm the behavior on your installed version rather than assuming the first thing you read about it still holds.
KICS is the most restrictive of the three by design, not by omission. Its own platform documentation states that it supports a curated list of official AWS modules from the public registry and explicitly says it "does not support unofficial or custom modules." A git-sourced module from your own organization's repository, or from any community author not on that allow-list, is structurally outside what KICS resolves, regardless of flags or a prior terraform init. KICS does, however, support scanning a Terraform plan given as JSON, which is the practical workaround covered in the next section.
Terrascan, for context, was archived by its maintainer, Tenable, on November 20, 2025; the repository is now read-only with no further updates, so treat it as legacy advice for teams running an existing pinned install, not a tool to newly adopt. Even while it was maintained, a direct local scan of a root configuration did not expand git-sourced module blocks into their underlying resources: community bug reports comparing a direct scan against a scan of a generated Terraform plan showed zero findings from the direct scan and real findings once the plan, which Terraform itself had already expanded, was scanned instead.
Prerequisites
Before working through the procedure below, confirm the following are in place.
Subscribe to unlock Remediation & Mitigation steps
Free subscribers unlock full IOC lists, Sigma detection rules, remediation steps, and every daily briefing.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Step-by-Step: Scanning the Fully Resolved Configuration
- Resolve the modules first. Run
terraform init(orterraform init -backend=falseif you do not want to touch remote state during a scan-only pipeline stage) from the root of the configuration that references the git-sourced module. Confirm the module actually landed on disk:
terraform init -backend=false
ls -R .terraform/modules
cat .terraform/modules/modules.json
- Scan with Checkov using explicit external-module resolution. Do not rely on scanning the root directory alone. Either download modules directly:
checkov -d . --download-external-modules true --external-modules-download-path .external_modules
or reuse what terraform init already resolved, run this from the exact directory where init ran:
CHECKOV_EXPERIMENTAL_TERRAFORM_MANAGED_MODULES=True checkov -d .
For private repositories, set GITHUB_PAT, BITBUCKET_TOKEN, or the self-hosted VCS variables (VCS_BASE_URL, VCS_USERNAME, VCS_TOKEN) so Checkov's clone step can authenticate.
- Scan with Trivy after init, without excluding downloaded modules. Since Trivy scans downloaded modules by default, the main thing to verify is that you have not accidentally set the flag that turns this off:
terraform init -backend=false
trivy config .
If you specifically want to confirm module resources are included, point Trivy directly at the resolved directory as a sanity check: trivy config .terraform/modules. Do not pass --tf-exclude-downloaded-modules unless you have a deliberate reason to skip third-party code, which is discussed in the tradeoffs section below.
- Work around KICS's module allow-list with a plan-based scan. Because KICS only recognizes a curated set of official registry modules, the reliable way to get it to see your git-sourced module's resources is to let Terraform itself expand everything first, then scan the resulting plan:
terraform init -backend=false
terraform plan -out=tfplan.bin
terraform show -json tfplan.bin > tfplan.json
kics scan -p tfplan.json
Because the plan JSON contains fully expanded resource data (including values computed from variables and data sources), this also catches misconfigurations that a purely static .tf file scan would miss, at the cost of needing valid provider credentials to generate a real plan.
- If you still run an existing Terrascan install, use the same plan-based approach. Terrascan is archived and unmaintained as of November 2025, so this step is for teams with an existing pinned deployment, not new adoption:
terrascan scan -i tfplan -f tfplan.json
- Wire whichever combination you use into CI as a distinct stage, after init. The scan stage must run after
terraform init(or after plan generation, for KICS and Terrascan) and beforeterraform apply. Running the scan as an isolated pre-init lint step, which is a common leftover pattern from before teams added git-sourced modules, silently reverts you back to the blind spot this guide is fixing. This is also a natural place to reuse whatever authentication your CI/CD pipeline already has for cloud and VCS access, since the module download step needs its own credentials independent of how later apply-stage cloud authentication is handled.
Validation: Prove the Scanner Actually Sees Inside the Module
Do not trust that a flag "worked" just because the command exited without an error. Confirm it by deliberately planting a known-bad resource inside a throwaway module and checking that the scanner's output references a file path inside the resolved module directory, not just the root configuration.
Create a small test module repository (or a local git repo you push to a scratch location) with an obviously non-compliant resource:
# test-module/main.tf
resource "aws_s3_bucket" "bad" {
bucket = "scanner-validation-test-bucket"
acl = "public-read"
}
Reference it from a scratch root configuration using the same git-source pattern your real project uses:
module "scanner_test" {
source = "git::https://github.com/your-org/test-module.git?ref=main"
}
Run terraform init, then each scanner command from the previous section. A working configuration should report a finding equivalent to Checkov's CKV_AWS_20 (S3 bucket ACL allows public read access) or Trivy's corresponding AVD-AWS-0086-family public-ACL check, and critically, the finding's file path should point somewhere under .external_modules/, .terraform/modules/, or the plan JSON's module address, not just list findings from your root .tf files. If the scan comes back clean, the module resolution step did not actually feed the scanner, and you should re-check the flags and working directory from the procedure above before assuming your real infrastructure is clean.
What Still Gets Missed After This Fix
Getting the scanner to see resolved module content closes the most common gap, but it does not close every gap. The following cases still require separate handling.
Subscribe to unlock Remediation & Mitigation steps
Free subscribers unlock full IOC lists, Sigma detection rules, remediation steps, and every daily briefing.
Troubleshooting Checklist
Work through this list in order when a scan against a repo you know contains git-sourced modules comes back with suspiciously few findings.
Subscribe to unlock Remediation & Mitigation steps
Free subscribers unlock full IOC lists, Sigma detection rules, remediation steps, and every daily briefing.
The Real Tradeoff: Failing Builds on Code You Do Not Own
Closing this blind spot is not free, and the cost is worth naming honestly rather than treating scanning resolved modules as a strictly-better default for every pipeline.
Downloading and scanning module source adds real CI time: cloning one or more additional git repositories, and for the KICS and Terrascan plan-based workflow, generating a full terraform plan with valid provider credentials, both slow down every pipeline run, not just the ones where it matters. On a monorepo with dozens of git-sourced modules, that overhead compounds.
The more consequential tradeoff is what to do with findings inside third-party code you do not control. A hard-fail gate on any medium-or-higher finding inside a community module you depend on will regularly block deployments over misconfigurations you cannot fix directly (you would need to fork the module, patch it, and repoint your source, or wait on an upstream maintainer). That is a legitimate response for a finding that represents genuine unacceptable risk (an S3 bucket a widely used module creates with public read access, for example), but treating every module finding with the same severity as findings in your own first-party code produces alert fatigue fast and trains teams to reflexively suppress rather than triage.
A more sustainable pattern many teams land on is to gate on finding path as well as severity: fail the build on critical and high findings regardless of whether they originate in first-party code or a module, but route medium and low findings from module paths to an alerting or tracked-backlog workflow rather than a hard build failure, while still failing on the same severities in first-party resources. This preserves the core benefit (you actually see what a module is doing) without making every third-party dependency update or module version bump a build-breaking event on findings you have limited ability to act on immediately. The related discipline of vetting and pinning third-party code before it enters your pipeline is covered in more depth in our guide to software supply chain attack defense.
The bottom line
Checkov, Trivy, and KICS each treat git-sourced Terraform modules differently by default, and none of them make that behavior obvious from a green pipeline alone. Run terraform init before scanning, use --download-external-modules true or the experimental managed-modules environment variable for Checkov, trust Trivy's default recursive scan of downloaded modules while confirming no exclusion flag is set, and fall back to scanning a generated terraform plan in JSON for KICS's module allow-list limitation (and for any existing Terrascan install, which is archived and unmaintained as of November 2025). Validate the fix by planting a deliberately non-compliant resource inside a test module and confirming the scanner's findings actually reference a path inside the resolved module, not just your root configuration. Even after that fix, private modules your CI cannot reach, modules pinned to a mutable branch or tag instead of a commit SHA, and KICS's structural allow-list limitation all remain real gaps that scanning alone does not close, and gating build failures on findings inside code you do not control is a genuine operational tradeoff worth deciding on deliberately rather than defaulting into.
Frequently asked questions
Does Checkov scan Terraform modules sourced from a git repository by default?
No. Checkov checks the module block's source and version pinning by default but does not download or scan the module's actual resource content unless you pass --download-external-modules true or set the experimental CHECKOV_EXPERIMENTAL_TERRAFORM_MANAGED_MODULES environment variable.
Does Trivy (the tool that absorbed tfsec) scan git-sourced Terraform modules automatically?
Generally yes. Trivy's config scanner recursively walks the target directory and scans .tf files it finds, including modules already resolved into .terraform/modules by a prior terraform init, unless the --tf-exclude-downloaded-modules flag is set to turn that off.
Why doesn't KICS scan my custom Terraform module pulled from GitHub?
KICS's own documentation states it supports only a curated allow-list of official AWS modules from the public Terraform Registry and explicitly does not support unofficial or custom modules, so an arbitrary git-sourced module falls outside its static scanning coverage regardless of flags.
Is Terrascan still a good choice for scanning git-sourced Terraform modules in 2026?
Not for new adoption. Tenable archived the Terrascan repository on November 20, 2025, and it now receives no further updates, so teams should treat it as legacy tooling for an existing pinned install and plan a migration to Checkov, Trivy, or KICS.
How do I confirm my scanner is actually seeing inside a git-sourced module, not just the root config?
Plant a deliberately non-compliant resource, such as an aws_s3_bucket with acl set to public-read, inside a throwaway test module referenced the same way your real modules are, run the scan, and confirm the reported finding's file path points inside the resolved module directory or plan JSON, not only your root .tf files.
What is the biggest remaining risk even after fixing scanner coverage for git-sourced modules?
A module source pinned to a mutable ref, a branch or even a tag rather than an immutable commit SHA, since the code a scan checks today is not guaranteed to be the code that resolves on the next terraform init, which is a supply chain drift risk that scanning coverage alone does not close.
Sources & references
- Checkov: Terraform Scanning documentation
- bridgecrewio/checkov Issue #3417: unable to download modules when source is a git ref with commit SHA
- bridgecrewio/checkov Issue #6328: scanning external modules in Terraform
- bridgecrewio/checkov Issue #6335: CKV_TF_2 false positive on pinned registry modules
- Trivy: Terraform misconfiguration scanning documentation
- aquasecurity/trivy Issue #5416: fix the description of --tf-exclude-downloaded-modules
- Checkmarx KICS: Platforms documentation (module support)
- tenable/terrascan (archived Nov 20, 2025)
- HashiCorp Terraform: Module Sources documentation
- Gartner cloud security failures projection, via CIO.com
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
