Follow-up to LeanerCloud/cloud-commitments-cli#1614 / PR LeanerCloud/cloud-commitments-cli#1682, which added scripts/check-gcp-secret-scope.sh. Sibling of #149 (the locals / for_each blind spot). Neither shape exists in the repo today, so this is hardening, not a live exposure.
1. google_project_iam_policy + data "google_iam_policy"
The authoritative-policy shape binds roles like this:
data "google_iam_policy" "admin" {
binding {
role = "roles/secretmanager.admin"
members = ["serviceAccount:..."]
}
}
resource "google_project_iam_policy" "project" {
project = var.project_id
policy_data = data.google_iam_policy.admin.policy_data
}
Note that simply adding _policy to the guard's resource-type list would not fix this: the role literal lives in the data block, which is not a tracked resource block, while the resource block that applies it project-wide contains no role at all. Catching this needs the two blocks correlated through policy_data.
This shape is also the most dangerous of the three, because google_project_iam_policy is authoritative: it replaces the project's entire IAM policy rather than adding to it.
2. Unindented resource bypass
The scanner detects a block close with /^}/ and a block open with ^resource. A resource whose body lines start at column 0 ends the block early, so a role literal after that point is not attributed to the block.
Inside terraform/ this is mitigated by terraform fmt -check -recursive terraform/ (.github/workflows/ci.yml:254), which enforces the indentation the scanner assumes. It is not mitigated anywhere else, and PR LeanerCloud/cloud-commitments-cli#1682 extends the guard's scan root to iac/, which has no equivalent fmt gate. So the assumption the scanner relies on is only enforced for part of what it now scans.
Suggested fix
Neither shape exists in the repo, so the smallest remedy that closes both is to make the guard refuse what it cannot analyse rather than teach it to analyse a shape nobody uses. The guard already does exactly this for .tf.json, which it exits 2 on rather than reporting clean.
google_project_iam_policy / data "google_iam_policy": exit 2, with a message saying the guard cannot analyse the authoritative-policy shape and pointing here. No policy_data correlation to build, no binding-shape fixtures to maintain, and the first person to add one of these blocks is routed to this issue instead of getting a false green. Correlating the two blocks is the right work when the shape arrives.
- Unindented resource bypass: extend the existing
terraform fmt -check -recursive step (.github/workflows/ci.yml:254) to cover iac/ as well as terraform/, so the indentation the scanner assumes is enforced across everything it now scans. One line, and it has independent value.
Acceptance
- A fixture containing
google_project_iam_policy exits 2 (not 0).
terraform fmt -check covers every root in the guard's SCAN_ROOTS, and iac/ is formatted so the new step passes.
- The LIMITATIONS header records that the authoritative-policy shape is rejected rather than unanalysed.
Remedy simplified (2026-08-03): correlating data "google_iam_policy" with google_project_iam_policy through policy_data, and the fixtures for it, were dropped in favour of failing closed on the shape, matching the guard's existing .tf.json handling. Neither shape exists in the tree, so building analysis for them now is work against a hypothetical. The HCL-parser rewrite is dropped for the same reason as in #149; the fmt gate covers the indentation assumption at a fraction of the cost.
Follow-up to LeanerCloud/cloud-commitments-cli#1614 / PR LeanerCloud/cloud-commitments-cli#1682, which added
scripts/check-gcp-secret-scope.sh. Sibling of #149 (thelocals/for_eachblind spot). Neither shape exists in the repo today, so this is hardening, not a live exposure.1.
google_project_iam_policy+data "google_iam_policy"The authoritative-policy shape binds roles like this:
Note that simply adding
_policyto the guard's resource-type list would not fix this: the role literal lives in thedatablock, which is not a tracked resource block, while theresourceblock that applies it project-wide contains no role at all. Catching this needs the two blocks correlated throughpolicy_data.This shape is also the most dangerous of the three, because
google_project_iam_policyis authoritative: it replaces the project's entire IAM policy rather than adding to it.2. Unindented resource bypass
The scanner detects a block close with
/^}/and a block open with^resource. A resource whose body lines start at column 0 ends the block early, so a role literal after that point is not attributed to the block.Inside
terraform/this is mitigated byterraform fmt -check -recursive terraform/(.github/workflows/ci.yml:254), which enforces the indentation the scanner assumes. It is not mitigated anywhere else, and PR LeanerCloud/cloud-commitments-cli#1682 extends the guard's scan root toiac/, which has no equivalent fmt gate. So the assumption the scanner relies on is only enforced for part of what it now scans.Suggested fix
Neither shape exists in the repo, so the smallest remedy that closes both is to make the guard refuse what it cannot analyse rather than teach it to analyse a shape nobody uses. The guard already does exactly this for
.tf.json, which it exits 2 on rather than reporting clean.google_project_iam_policy/data "google_iam_policy": exit 2, with a message saying the guard cannot analyse the authoritative-policy shape and pointing here. Nopolicy_datacorrelation to build, no binding-shape fixtures to maintain, and the first person to add one of these blocks is routed to this issue instead of getting a false green. Correlating the two blocks is the right work when the shape arrives.terraform fmt -check -recursivestep (.github/workflows/ci.yml:254) to coveriac/as well asterraform/, so the indentation the scanner assumes is enforced across everything it now scans. One line, and it has independent value.Acceptance
google_project_iam_policyexits 2 (not 0).terraform fmt -checkcovers every root in the guard'sSCAN_ROOTS, andiac/is formatted so the new step passes.Remedy simplified (2026-08-03): correlating
data "google_iam_policy"withgoogle_project_iam_policythroughpolicy_data, and the fixtures for it, were dropped in favour of failing closed on the shape, matching the guard's existing.tf.jsonhandling. Neither shape exists in the tree, so building analysis for them now is work against a hypothetical. The HCL-parser rewrite is dropped for the same reason as in #149; the fmt gate covers the indentation assumption at a fraction of the cost.