How do you find every live Checkov suppression across your Terraform repos, and spot the stale ones?
answer
- two sources, not one
- config and CI skips leave no annotation
- an excluded path leaves no record at all
- read the skipped section of the output
- git blame supplies the missing date
basics
~20 sCollect two sources, not one: every inline skip annotation in the source, and every skip-check, skip-path and skip flag in scan configuration and pipeline definitions. Then age each one with git history and the scan's skipped-checks output.
solid answer
~50 sGrepping for `checkov:skip=` is the obvious half and it misses the dangerous half. Config-file `skip-check` and `skip-path` entries, and `--skip-check` flags baked into pipeline definitions, leave no annotation in the code — and an excluded path produces neither a finding nor a skip record, so it is invisible in the output too. So I collect three things per repository: the annotations, the skip entries in `.checkov.yaml` and CI definitions, and the skipped-checks section of the scan's JSON or SARIF output, which gives file, resource, check id and comment as structured records. Then `git blame` supplies the date and author the annotation never carried. Stale shows up as a skip matching no check any more, a reason naming a closed ticket, or the same reason string across dozens of repositories — a module or template propagated it.
go deeper
Know that suppressions can be found by searching the repository for the annotation form, and that scan configuration is a second place exemptions can hide.
Be ready to name all three sources — annotations, configuration and pipeline flags, and the scan's own skipped-checks output — and to explain why an excluded path is invisible in both the code and the report.
Show how you turn the raw list into triage: git blame for age and author, matching against live findings to spot inert annotations, and reading repeated reason strings as evidence of copy-paste propagation.
Own the recurring version of this: a set of unowned, undated decisions grows unless someone rebuilds and diffs the inventory. Be able to argue what metadata an exemption must carry for this to be tractable at estate scale.
## Why this is an inventory problem at all A suppression is written once, by one person, under deadline, and then it is permanent and unattended. No report tells you it aged. No scan tells you the reason stopped being true. Over a few years an estate accumulates a large, unowned set of decisions that are individually defensible and collectively unknown. Enumerating them is the only way anyone finds out what the gate is actually enforcing versus what it is configured to enforce. ## Source one: the annotations A repository-wide search for the annotation form (`checkov:skip=`) gets you every inline suppression in the source. This part is easy and people stop here. What the raw grep does not give you is whether each one still does anything. A skip only has an effect if the check it names would otherwise fire on that block. Change the resource so it is compliant, and the annotation stays behind, suppressing nothing while telling every future reader that a risk was considered and accepted here. That is worse than no comment, because it is a false statement about the security posture of the code. ## Source two: the invisible skips This is the half that gets missed, and it is the half with the widest blast radius: - `skip-check` lists in `.checkov.yaml` — a rule disabled across an entire repository, with no field for a reason and nothing at any violating line. - `skip-path` patterns — whole directories removed from the scan. These are the hardest to find by inspection, because an excluded path produces **no finding and no skip record**. Nothing in the output shows the code was never looked at; you can only find it by reading the pattern. - Skip flags passed on the command line inside the pipeline definition, which live in a CI file that nobody thinks of as security configuration. An inventory that does not read all three of these under-reports, and it under-reports precisely where the exposure is largest. ## Source three: the scan's own output The authoritative view is the scanner telling you what it skipped. Run the scan across the estate and read the skipped section of the machine-readable output — it yields one structured record per suppressed check, carrying the file, the resource, the check id and the suppression comment. That is directly joinable into a single table across every repository, and it is the artifact you actually want: repo, path, resource, check id, reason. It has one more property that grep lacks: a suppression that appears in the skipped section is one that matched a real finding. Annotations present in source but absent from that section are inert. ## Ageing the list Git supplies the metadata the annotation does not carry. `git blame` on the skip line gives the commit, the author and the date; the commit message and the pull request give the change it was introduced with. From that you can build the columns that matter for triage: how old, who added it, and whether the change it accompanied is still relevant. ## What stale looks like Several distinct signals, and they call for different actions: - **Inert.** The annotation matches no finding — the resource was fixed, or the check id was renamed or removed. Delete it. An id that matches nothing produces no error and no warning, so these accumulate quietly. - **Aged intent.** A reason saying "temporary" or "until the migration lands", dated years ago. The migration either landed or was abandoned; either way the stated condition is resolvable by asking one person. - **Dead reference.** The reason names a ticket that is closed, or a system that was decommissioned. - **Orphaned.** The author has left, and no team claims the resource. This is an ownership problem before it is a security one. - **Propagated.** The same reason string appears verbatim across many repositories. That is copy-paste: a shared module, a repository template, or a widely-copied example carried the annotation outward. The receiving teams never evaluated it and usually do not know it is there. The fix is upstream — either the module should stop producing the violating configuration, or the exemption should exist once, where someone owns it, rather than in fifty places where nobody does. ## Turning it into something that stays true The inventory is only worth building once if it is rebuilt. In practice that means running the collection on a schedule and diffing it: new suppressions since last time is a short, reviewable list, where the full set is not. And it means fixing the metadata problem at the source — if an exemption needs an owner and a date to be triageable later, a free-text comment cannot carry either, and that argues for keeping the exemption somewhere that can.
- Why is grepping the source for skip annotations not enough?Because the widest exemptions are not annotations. A rule disabled in scan configuration, or a directory excluded by a path pattern, appears nowhere in the Terraform. The excluded path is the worst case: it produces no finding and no skip record, so neither the code nor the report shows that the scanner never looked. You have to read the config and the pipeline definition too.
- You find a skip naming a check id the scanner no longer has. What is it doing?Nothing. An id that matches no check suppresses no finding and raises no error — the scan just proceeds. The annotation still communicates to every reader that a risk was accepted at that line, which is now false. Renamed and retired ids are the usual cause, and the right action is to delete the line.
- The same skip reason appears verbatim in thirty repositories. What does that tell you?That it was propagated rather than decided. A shared module, a repository template or a much-copied example carried the annotation outward, and the receiving teams never evaluated the reason. Fix it upstream: stop the module emitting the violating configuration, or keep the exemption in the one place someone owns it, instead of in thirty places nobody does.
saying these in an interview costs you the question
- Greps only for inline annotations and calls the list complete
- Forgets that path exclusions leave no record in the output
- Assumes an annotation still suppresses something
- Treats a dead check id as harmless rather than misleading
- Has no way to date a suppression or find who added it
- Builds the inventory once and never rebuilds it