skip to content

In Checkov, what does a #checkov:skip comment inside a Terraform resource block do?

level: juniorimportance: should knowfreq 58%

answer

  1. one comment, one block
  2. it has to sit inside the braces
  3. the id must match exactly
  4. the reason is free text, unchecked
  5. counted as skipped, not failed

basics

~20 s

A checkov:skip comment suppresses one named check for the single block it sits inside. The form is checkov:skip=CHECK_ID:reason; the reason is free text nothing validates, and the finding is reported as skipped rather than failing the run.

solid answer

~40 s

The comment is an inline suppression. It must sit **inside** the block it exempts, and it names exactly one check id: `#checkov:skip=CKV_AWS_1:CI role is bounded elsewhere, INFRA-4412`. Everything after the second colon is a free-text reason — Checkov carries it into the report but never parses or verifies it, so it can name a ticket, a person, or nothing at all. The effect is that the check is evaluated, matched, and then moved from failed to skipped, so it does not contribute to the non-zero exit code that fails the pipeline. Scope is narrow by design: one block, one check id, and it dies when the resource is deleted. There is no expiry, no approver and no second signature — the only review it gets is whoever reviews the diff that introduced it.

code

hcl · 13 lines
hcl
resource "aws_iam_policy" "ci_deploy" {
  #checkov:skip=CKV_AWS_1:CI role is bounded by a permissions boundary, INFRA-4412
  name = "ci-deploy"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = "*"
      Resource = "*"
    }]
  })
}

go deeper

for a junior

Be ready to write the line from memory: the comment goes inside the block, names one check id, and takes a free-text reason. Know that a suppressed check is reported as skipped rather than failed.

for a middle

Explain the mechanics behind it: the check still runs and still matches, the finding is moved out of the failed bucket, and an unmatched id fails silently. Know why placement inside the block matters.

for a senior

Show that you treat a new skip line as a posture change under review. Talk about how narrow it is, that it has no expiry, and that a skip inside a shared module travels to every consumer.

for a principal

Own the framing that the person being gated writes their own exemption here. Be able to say when that is acceptable self-service and when the exemption needs to live somewhere with an owner and a review, rather than in a comment.

## What an inline suppression is An infrastructure scanner reads your Terraform, evaluates a library of checks against each resource, and exits non-zero if anything failed. An **inline suppression** is a comment in that same Terraform that tells the scanner to stand down for one specific check on one specific block. In Checkov the syntax is: ``` #checkov:skip=<CHECK_ID>:<free-text reason> ``` The reason part is optional; the check id is not. ## The three things that make it work **1. Placement.** The comment has to be *inside* the block it applies to — between the opening brace of the `resource` (or `module`, or `data`) block and its closing brace. A comment sitting on the line above the block is attached to nothing and the check still fails. This is the single most common reason a developer's skip "doesn't work". **2. The check id.** The id must match the check that actually raised the finding, character for character. An id that matches no check is not an error — the scan simply proceeds and the finding stays. So a typo, or a copy-pasted id from a different cloud provider's check family, fails silently. When a finding comes from a check that evaluates a *relationship* between resources (Checkov gives those ids beginning `CKV2_`), the finding may be reported against a resource other than the one you were editing, and the skip has to go on the block the report names. **3. One id per comment.** In practice you write one comment line per check id you want to exempt; a block that trips three checks carries three skip lines. ## What the reason is, and is not The text after the check id is not a structured field. Nothing requires a ticket number, an owner, a date or an expiry, and nothing checks that any of them are real. Checkov carries the string into its output next to the skipped check — so it shows up in the human-readable report and in the machine-readable JSON/SARIF output — but it is a note to the next human, not a control. This matters more than it sounds: a suppression is the only place in a policy pipeline where the person being blocked writes the exemption themselves, in the code they already own, with no approval step of its own. ## Where the finding goes A suppressed check is not silently deleted. It is evaluated, matched, and then reported in the **skipped** bucket rather than the failed one. That distinction is what makes suppressions auditable at all: the scan output still knows the resource was non-compliant and still carries the stated reason. It is also why suppression differs from simply not scanning a path — an excluded directory produces no finding *and* no skip record, so nothing is left to review. Because the check is moved out of the failed bucket, it no longer contributes to the run's exit code. From the gate's point of view the violation never happened. ## The properties worth internalising - **Narrow:** one block, one check. Adding a second resource next to it does not inherit the exemption. - **Local:** it lives at the violating line, so it appears in the diff of the change that needed it and gets reviewed with that change. - **Mortal:** delete the resource, and the suppression goes with it. - **Immortal in every other way:** it has no expiry and nothing re-raises it. "Temporary" in a skip reason means permanent until a human goes looking. - **Portable, which is the hazard:** a skip line inside a shared module or a repository template is copied into every consumer, carrying a judgment those teams never made. That last property is why reviewers treat a new skip line as a change to the security posture of the repository, not as a comment.

  • The skip comment is in the file but the check still fails. What do you look at first?
    Placement, then the id. The comment must be inside the block that the finding names, not above it; a check that spans resources can raise the finding on a different block than the one you edited. Then compare the id character for character against the report — an id that matches no check produces no error, it just does nothing, so a typo fails silently.
  • Does one skip comment exempt the whole resource from scanning?
    No. It exempts exactly the check id it names. A resource that trips three checks needs three skip lines, each with its own id and reason. If someone actually wants the resource excluded from all checks, that is a scan-scope decision made in configuration, not an inline annotation — and it removes every other check over that code too.
  • Where does the reason text end up?
    In the scan output, alongside the skipped check, in both the human-readable and machine-readable formats. That makes it greppable after the fact, which is the only reason it is worth writing well. It is not parsed: no ticket link is required, no owner is recorded, and there is no field the scanner could use to expire it.

It is a sticky note on one line of one form, not a policy change: it excuses that line, it is written by the person filling in the form, and nobody countersigns it.

saying these in an interview costs you the question

  • Places the comment above the resource block instead of inside it
  • Thinks the reason text is validated or must name a ticket
  • Believes one skip comment suppresses every check on the resource
  • Assumes a mistyped check id causes an error rather than doing nothing
  • Thinks a suppressed finding still fails the build
  • Expects the suppression to lapse on its own after some period

context