CoursesCheckov & IaC scanningSecrets, SBOM & image overlap

Secrets, SBOM & image overlap

Where scanners meet.

Advanced12 min · lesson 10 of 12

A hardware store keeps plumbing in one aisle and electrics in another. Security scanning used to work the same way, one tool per aisle: IaC (infrastructure as code, the Terraform and Kubernetes files that describe your cloud) misconfigurations, leaked secrets, SBOM (software bill of materials, the ingredient list for what you shipped), and container CVEs (Common Vulnerabilities and Exposures, the public catalogue of known software flaws). Now every shopkeeper stocks the neighbouring aisle. Checkov scans secrets and emits an SBOM. Trivy scans IaC and images. The overlap is real, and without a map you either run the same check three times or leave one aisle completely empty.

Say the secrets scanner, the IaC scanner and the image CVE scanner all flag the same line of the same Dockerfile. That is three tickets, three suppressions written in three different syntaxes, and one very tired engineer. Deciding on purpose who covers what is how you keep the signal from turning into busywork.

Checkov's home turf is IaC misconfiguration. Its secrets and SBOM output are useful extras, never replacements for Gitleaks or Syft. Give each domain exactly one owner so the same risk never lands in your inbox three times.

Checkov's side jobs: secrets and SBOM

The secrets framework runs detect-secrets (regex patterns plus an entropy score for random-looking strings) across every file in the tree, not only the infrastructure code. Before it scans, Checkov builds a graph of your infrastructure code, wiring each resource to the variables and modules that feed it so a value can be followed across files. The CycloneDX and SPDX output, two standard SBOM file formats, lists what that graph parsed: the files and resources of your infrastructure, not the libraries inside your application. Name your frameworks on the command line so the scan stays fast and the output stays scoped to what you asked for.

terminal
$ checkov -d . --framework terraform,secrets --compact
$ checkov -d . -o cyclonedx_json > sbom.cdx.json
output
terraform scan results:
Passed checks: 812, Failed checks: 37
secrets scan results:
Passed checks: 1042, Failed checks: 1
Check: CKV_SECRET_6: "Base64 High Entropy String"
FAILED for resource: /app/config.yml
# sbom.cdx.json written: the files and resources Checkov parsed, not app deps

Where Checkov runs out of road: image CVEs

Checkov's SBOM mirrors the graph it parsed, not your application's dependency tree. The sca_image and sca_package frameworks (SCA is software composition analysis, matching the components you ship against a vulnerability database) phone home to Prisma Cloud and need BC_API_KEY set. Without that key they do nothing at all. The purpose-built lane is Syft plus Grype, or Trivy covering image, config and filesystem from a single binary.

terminal
$ syft nginx:1.27 -o cyclonedx-json=sbom.cdx.json
$ grype sbom:./sbom.cdx.json --fail-on high
$ checkov --framework sca_image --docker-image nginx:1.27 --dockerfile-path ./Dockerfile --bc-api-key "$BC_API_KEY" --repo-id org/repo
output
✔ Vulnerability DB downloaded
NAME INSTALLED FIXED-IN VULNERABILITY SEVERITY
libssl 1.1.1 1.1.1w CVE-2024-... High
checkov sca_image: Passed 0, Failed 0 # without BC_API_KEY: framework skipped
One owner per scan domain
1IaC misconfig
Checkov primary
2Secrets
Gitleaks hook + CI sweep
3SBOM
Syft → CycloneDX
4Image CVEs
Grype / Trivy image
Overlapping tools can cover every lane. Name one owner so each finding is reported once.

Wiring the lanes together

Let Checkov own IaC misconfiguration. Let Gitleaks in a pre-commit hook own secrets at commit time, with Checkov's secrets framework as the CI (continuous integration) backstop behind it. Let Syft and Grype, or Trivy, own SBOM and image CVEs. Then drop the parsers that are now dead weight: --skip-framework sca_image,sca_package. Leave secrets on, unless a second tool is already sweeping for secrets in CI, in which case add secrets to that same list and let the other tool have the lane.

terminal
$ checkov -d . --skip-framework sca_image,sca_package
$ gitleaks git --pre-commit --staged --redact -v
output
Passed checks: 812, Failed checks: 37
# gitleaks, staged changes only
INF scan completed in 12.4ms
INF no leaks found
A green SCA summary can mean nothing ran
-o cyclonedx_json lists only what Checkov parsed, which is not the application dependency list an auditor is asking for. The sca_* frameworks skip quietly when BC_API_KEY is missing, so green here is no clean bill of health. Read the summary counts. Zero SCA resources parsed means the framework never ran.

Secrets: block at the door, sweep afterwards

checkov --framework secrets runs detect-secrets over the whole tree and makes a decent CI backstop. Gitleaks in a pre-commit hook, or server-side push protection, stops a credential before it reaches git history, where the only honest fix left is rotating it. The hook has to read the staged diff, so the command is gitleaks git --pre-commit --staged. Builds before 8.19 spell that same scan gitleaks protect --staged. Plain gitleaks git, which used to be called gitleaks detect, walks commits that already exist, so it sweeps rather than blocks. Run both layers and say out loud which one does which job: prevent at commit, detect in CI. Neither one covers for the other.

terminal
$ gitleaks git --pre-commit --staged --redact
$ checkov -d . --framework secrets --compact --quiet
output
INF scan completed in 12.4ms
INF no leaks found
secrets scan results:
Passed checks: 1042, Failed checks: 0

The SBOM lane: Syft and Grype

The SBOM an auditor actually wants comes from Syft run against your build artefact or image, with Grype or Trivy matching CVEs against it, archived once per release. Checkov's CycloneDX is evidence of what your infrastructure code referenced, which is supplementary rather than a substitute.

Keeping --skip-framework honest

Once Trivy owns images, drop sca_image and sca_package from Checkov so one finding does not turn into three PR (pull request) comments. Add secrets to that list on the day another tool starts sweeping for secrets in CI, and not before. Write the ownership map into the repo README or a comment in .checkov.yaml, so the next engineer does not switch sca_* back on chasing "more coverage."

trivy fs --scanners secret,vuln,misconfig sweeps three lanes in one pass, which is genuinely appealing when your platform team is four people. Bigger estates still want a named owner per lane, so comments are not triplicated and every suppression is written in one syntax you can grep for.

Aim for at most one authoritative result per domain per pull request: one misconfiguration thread, one secrets hit, one image CVE summary. Duplicated gates teach developers to ignore all of them equally.

If you would rather trivy fs --scanners secret did the CI sweeping, that is fine, but then Checkov's secrets framework goes in the skip list and stays there. Put the image CVE gate on the job that builds the image rather than on the Terraform job, even though Checkov sca_image could technically run there with BC_API_KEY set.

These extras stay shallow because they ride along on a scan whose real job is misconfiguration. detect-secrets gives Checkov regex and entropy checks over every file, and the same run can drop a CycloneDX or SPDX artefact on its way out. Both come free with work already happening, and the depth matches the price. Selecting frameworks explicitly stops that free ride from slowing the scan you actually care about.

A pipeline layout that works

A sane default for a shop running Terraform, Kubernetes and containers is three jobs. Job 1 runs checkov -d . --framework terraform,kubernetes --skip-framework sca_image,sca_package,secrets on every infrastructure pull request. Job 2 owns the whole secrets lane here, with the Gitleaks hook on developer machines and gitleaks git on every push, which is why Job 1 hands secrets over instead of sweeping again. Job 3 runs syft and grype against the image tag you built. Each job uploads SARIF (Static Analysis Results Interchange Format, the standard file that code-scanning dashboards read) under its own category, or deduplicated by tool name. Developers then learn three comment threads instead of one thread showing the same bucket finding three times over from Checkov, Trivy config and Trivy fs.

terminal
$ checkov -d . --framework terraform,kubernetes --skip-framework sca_image,sca_package,secrets -o sarif --output-file-path .
$ trivy image --format sarif -o trivy-image.sarif myapp:${{ github.sha }}
output
Wrote SARIF output to ./results_sarif.sarif
Wrote trivy-image.sarif
# IaC findings from Checkov; CVE findings from Trivy image — separate artefacts

Notice what Job 1 is not doing. Because Job 2 owns secrets from the developer's machine through to every push, the infra job skips that framework alongside the sca_* pair, and the pull request gets one Checkov thread about misconfiguration and nothing else. Delete Job 2 tomorrow and secrets comes back out of that skip list the same day.

When leadership asks for "one scanner", put a number on the cost of overlap. Three tools reporting the same public S3 bucket produce three suppressions in three syntaxes, and somebody has to keep all three current. A documented ownership map beats a consolidation that triples the noise. And before you quote Checkov coverage in that meeting, look at the sca_* counts, because a framework that never ran also reports zero failures.

Try this

Run Checkov for infrastructure only, with the secrets and SCA frameworks skipped. Then run the commit-time secrets check, an image scanner, and Checkov's secrets sweep as its own step. Stage a file holding a fake key first, or the hook has nothing to read. What you want on screen afterwards is one artefact per lane, rather than three copies of the same alert.

terminal
$ checkov -d . --framework terraform,kubernetes --skip-framework secrets,sca_image,sca_package --compact --quiet
$ gitleaks git --pre-commit --staged --redact || true
$ trivy image myapp:local --severity HIGH,CRITICAL -f sarif -o trivy-image.sarif
$ checkov -d . --framework secrets --quiet
output
terraform/kubernetes: Passed 1016, Failed 48
INF scan completed in 9.8ms
INF no leaks found
Wrote trivy-image.sarif
secrets: Failed 0
# IaC job authoritative for misconfig; secrets/CVE have their own owners

Takeaway

Overlap is not free coverage. It is the same finding suppressed three times in three syntaxes, and those three suppressions go stale at three different rates. Pick one source of truth per domain: Checkov for IaC misconfiguration, a commit-time secrets scanner with a CI sweep behind it, and an image or SBOM tool for CVEs. Turning off Checkov's sca_* frameworks, and its secrets framework once another tool owns that sweep, is housekeeping, not a security regression.

Write the map down today, in the README or beside .checkov.yaml, while you still remember why each lane went where it did. Next, put the IaC lane behind a real merge gate with SARIF upload (cv-cicd), so the map becomes something the pipeline enforces instead of a diagram nobody reads.

Quick check
01You add gitleaks git --pre-commit --staged --redact as your hook, paste a fake key into config.yml, and run the hook. It prints INF no leaks found. What is the most likely reason?
Correct — The hook reads staged changes, so an edit sitting in your working tree is invisible to it. Run git add on config.yml and the hook finally has something to look at.
Incorrect — --redact masks the secret value in the output so the credential is not reprinted in your terminal or CI log. The finding itself still prints, with the file and the rule that matched.
Incorrect — That describes plain gitleaks git, which used to be called gitleaks detect. It sweeps history after the fact. The pre-commit form does the opposite job and looks at what has not been committed yet.
Incorrect — Checkov's secrets framework runs detect-secrets over the whole tree as a CI sweep, which is a different lane from blocking at commit time. Swapping tools does not fill an empty staging area.
02Your infra job runs checkov -d . --framework terraform,kubernetes --skip-framework sca_image,sca_package,secrets, but the team has no Gitleaks hook and no other secrets job anywhere. Which part of that setup should change?
Incorrect — Those two frameworks match shipped components against a vulnerability database, and they need BC_API_KEY before they do anything at all. Neither one looks for credentials.
Incorrect — Skipping a framework stops it running, so those checks are gone rather than deferred. Naming frameworks does keep the scan fast, but the coverage genuinely leaves with them.
Correct — You hand the secrets lane over on the day another tool owns it, and not before. With no hook and no other sweep, Checkov's secrets framework is the only thing reading the tree.
Incorrect — That scanner looks at configuration, not credentials. Secret is a separate value in the same list, so picking the wrong one leaves the lane empty while looking busy.
03An auditor asks for the release SBOM and a CVE report. You have sbom.cdx.json from checkov -d . -o cyclonedx_json, plus a job log reading checkov sca_image: Passed 0, Failed 0. What do you hand over?
Incorrect — Zero passed alongside zero failed means nothing was parsed, which is exactly what a missing BC_API_KEY looks like. A framework that never ran also reports no failures.
Incorrect — The format is right and the scope is wrong. Checkov's CycloneDX records the modules, providers and image references its graph resolved, not the ingredients of the thing you shipped.
Incorrect — Scope is not the gap. A wider scan still produces a graph inventory, and sca_image still does nothing without the Prisma Cloud key.
Correct — Syft run against the image or build artefact gives the inventory an auditor is asking for, with Grype matching CVEs against it. Checkov's file is supporting evidence about the infrastructure code.

Related