gcp-kms-cross-project-enter
Decrypting another project's key that unlocks a materialized credential privileged in that project enters it.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
?principal.project != ?key.project
?key CMEK-encrypts ?secret (customerManagedEncryption.kmsKeyName on ?secret references ?key), so
CanDecrypt(?key) unlocks ?secret's payload
?identity (the identity ?secret's payload authenticates as) is a principal in ?key.project AND is privileged there (has a role binding at project/folder/org scope covering ?key.project)
emit
| source type | Identity |
|---|---|
| target type | Project |
| source | <principal> |
| target | <key.project> |
| permissions | cloudkms.cryptoKeyVersions.useToDecrypt |
| conditions | iam_permission resource_policy key_permission trust_relationship |
| state logic | POTENTIAL by default (the entry is realized only after the attacker actually decrypts ?secret and assumes ?identity). ACTIVE only when ALL structural facts hold: CanDecrypt(?key) is ACTIVE, ?key CMEK-encrypts ?secret, ?secret CredentialsFor ?identity, and ?identity is a confirmed privileged principal in ?key.project. If ?identity's privilege in ?key.project is unresolved, stay POTENTIAL. BLOCKED if useToDecrypt is denied by VPC-SC/IAM deny. |
Narrative
{principal.name} (project {principal.project}) can decrypt {key.name} in project {key.project}, unlocking {secret.name} whose payload authenticates as {identity.name} - a privileged principal in {key.project} - entering {key.project}.
Raw rule rules/derived/gcp/kms.yaml
id: gcp-kms-cross-project-enter
emits: CanEnterProject
description: Decrypting another project's key that unlocks a materialized credential privileged in that
project enters it.
match:
- - principal: null
- CanDecrypt
- key: null
- - secret: null
- CredentialsFor
- identity: null
where:
- ?principal.project != ?key.project
- ?key CMEK-encrypts ?secret (customerManagedEncryption.kmsKeyName on ?secret references ?key), so CanDecrypt(?key)
unlocks ?secret's payload
- ?identity (the identity ?secret's payload authenticates as) is a principal in ?key.project AND is privileged
there (has a role binding at project/folder/org scope covering ?key.project)
emit:
source_type: Identity
target_type: Project
source: <principal>
target: <key.project>
permissions:
- cloudkms.cryptoKeyVersions.useToDecrypt
conditions:
- iam_permission
- resource_policy
- key_permission
- trust_relationship
state_logic: 'POTENTIAL by default (the entry is realized only after the attacker actually decrypts
?secret and assumes ?identity). ACTIVE only when ALL structural facts hold: CanDecrypt(?key) is ACTIVE,
?key CMEK-encrypts ?secret, ?secret CredentialsFor ?identity, and ?identity is a confirmed privileged
principal in ?key.project. If ?identity''s privilege in ?key.project is unresolved, stay POTENTIAL.
BLOCKED if useToDecrypt is denied by VPC-SC/IAM deny.'
confidence: min(contributing_confidences) * 0.8
derived_from:
- <CanDecrypt edge_id>
- <key CMEK-encrypts ?secret>
- <CredentialsFor edge_id>
- <?identity privilege binding in key.project>
false_positive_note: "A bare cross-project useToDecrypt on a CMEK key (the overwhelmingly common, legitimate\
\ cross-project CMEK pattern \u2014 e.g. decrypting a CMEK-encrypted GCS object in another project)\
\ is NOT project entry and MUST NOT emit this edge. Require a MATERIALIZED credential node: ?key ->\
\ CMEK-encrypts -> ?secret -> CredentialsFor -> ?identity, with ?identity demonstrably privileged\
\ in ?key.project. Absent any of those, do not emit. The ACTIVE branch must rest on these structural\
\ matches, never on prose about \"material privileged there\"."
narrative: "{principal.name} (project {principal.project}) can decrypt {key.name} in project {key.project},\
\ unlocking {secret.name} whose payload authenticates as {identity.name} \u2014 a privileged principal\
\ in {key.project} \u2014 entering {key.project}."