Summary
The KMS statement in the CI/CD deploy role's networking policy grants kms:CreateGrant, kms:Decrypt, kms:Encrypt, kms:GenerateDataKey and kms:DescribeKey on Resource = "*" with no Condition. Every other KMS grant in ci-cd-permissions/ is gated on an ARN prefix or on aws:ResourceTag/Project (KMSAliasMutate, KMSReadTaggedOnly, KMSTagOnCreate, KMSMutateTaggedOnly), and KMSReadTaggedOnly gates the far weaker kms:GetKeyPolicy explicitly to stop account-wide key reconnaissance. Because AWS CMKs carry the default key policy that delegates to IAM, this statement really does reach unrelated keys in the account. Blast radius is our own hosted AWS account, not customer accounts.
Location
terraform/environments/aws/ci-cd-permissions/policy_networking.tf:166 at 3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd (the KMS Sid spans lines 164-175)
Failure scenario
A leaked GitHub Actions OIDC token assumes cudly-terraform-deploy and calls kms:Decrypt against any CMK in the account, including keys belonging to unrelated workloads that share it. kms:CreateGrant lets it hand Decrypt on any CMK to a grantee principal it controls, and that grant survives the deploy role later being revoked.
Evidence
{
Sid = "KMS"
Effect = "Allow"
Action = [
"kms:CreateGrant",
"kms:Decrypt",
"kms:DescribeKey",
"kms:Encrypt",
"kms:GenerateDataKey",
]
Resource = "*"
}
Suggested fix
Add the same StringEqualsIgnoreCase condition on aws:ResourceTag/Project that KMSMutateTaggedOnly (policy_compute.tf:347-375) already uses, keeping only kms:DescribeKey unconditioned if a plan-time lookup needs it. Note the ACM (:139) and Route53 (:152) statements in the same file are also uncommented "*" grants, though those actions are read-mostly.
Found by the 2026-09-02 codebase audit, finding A13-004, reported by one reviewer and independently confirmed by a second. Full report: docs/audits/codebase-audit-2026-09-02.md.
Summary
The
KMSstatement in the CI/CD deploy role's networking policy grantskms:CreateGrant,kms:Decrypt,kms:Encrypt,kms:GenerateDataKeyandkms:DescribeKeyonResource = "*"with noCondition. Every other KMS grant inci-cd-permissions/is gated on an ARN prefix or onaws:ResourceTag/Project(KMSAliasMutate,KMSReadTaggedOnly,KMSTagOnCreate,KMSMutateTaggedOnly), andKMSReadTaggedOnlygates the far weakerkms:GetKeyPolicyexplicitly to stop account-wide key reconnaissance. Because AWS CMKs carry the default key policy that delegates to IAM, this statement really does reach unrelated keys in the account. Blast radius is our own hosted AWS account, not customer accounts.Location
terraform/environments/aws/ci-cd-permissions/policy_networking.tf:166at 3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd (theKMSSid spans lines 164-175)Failure scenario
A leaked GitHub Actions OIDC token assumes
cudly-terraform-deployand callskms:Decryptagainst any CMK in the account, including keys belonging to unrelated workloads that share it.kms:CreateGrantlets it handDecrypton any CMK to a grantee principal it controls, and that grant survives the deploy role later being revoked.Evidence
{ Sid = "KMS" Effect = "Allow" Action = [ "kms:CreateGrant", "kms:Decrypt", "kms:DescribeKey", "kms:Encrypt", "kms:GenerateDataKey", ] Resource = "*" }Suggested fix
Add the same
StringEqualsIgnoreCasecondition onaws:ResourceTag/ProjectthatKMSMutateTaggedOnly(policy_compute.tf:347-375) already uses, keeping onlykms:DescribeKeyunconditioned if a plan-time lookup needs it. Note the ACM (:139) and Route53 (:152) statements in the same file are also uncommented"*"grants, though those actions are read-mostly.Found by the 2026-09-02 codebase audit, finding
A13-004, reported by one reviewer and independently confirmed by a second. Full report:docs/audits/codebase-audit-2026-09-02.md.