CoursesCheckov & IaC scanningWhat IaC scanning finds

What IaC scanning finds

Misconfigurations, before deploy.

Intermediate12 min · lesson 1 of 12

You can proofread a recipe all afternoon. Every ingredient spelled right, the oven temperature a real number, nobody asking for a cup of concrete. None of that tells you whether the cake rises. Infrastructure code has the same gap. A linter (a tool that checks syntax and style) confirms your Terraform parses. A runtime scanner catches a public bucket after it already exists. IaC static analysis (reading infrastructure-as-code files without running them) sits in the middle. It reads your Terraform, Kubernetes manifests, CloudFormation templates and Dockerfiles, then flags insecure configuration before any of it reaches a cloud account. An unencrypted volume. A security group open to 0.0.0.0/0, which means the entire internet. An IAM (identity and access management) policy with Action:*, which grants every permission there is. A container running as root. Those are the mistakes that ship quietly when nobody reads the diff closely enough.

Checkov is one of those readers. It is a Python package from Prisma/Bridgecrew, it ships with hundreds of built-in policies, and you run it on your laptop or in CI (continuous integration, the automation that fires on every push). The shape of the work is identical across Checkov, tfsec, Trivy, Terrascan and KICS: read config, match rules, report violations. Learn that loop once and every scanner in the family clicks into place.

Where the scan sits in your pipeline

Shift-left is the habit of moving a check earlier, closer to the person who can fix it in a minute. For infrastructure it looks like this. You write code, you open a pull request, and a scanner grades every resource against a catalog of checks before anyone can merge. A source scan needs no cloud credentials at all. Checkov parses the files sitting on disk and compares each attribute to a rule. Cheap enough to run on every commit, fast enough that a developer gets an answer in minutes. The gate stops being "deploy and hope" and becomes "fix the misconfiguration while the change is still a diff."

terminal
$ checkov -d ./infra --framework terraform --compact --quiet
output
terraform scan results:
Passed checks: 38, Failed checks: 4, Skipped checks: 0
Check: CKV_AWS_20: "S3 Bucket has public access block"
FAILED for resource: aws_s3_bucket.uploads
File: /infra/s3.tf:3-11
Guide: https://docs.prismacloud.io/...
Check: CKV_AWS_79: "Ensure Instance Metadata Service V2 is required"
FAILED for resource: aws_instance.app
File: /infra/ec2.tf:14-28
From IaC write to gate
1Write IaC
Terraform, k8s YAML, Dockerfile…
2Static scan
checkov -d . matches rules
3Findings
CKV_* IDs + remediation links
4Fix or gate
block merge on new failures
No cloud account is touched. The scanner reads files (or plan JSON) and reports misconfigurations before deploy.

What a scanner is genuinely good at

Checkov is strongest on the boring, well-known failure classes, because each one maps to a field you can read straight off the page: encryption switched off, something exposed publicly, an over-broad IAM policy, missing logging, weak TLS (transport layer security, the encryption behind https) settings, containers running as root, GitHub Actions pinned to a moving tag instead of a fixed version. Most checks boil down to one question. Does this attribute equal the safe value? Graph checks, the ones with a CKV2_ prefix, ask a bigger one. Does the relationship between these resources create exposure? The built-in catalog covers AWS, Azure, GCP, Kubernetes, Docker, CloudFormation and more, thousands of rules kept current by the vendor and the community.

terminal
$ checkov -d . --check CKV_AWS_18,CKV_AWS_21,CKV_K8S_22 --compact
output
Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"
FAILED for resource: aws_s3_bucket.logs
Check: CKV_AWS_21: "Ensure all data stored in the S3 bucket have versioning enabled"
FAILED for resource: aws_s3_bucket.logs
Check: CKV_K8S_22: "Use read-only root filesystem"
FAILED for resource: Deployment.default.payments-api

What a scanner cannot see

A green scan proves less than it feels like it proves. A scanner cannot watch runtime behaviour, cannot resolve business logic that only settles after deploy, and cannot see risk that never appears in config at all. Your bucket can pass every encryption check while the application quietly writes secrets into a plain text file. Rules of your own making, like "only these instance types in prod", need a custom check or OPA/Conftest (Open Policy Agent and its file-checking companion) running alongside Checkov. Static scanning is one layer. Policy-as-code, secrets detection, image CVE (common vulnerabilities and exposures) scanning and runtime detection cover the rest. Checkov is a gate you need, never the whole story.

Check IDs are the shared vocabulary

Every finding arrives with a stable identifier: CKV_AWS_18, CKV2_AWS_6, and so on. Treat that string the way a mechanic treats a part number. You will type these IDs into suppressions, baselines, CI config and Slack threads, and they mean exactly the same thing in every one of those places. A CKV_ check evaluates a single resource. A CKV2_ check is a graph check that spans relationships between resources. The middle of the ID tells you the framework: CKV_AWS_*, CKV_K8S_*, CKV_DOCKER_*, CKV_GHA_*. When you triage, read the ID before the sentence beside it. The sentence is a friendly summary. The ID is the handle every other tool understands.

terminal
$ checkov -d . --framework terraform -o json | jq -r '.results.failed_checks[] | "\(.check_id) \(.resource)"' | head
output
CKV_AWS_18 aws_s3_bucket.logs
CKV_AWS_79 aws_instance.app
CKV2_AWS_6 aws_s3_bucket.uploads
Green is not the same as safe
Passing Checkov means the rules it ran found no known misconfiguration. It does not mean the system is secure. And a summary reading zero passed, zero failed almost always means nothing got parsed, not that everything is perfect. Confirm real resources were evaluated before you celebrate a green summary.

Source scan vs plan scan (a preview)

Reading source is like reading a blueprint. Reading a plan is like watching the builder lay out the actual materials on site. A source scan reads the .tf and YAML files on disk: fast, no cloud credentials, ideal in the editor and on every commit. It has one blind spot. Values that only settle once terraform plan runs, such as variables, module outputs and dynamic blocks, may never appear as the insecure value the plan would really create. Plan scanning, covered in cv-plan, closes that gap by reading terraform show -json output. Teams who have been at this a while run both. Source for speed, plan for authority before apply.

terminal
$ checkov -d . --framework terraform --compact
$ checkov -f tfplan.json --framework terraform_plan --compact
output
terraform scan results:
Passed checks: 812, Failed checks: 37
terraform_plan scan results:
Passed checks: 156, Failed checks: 2
# plan may surface failures invisible in source — both layers matter

Where this course goes

The topics ahead follow the order you meet these problems in real life: first install, then reading a failure, then repos with four frameworks jammed into them, then suppressions, custom rules, plan scanning, baselines, how the rival scanners compare, where tools overlap, CI gates, and finally running the whole thing as a programme with owners and deadlines. Every topic stands on its own, so start wherever your current problem is.

The layers either side of the scanner

A seatbelt, an airbag and good brakes do not replace each other, and nobody argues that they should. Pair Checkov with secrets detection (Gitleaks at commit time, Checkov's secrets framework as a backstop), image CVE scanning (Trivy or Grype), and runtime controls (Falco, cloud guardrails). Checkov catches misconfiguration in code. It will not rotate a leaked key or patch an operating system package inside a container image. Give each domain one owner so your gates do not end up arguing with each other.

terminal
$ checkov -d . --framework terraform --compact --quiet
$ gitleaks detect --source . --redact
$ trivy config . --severity HIGH,CRITICAL
output
checkov Failed checks: 4
gitleaks: no leaks found
Trivy HIGH/CRITICAL misconfigurations: 3
# three layers, three owners — not three copies of the same gate

Graph checks (CKV2_) catch the risk that lives in the wiring. A security group rule can look perfectly sensible on its own and turn dangerous the moment it is attached to an instance with a public IP address. Plan scanning and baselines are the other two things Checkov does its own way, and scanners built purely on Rego (the policy language behind OPA) tend to handle both quite differently.

These findings are boring right up until they are catastrophic. A public S3 bucket. A security group open to the world. An IAM policy with admin rights. A container running as root. Boring mistakes cause most incidents, and a scanner performs the boring review tirelessly, which frees human attention for architecture and threat modelling instead of checking whether logging is enabled on bucket number forty.

How this compares to runtime tools and policy-as-code

Runtime tools answer a different question. CloudTrail alerts, GuardDuty and Falco tell you what is happening in the account right now. Checkov tells you what you will build if this merge lands. OPA with Conftest evaluates Rego against structured input, which is powerful for policy specific to your company, though you still have to decide what input to feed it: manifests, plans, something else. Checkov turns up with hundreds of ready-made checks and a command your developers can run in thirty seconds. Most teams take Checkov (or a sibling scanner) for breadth and add OPA where Rego already powers their admission control.

Secrets scanners find credentials committed to git. Image scanners find known vulnerabilities buried in image layers. Checkov finds buckets without encryption and pods without resource limits. None of the three substitutes for the others. Reading infrastructure code before it deploys is the job Checkov was built for.

terminal
$ checkov -d . --framework terraform,kubernetes,dockerfile --compact
$ checkov -d . --framework secrets --quiet
output
terraform: Passed 812, Failed 37
kubernetes: Passed 204, Failed 11
dockerfile: Passed 12, Failed 2
secrets: Failed 1
Check: CKV_SECRET_6 on /app/config.yml

One repository, several frameworks, one scanner across all of them. That mix is what most real codebases look like, which is why the topics ahead handle frameworks before they get clever about custom rules and gates. Start with install (cv-install) and move at whatever pace the repo you are stuck with demands.

Try this

Do this: make a throwaway folder holding a single Terraform file, or clone a small public sample, install Checkov if you have not already, and run the commands below. You are proving the loop works. Point at files, get check IDs back, confirm the resources were actually parsed. You are not proving your cloud is safe.

terminal
$ mkdir -p /tmp/cv-what && cd /tmp/cv-what
$ printf 'resource "aws_s3_bucket" "demo" { bucket = "demo-example" }\n' > main.tf
$ checkov -d . --framework terraform --compact --quiet
$ checkov -d . -o json | jq 'if type=="array" then .[].summary else .summary end'
output
Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"
FAILED for resource: aws_s3_bucket.demo
{
"passed": 4,
"failed": 6,
"skipped": 0,
"parsing_errors": 0,
"resource_count": 1
}
# resource_count: 1 means Checkov saw the bucket — not a hollow green scan

Takeaway

Remember: the trade-off in IaC static scanning is speed against certainty. Source scans cost nothing and need no credentials, so you can run them on every single commit, but they cannot see values that only appear after terraform plan and they cannot prove anything about runtime behaviour. Checkov is the smoke detector you fit while reading the blueprint, not the fire brigade for the building you already live in. Give secrets detection, image CVE scanning and cloud runtime controls their own layer so each one owns exactly one question.

Next up: install Checkov on your own laptop (cv-install) and read real failure lines until the check ID, the resource address and the Guide link stop looking like noise. Once that vocabulary sticks, triage (cv-results) and CI gates read like a worklist instead of alien output.

Quick check
01What can IaC static scanning tell you that a runtime cloud scanner cannot?
Incorrect — No. Reachability is a live network question, and files sitting on disk cannot answer it.
Correct — Yes. The scanner reads the code (or the plan) before deploy, which is the entire point of moving the check left.
Incorrect — No. Dependency and CVE scanning is a separate domain, picked up later in cv-overlap.
Incorrect — No. That is drift detection, which compares state against live cloud APIs. A source scan never calls the cloud.
02A scan finishes and reports Passed: 0, Failed: 0. What is the most likely explanation?
Incorrect — No. A real scan over parsed resources always reports a non-zero passed count.
Correct — All-zero summaries usually mean the wrong path, an empty folder, or files Checkov could not read.
Incorrect — No. Suppressed checks land in the skipped count, so the summary would not read zero everywhere.
Incorrect — No. --quiet hides the PASSED lines, but the summary still prints the passed and failed totals.
03Your source scan of the repo comes back clean, but a colleague points out that the encryption setting on one resource comes from a variable that only resolves at plan time. What do you do next?
Incorrect — No. Source scanning cannot see values that settle only when terraform plan runs, so a pass here proves nothing about that setting.
Correct — Plan scanning reads terraform show -json output and surfaces failures that stay invisible in source.
Incorrect — No. A suppression hides a finding. It tells you nothing about the value the plan would actually create.
Incorrect — No. A source scan never calls the cloud at all. It reads files on disk, credentials or not.

Related