gcp-gcs-enter-project-via-secrets

A cross-project principal that can read a bucket containing credentials for a principal privileged in the bucket's project effectively enters that project.

derived gcp emits CanEnterProject

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'principal': None} CanReadData {'bucket': None} {'bucket': None} ContainsSecret {'secret': None} {'secret': None} CredentialsFor {'identity': None}

where

?principal.project != ?bucket.project # cross-project read ?identity has bindings granting privilege in ?bucket.project

emit

source typeIdentity
target typeProject
source?principal
target<bucket.project>
permissionsstorage.objects.get
conditionsiam_permission resource_policy key_permission
state logicInherits weakest state from CanReadData (ACTIVE or CONDITIONAL(key_permission) for CMEK), ContainsSecret, and CredentialsFor contributors. ACTIVE when all three contributing edges are ACTIVE and the credentialed identity is confirmed as privileged in the target project; CONDITIONAL(key_permission) when the bucket is CMEK and service-agent grant is unconfirmed.

Narrative

{principal.name} (project {projectB.name}) can read objects in bucket {bucket.name} (project {projectA.name}), which is classified as containing credentials for {identity.name} that is privileged in {projectA.name} - reading the object yields a pivot into project {projectA.name}.

Raw rule rules/derived/gcp/gcs.yaml

id: gcp-gcs-enter-project-via-secrets
emits: CanEnterProject
description: A cross-project principal that can read a bucket containing credentials for a principal privileged
  in the bucket's project effectively enters that project.
match:
- - principal: null
  - CanReadData
  - bucket: null
- - bucket: null
  - ContainsSecret
  - secret: null
- - secret: null
  - CredentialsFor
  - identity: null
where:
- '?principal.project != ?bucket.project   # cross-project read'
- ?identity has bindings granting privilege in ?bucket.project
emit:
  source_type: Identity
  target_type: Project
  source: ?principal
  target: <bucket.project>
  permissions:
  - storage.objects.get
  conditions:
  - iam_permission
  - resource_policy
  - key_permission
  state_logic: Inherits weakest state from CanReadData (ACTIVE or CONDITIONAL(key_permission) for CMEK),
    ContainsSecret, and CredentialsFor contributors. ACTIVE when all three contributing edges are ACTIVE
    and the credentialed identity is confirmed as privileged in the target project; CONDITIONAL(key_permission)
    when the bucket is CMEK and service-agent grant is unconfirmed.
  confidence: min(contributing_confidences) * 0.8
  derived_from:
  - ?principal CanReadData ?bucket
  - ?bucket ContainsSecret ?secret
  - ?secret CredentialsFor ?identity
  false_positive_note: "Low-confidence derivation: ContainsSecret is asserted from object-name heuristics\
    \ (e.g. `.tfstate`, `serviceaccount`, `credentials`, `.env`, `kubeconfig`) \u2014 NEVER from reading\
    \ object content. Emit with confidence 0.6-0.7 when based on name alone; raise to 0.85+ when object\
    \ metadata (content-type, label, annotation) corroborates. The CredentialsFor edge must also be evidenced\
    \ or asserted. Do NOT emit when the principal and bucket are in the same project. The credentialed\
    \ identity must be privileged in the BUCKET's project, not the reading principal's project."
  narrative: "{principal.name} (project {projectB.name}) can read objects in bucket {bucket.name} (project\
    \ {projectA.name}), which is classified as containing credentials for {identity.name} that is privileged\
    \ in {projectA.name} \u2014 reading the object yields a pivot into project {projectA.name}."
move · open · esc close