In Checkov, how does a CKV2_ graph check differ from a CKV_ attribute check, and why can one bucket pass one and fail the other?
answer
- one block versus connected blocks
- edges come from references
- public access block as a separate resource
- prefix is a convention
basics
~20 sA CKV_ check reads one resource's own settings; a CKV2_ graph check tests how resources are connected, such as a bucket and its public access block. A clean bucket still fails when that linked resource is missing or unreferenced.
solid answer
~40 sClassic `CKV_` checks evaluate one resource block's attributes. `CKV2_` checks are graph checks: Checkov builds a connection graph from the references between blocks and the check asserts a relationship. `CKV2_AWS_6` requires an `aws_s3_bucket` to be connected to an `aws_s3_bucket_public_access_block` whose `block_public_acls` and `block_public_policy` are true; `CKV2_AWS_11` requires every `aws_vpc` to be connected to an `aws_flow_log`. So a bucket whose own attributes pass can still fail `CKV2_AWS_6` if the access block is missing, lives in a repo Checkov did not scan, or names the bucket with a literal string, which gives the graph no edge. The prefix is a convention, not a law: some graph checks, such as `CKV_AWS_18` for access logging, keep a `CKV_` ID.
go deeper
Remember that some Checkov checks look at one resource and others look at how resources are connected, and that the connected kind usually has a CKV2_ ID.
Explain how Checkov draws edges from references and walk through why a literal bucket name or an unscanned module breaks CKV2_AWS_6 or CKV2_AWS_11.
Show you can triage a graph-check failure in a split estate: decide whether the companion resource is truly missing or just out of scan scope, and fix the reference style.
Consider how repository boundaries shape which relationships any static scanner can verify, and where that pushes you toward checking deployed state instead.
## Two kinds of built-in check Checkov ships well over a thousand built-in checks. They come in two shapes, and the difference explains a whole class of confusing results. - **Attribute checks** look at **one resource block** in isolation: does this `aws_s3_bucket` set an ACL that allows public reads, does this Kubernetes `Deployment` run a privileged container (`CKV_K8S_16`). Most of them carry a `CKV_` ID such as `CKV_AWS_21` (bucket versioning) or `CKV_K8S_16`. - **Graph checks**, which Checkov's documentation also calls composite or connection-state policies, evaluate **relationships between resources**. Checkov builds a virtual connection graph of the scanned configuration and the check asserts that certain resources are, or are not, connected. Most of them carry a `CKV2_` ID. Graph checks are defined in YAML with conditions such as `cond_type: connection` and `connected_resource_types`, combined with ordinary attribute conditions on either end of the connection. Writing such a definition is a separate skill; here the point is reading what the built-in ones demand. ## Two concrete graph checks | Check | Subject | What it demands | |---|---|---| | `CKV2_AWS_6` | `aws_s3_bucket` | connected to an `aws_s3_bucket_public_access_block` with `block_public_acls` and `block_public_policy` equal to `true` | | `CKV2_AWS_11` | `aws_vpc` | connected to an `aws_flow_log` | Neither can be decided by looking at the bucket or the VPC alone. The public access block and the flow log are **separate Terraform resources**; what matters is whether one points at the other. ## Where the edges come from Checkov draws an edge when one block **references** another, for example `bucket = aws_s3_bucket.logs.id` inside the access block, or `vpc_id = aws_vpc.main.id` inside the flow log. That has practical consequences: 1. **A literal string is not a reference.** An access block that says `bucket = "acme-logs"` instead of referencing the bucket resource gives the graph nothing to join, so `CKV2_AWS_6` fails even though the protection exists. 2. **Scope is what you scanned.** If the access block is declared in another repository or another root module that Checkov did not see in the same run, there is no vertex to connect to, and the check fails. 3. **Modules count only if they were read.** A flow log created inside a registry module is invisible unless the module was downloaded for the scan. Each of these produces a failure on a bucket or VPC that is, in the deployed account, perfectly configured. The fix is usually to reference the resource rather than its name, or to scan the configuration that contains both ends. ## Why a bucket can pass one and fail the other Consider a bucket with no `acl` argument at all. The attribute side of the bucket is clean: nothing on the bucket itself grants public access. But if the repository never declares an `aws_s3_bucket_public_access_block`, or declares one that is not connected, `CKV2_AWS_6` fails. The two results are not contradictory — they answer different questions: - the attribute check asks **"is this block misconfigured?"** - the graph check asks **"is the protective companion resource present and attached?"** The split matters more because the AWS provider models many S3 settings — versioning, logging, ACLs, encryption — as separate resources next to the bucket. Checks such as `CKV_AWS_18` and `CKV_AWS_20` are written as graph checks so that they accept either the inline argument or the connected resource. ## The prefix is a convention It is tempting to say "CKV2 means graph". At Checkov 3.3 that is mostly true but not exact: - **Some graph checks keep a `CKV_` ID.** `CKV_AWS_18` (S3 access logging) and `CKV_AWS_20` (public-read ACL) are defined as YAML graph checks that accept either an inline argument or a connected `aws_s3_bucket_logging` / `aws_s3_bucket_acl` resource. - **At least one `CKV2_` ID is a Python resource check**, for example `CKV2_AWS_70`. So when a result surprises you, read the check's definition rather than inferring its behaviour from the prefix. `checkov --list` prints the catalogue, and the policy index in the Checkov documentation lists each ID with its resource types. ## Operating graph checks in a gate - Expect graph checks to be the main source of **cross-repository false positives** in a split estate, because the companion resource often lives elsewhere. - Prefer fixing the reference style over excluding the check; a referenced companion is also easier for humans to review. - Checkov also builds graphs for plan JSON, CloudFormation, Kubernetes and Bicep, so the same reasoning applies outside Terraform source.
- The public access block for a bucket lives in a separate network repository. How do you stop CKV2_AWS_6 failing on every bucket?First confirm the protection really exists for each bucket. Then either move the access block next to the bucket it protects and reference it, or accept that the check cannot see across repositories and record that as a known limitation through your normal exemption process. Silently removing the check from every run loses the cases where the block is genuinely missing.
- Why can a flow log created inside a registry module still leave CKV2_AWS_11 failing?Checkov does not download registry or git modules by default, so the module's `aws_flow_log` never enters the graph and there is no connection to the VPC. Running with `--download-external-modules true` brings the module source into the scan, and the edge can then be drawn.
saying these in an interview costs you the question
- Every graph check has a CKV2_ ID and every CKV_ check reads one block.
- A graph check matches resources by bucket name, so a literal name string is enough.
- If the bucket's own attributes pass, CKV2_AWS_6 must pass too.
- Graph checks only run against plan JSON, never against HCL.
- CKV2 checks need a platform API key to run.