azure-evg-read-dlq
A principal with read access to an Event Grid subscription's dead-letter destination (Storage blob container) can read all failed/expired event payloads, including any credential material they contain.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'principal': None}
CanReadData {'storage_container': None}
{'subscription': None} HasPolicy {'dlq_policy': None}
where
node_type(?principal) in [Identity, Compute]
node_type(?storage_container) in [
ObjectStorage]
node_type(?subscription) == EventRule
?subscription.properties.deadLetterDestination.properties.resourceId == ?storage_container.id
?dlq_policy is the dead-letter destination configuration on the subscription
emit
| source type | Identity |
|---|---|
| target type | GenericStorage |
| source | ?principal |
| target | ?storage_container |
| permissions | Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read (or Storage Blob Data Reader role) |
| state logic | ACTIVE when the principal has effective read permission on the storage container AND the subscription is configured with a dead-letter destination pointing to that container. The principal can read all event blobs in the dead-letter container, including events that failed delivery or expired. BLOCKED if a deny assignment covers blob read on the container or if no dead-letter destination is configured. |
Narrative
{principal.name} has read access to the Storage container {target.name} that serves as the dead-letter destination for Event Grid subscription on topic {subscription.topic}; it can read all failed/expired event payloads in the container, including any credential material embedded in those events.
Raw rule rules/derived/azure/eventgrid.yaml
id: azure-evg-read-dlq
emits: CanReadData
description: A principal with read access to an Event Grid subscription's dead-letter destination (Storage
blob container) can read all failed/expired event payloads, including any credential material they contain.
match:
- - principal: null
- CanReadData
- storage_container: null
- - subscription: null
- HasPolicy
- dlq_policy: null
where:
- node_type(?principal) in [Identity, Compute]
- node_type(?storage_container) in [ObjectStorage]
- node_type(?subscription) == EventRule
- ?subscription.properties.deadLetterDestination.properties.resourceId == ?storage_container.id
- ?dlq_policy is the dead-letter destination configuration on the subscription
emit:
source_type: Identity
target_type: GenericStorage
source: ?principal
target: ?storage_container
permissions:
- Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read (or Storage Blob Data Reader
role)
conditions: []
state_logic: ACTIVE when the principal has effective read permission on the storage container AND the
subscription is configured with a dead-letter destination pointing to that container. The principal
can read all event blobs in the dead-letter container, including events that failed delivery or expired.
BLOCKED if a deny assignment covers blob read on the container or if no dead-letter destination is
configured.
confidence: 0.92
derived_from:
- '?principal CanReadData ?storage_container: effective Storage blob read permission'
- 'subscription.properties.deadLetterDestination.properties.resourceId: configuration link to DLQ container'
- 'messaging-chains.yaml: messaging-read-data-exfiltration (if DLQ contains credentials, credential-chains
fires)'
false_positive_note: The dead-letter queue is populated with events that failed delivery after max retries
or that expired before delivery. These failures are often transient (endpoint down, slow network),
so the DLQ may be infrequently populated. However, if a custom topic receives events with sensitive
data and event delivery fails systematically (e.g., webhook endpoint unreachable), the DLQ accumulates
those events. Confirm that the subscription actually uses a dead-letter destination (not all subscriptions
do); check deadLetterDestination.properties.resourceId on the subscription object. If the container
is in a different subscription and the principal crosses that boundary to read it, check the cross-subscription
path separately.
narrative: '{principal.name} has read access to the Storage container {target.name} that serves as the
dead-letter destination for Event Grid subscription on topic {subscription.topic}; it can read all
failed/expired event payloads in the container, including any credential material embedded in those
events.'