Why IaC Scanners Miss the Default Credential Problem
Default credentials hiding in database images slip past IaC scanners entirely.
Summary
Default credentials hiding in database images slip past IaC scanners entirely.
Google Cloud's Cloud Threat Horizons research found that weak or missing credentials caused 47.1% of cloud initial-access incidents in the first half of 2025. Misconfiguration, the thing almost every scanning tool on the market is built to catch, accounted for 29.4%. That's a large gap. It's the difference between building a smoke detector and installing it next to the stove while the actual fire starts in the wiring behind the wall.
Why credentials, not misconfigurations, lead cloud breaches
Start with the numbers, because they settle the argument before it starts. Credentials beat misconfiguration as an initial-access vector by close to 18 points in Google's H1 2025 data. Yet walk into any cloud security conversation and the tooling on the table is almost entirely misconfiguration-focused: policy engines, IaC scanners, posture management dashboards that flag open security groups and public buckets. The industry built excellent detection for the second-place problem and called it done.
None of this makes misconfiguration a non-issue. It accounted for 23% of all cloud security incidents in 2025, and 82% of those traced back to human error, someone clicking the wrong box or skipping a review. Gartner's analysis puts the number even more bluntly: 99% of cloud security failures are on the customer, not the provider. Misconfiguration costs organizations real money and is worth fixing. It's just not the thing that's most often letting attackers in the front door. Credentials are. And the tooling gap between those two facts is the subject of everything that follows.
What IaC scanners are built to do
IaC scanners read infrastructure-as-code templates, Terraform, CloudFormation, Kubernetes manifests, Helm charts, and check every declared resource against a library of policies. They don't run the code. They read it the way a proofreader reads a manuscript, checking each sentence against a style guide, flagging violations by exact line number and file. That's the entire job description, and it's a genuinely useful one: public storage buckets, IAM policies with wildcard permissions, security groups wide open to 0.0.0.0/0, databases with encryption toggled off. All of it gets caught before it ever reaches production.
Checkov, maintained under Bridgecrew and now part of Palo Alto Networks, runs over 1,000 policies and adds 800-plus graph policies for cross-resource analysis, the only open-source scanner in this category that does graph-based reasoning across resources instead of checking each one in isolation. It covers Terraform, CloudFormation, Kubernetes, Helm, Dockerfiles, ARM, Bicep, the Serverless framework, Ansible, Kustomize, and OpenTofu, and it also flags hardcoded secrets sitting in IaC source files. KICS, from Checkmarx, runs more than 2,400 queries across upward of 22 frameworks. Trivy bundles IaC scanning with container and dependency scanning in one binary. tfsec sticks to Terraform only. TFLint is a Terraform-focused linter. Kubescape covers Kubernetes and Helm, with a live cluster scan option. Terrascan, notably, was archived by Tenable on November 20, 2025. The README now says the project accepts no further updates, issues, or pull requests, with Tenable's commercial energy redirected to Tenable Cloud Security.
That's a genuinely deep bench of tools. All of them are built to answer one question: does this template violate a known policy? None of them are built to answer a different, more consequential question: does this thing, once deployed, still have the password it shipped with?
The difference between a hardcoded credential and a default credential
These two get lumped together constantly, and they shouldn't be. A hardcoded credential is a secret a developer typed into a file, a string sitting in source code or a template, password = "Summer2024!" committed straight into a repo. It exists as text. A scanner can pattern-match against it, the same way spellcheck catches a misspelled word: it's visibly, searchably wrong.
A default credential is a different animal. It's the username and password that ships baked into a database engine, a container image, or a Helm chart, and it was never written into the IaC template because it didn't need to be. Nobody typed admin/admin into the Terraform file. The database image arrived with admin/admin already loaded, and nobody overrode it. There's no string in the template for a scanner to find, because the problem isn't a string, it's an absence. No policy exists that fires on "you forgot to set a password," because forgetting doesn't leave a fingerprint.
Wiz's State of Code Security Report found 86% of organizations have at least one private repository with exposed secrets, and 61% have secrets sitting in a public repo. That's a legitimately huge problem, and it's exactly the kind of thing hardcoded-secret scanners are built to catch: those tools are doing real work. They just aren't doing this work.
Why vendor-default credentials produce no scanner signal
Static analysis reads the shape and logic of a template. It never runs the code, so it never gets to ask the cloud provider what actually happened after terraform apply. A database provisioned with the vendor's out-of-the-box default password looks, from Terraform's point of view, entirely correct. The resource block is valid. The security group might be scoped exactly right. Encryption at rest might be switched on. Every rule in the policy library passes, because every rule in the policy library is checking something other than "what password does this ship with."
The scanner has no window into the image layer. The default password lives inside the database engine's build, not inside the .tf file sitting in the repo, so there's structurally nothing for the scanner to read. And even in the narrower case where a variable does appear in the template, with some placeholder-looking default value assigned to it, scanners still struggle to tell a genuine placeholder from a live production default. If the string looks like a placeholder, no policy fires. The tool is doing what it was designed to do here. It's doing what it was designed to do, which is the whole point: the design itself has a blind spot shaped precisely like this problem.
Configuration drift and the console bypass problem
IaC scanners only ever see the code that's declared. What they cannot see is anything deployed by hand through the console, spun up by a separate tool, or drifted away from the Terraform state after the fact. Someone makes a quick fix in the console, bypassing IaC. Now the infrastructure's actual state and the code that's supposed to describe it are out of sync, and the code is no longer the source of truth it was assumed to be.
Apply that directly to credentials: a service someone spins up through the console, or through a third-party runbook, with its default password left untouched, sits completely outside the scanner's field of view. There's no template. There's no scan target. There was never a file to check in the first place, so there's no finding. Static analysis evaluates settings as they're declared in code, and it has no mechanism for catching a risk that only exists in the specific, post-deployment configuration of a running resource. The gap is the boundary of the category. It's the boundary of the category.
The Terraform module blind spot and default variable values
Terraform modules pulled from a Git source, anything using source = "git::https://...", get fetched at terraform init time. By default, scanners don't necessarily reach into that fetched module and scan its internals unless someone goes out of their way to configure that. A module pulled from a Git URL or a private registry can quietly embed an insecure default variable, including a default username and password, and if the team calling that module never overrides the variable, the scanner never sees a problem, because all it inspects is the calling code, and the calling code just contains a valid, unremarkable module reference.
Scanners that do reach into external modules typically support only a limited set of known sources. Anything unofficial, custom-built, or simply not on that list may pass through unexamined. Picture a platform team publishing an internal "blessed" database module, built to make onboarding painless for other engineering teams, with a default password variable included purely so nobody gets stuck during setup. Every downstream team adopts it. Nobody overrides the default, because overriding it was never made to feel necessary. The scanner checks the calling code, finds a clean, valid module reference, and reports nothing, while the actual password sits untouched three layers down in a module nobody thought to reread.
Terraform state files as an unscanned credential store
State files are where Terraform keeps a running record of everything it manages, every resource attribute, and that includes secrets stored in plaintext: database passwords, API keys, TLS private keys, IAM access keys. If a resource defines a credential as a plain property, without explicit handling, it can end up written into the state file as readable text. State and plan files can contain sensitive values, including initial database passwords or API tokens, in the clear.
Standard IaC scanners read .tf source files. They do not read state files. Which means a default or initial password written into state at the moment a resource gets provisioned sits in a place no scanner in the standard pipeline ever looks. It's just filed under a heading the tooling never opens.
The scanner supply chain risk that emerged in 2026
In March 2026, a threat actor identified as TeamPCP compromised two widely used open-source IaC scanners, Trivy and KICS, by poisoning their GitHub Actions workflows and Docker images. Any pipeline pointing at those tools through a mutable version tag, rather than a fixed digest, pulled down attacker-controlled code without any warning and, in doing so, exposed its own credentials to the compromise. Aqua Security, which maintains Trivy, and Checkmarx, which maintains KICS, removed or replaced the malicious artifacts and published remediation guidance.
The incident shows the scanning layer most teams treat as their main line of defense is itself a piece of software with a supply chain, and supply chains get poisoned. Credential exposure, in this case, didn't come from a misconfigured resource or a forgotten default password. It came from the very tool installed to catch those things.
A complete IaC security pipeline that accounts for all of this
A full, accounted-for secure IaC pipeline runs across seven stages. Most teams, in practice, stop at the PR scanning stage and go no further.
Stage one is pre-commit: secrets detection through something like Gitleaks or git-secrets, syntax validation via TFLint, and a fast, critical-only misconfiguration pass with Checkov. This needs to run in well under 30 seconds, because developers will disable anything slower. Branch protection defines stage two: no direct pushes to protected branches, required reviewers enforced through GitHub or GitLab rules. Stage three is PR scanning, the full misconfiguration and policy pass using Checkov, Trivy, and KICS together, and this is where most pipelines stop cold. Stage four is the CI gate: full policy validation and compliance mapping, run through Checkov, Trivy, and Kubescape.
Reviewing the terraform plan output before anything actually applies is stage five, catching blast radius and live infrastructure changes through tools like Atlantis paired with OPA policy checks. Stage six is post-deployment drift detection, run with terraform plan -refresh-only, Prowler, or a CSPM tool, and this is precisely where configuration drift and console-bypass changes would surface, including default credentials introduced entirely outside IaC. Stage seven is application validation: DAST scanning after deployment, which catches application-layer vulnerabilities, default credentials among them, that no IaC scanner was ever positioned to reach.
Secrets management sits outside this pipeline entirely, and it should. Credentials shouldn't get written into templates or state files at all; tools built for exactly this, HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, manage credentials at runtime instead of at scan time, and that's where default passwords actually get rotated, not caught after the fact by a linter. After the TeamPCP incident, pinning scanners to a specific commit digest rather than a mutable tag stopped being a nice-to-have and became a concrete operational requirement. And underneath all seven stages is the piece no static scan will ever replicate: real-time monitoring of how deployed infrastructure actually behaves, catching anomalous API calls, unexpected credential use, and policy violations as they happen, live, at runtime. Pre-deployment scanning tells a team what the code says. Only runtime visibility tells them what's actually running, and by the time those two answers disagree, the scanner has already done its job and gone home.