gcp-bigquery-explicit-cross-project-trust
A dataset ACL entry naming a dataset, view, or SA from another project grants cross-project data read access - a BigQuery-native cross-project trust.
match (record)
{
"field": "access[].dataset | access[].userByEmail (cross-project SA)",
"resource_type": "google.bigquery.Dataset"
}
where
ACL entry references a dataset, view, or SA whose project != the source dataset's project
emit
| source type | Project |
|---|---|
| target type | Project |
| source | <project owning the source dataset> |
| target | <foreign project owning the authorized dataset/view/SA> |
| conditions | resource_policy |
| state logic | ACTIVE when the authorized dataset or cross-project SA entry is present in the access[] array; CONDITIONAL(resource_policy) when the entry exists but the foreign principal's IAM cannot be confirmed. |
Narrative
Dataset {dataset.name} (project {projectA.name}) grants cross-project data access to a principal in project {projectB.name} via an authorized dataset or cross-project ACL entry; {projectB.name} can read {dataset.name} tables without holding IAM bindings in project {projectA.name}.
Raw rule rules/explicit/gcp/bigquery.yaml
id: gcp-bigquery-explicit-cross-project-trust
emits: CrossProjectTrust
description: "A dataset ACL entry naming a dataset, view, or SA from another project grants cross-project\
\ data read access \u2014 a BigQuery-native cross-project trust."
match_record:
resource_type: google.bigquery.Dataset
field: access[].dataset | access[].userByEmail (cross-project SA)
where:
- ACL entry references a dataset, view, or SA whose project != the source dataset's project
emit:
source_type: Project
target_type: Project
source: <project owning the source dataset>
target: <foreign project owning the authorized dataset/view/SA>
permissions: []
conditions:
- resource_policy
state_logic: ACTIVE when the authorized dataset or cross-project SA entry is present in the access[]
array; CONDITIONAL(resource_policy) when the entry exists but the foreign principal's IAM cannot be
confirmed.
false_positive_note: "Authorized views / authorized datasets (access[].dataset entries) grant the DATASET\
\ in project B read-only access to the source dataset in project A WITHOUT requiring direct IAM bindings\
\ in project A for the views' users. This is a data trust, not a management-plane trust. An attacker\
\ who controls project B's dataset can create views that read project A's tables. This edge does NOT\
\ allow cross-project IAM escalation \u2014 only data-plane read of the authorized tables. Authorized\
\ views CANNOT EXPORT data (EXPORT DATA fails without getData + storage.objects.create held by the\
\ querying principal independently). Cross-project SA entries (access[].userByEmail = SA from another\
\ project) grant that SA READER/WRITER/OWNER access in the source dataset \u2014 a broader trust."
narrative: Dataset {dataset.name} (project {projectA.name}) grants cross-project data access to a principal
in project {projectB.name} via an authorized dataset or cross-project ACL entry; {projectB.name} can
read {dataset.name} tables without holding IAM bindings in project {projectA.name}.
derived_from:
- google.bigquery.Dataset.access[] cross-project entry
api_source: bigquery.datasets.get (access[] array)
evidence_field: access[].dataset.projectId or cross-project SA email