azure-adls-public-container
ADLS Gen2 account with public blob access enabled and a public container exposes blob data to unauthenticated internet access.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_class(?account) == Data and node_type(?account) ==
DataLake
?account.properties.isHnsEnabled == true
?account.properties.publicNetworkAccess != Disabled # publicNetworkAccess=Disabled overrides allowBlobPublicAccess and closes the public endpoint entirely
?account.properties.allowBlobPublicAccess == true # account-level gate; if false, container publicAccess is ignored
node_type(?container) == ObjectStorage # blobServices/containers node
?container.properties.publicAccess in ['Blob', 'Container'] # Blob=blob-level anon read; Container=list+read
emit
| source type | Storage |
|---|---|
| target type | AnonymousIdentity |
| source | ?container |
| target | <AnonymousIdentity> |
| conditions | network_reachability |
| state logic | if ?account.properties.publicNetworkAccess == Disabled: DO NOT EMIT (public endpoint is fully closed regardless of allowBlobPublicAccess or container publicAccess settings — this is a blocking gate); elif ?account.properties.allowBlobPublicAccess == false: DO NOT EMIT (container publicAccess is ignored when account-level flag is false); elif networkAcls.defaultAction == Deny AND public internet is not in allowlist: CONDITIONAL(network_reachability) — the public access setting is enabled but the network firewall blocks public internet traffic; elif ?container.properties.publicAccess == 'Container': ACTIVE (anonymous list+read); elif ?container.properties.publicAccess == 'Blob': ACTIVE (anonymous blob read, no listing). |
Narrative
{container.name} in {account.name} is publicly readable from the internet without authentication: the account has allowBlobPublicAccess=true and the container publicAccess={container.properties.publicAccess}. Any internet user can read{if publicAccess=='Container': ' and list'} blobs in this container.
Raw rule rules/derived/azure/adls.yaml
id: azure-adls-public-container
emits: ExposedToInternet
description: ADLS Gen2 account with public blob access enabled and a public container exposes blob data
to unauthenticated internet access.
match:
- - account: null
- Contains
- container: null
where:
- node_class(?account) == Data and node_type(?account) == DataLake
- ?account.properties.isHnsEnabled == true
- '?account.properties.publicNetworkAccess != Disabled # publicNetworkAccess=Disabled overrides allowBlobPublicAccess
and closes the public endpoint entirely'
- '?account.properties.allowBlobPublicAccess == true # account-level gate; if false, container publicAccess
is ignored'
- 'node_type(?container) == ObjectStorage # blobServices/containers node'
- '?container.properties.publicAccess in [''Blob'', ''Container''] # Blob=blob-level anon read; Container=list+read'
emit:
source_type: Storage
target_type: AnonymousIdentity
source: ?container
target: <AnonymousIdentity>
permissions: []
conditions:
- network_reachability
state_logic: "if ?account.properties.publicNetworkAccess == Disabled: DO NOT EMIT (public endpoint is\
\ fully closed regardless of allowBlobPublicAccess or container publicAccess settings \u2014 this\
\ is a blocking gate); elif ?account.properties.allowBlobPublicAccess == false: DO NOT EMIT (container\
\ publicAccess is ignored when account-level flag is false); elif networkAcls.defaultAction == Deny\
\ AND public internet is not in allowlist: CONDITIONAL(network_reachability) \u2014 the public access\
\ setting is enabled but the network firewall blocks public internet traffic; elif ?container.properties.publicAccess\
\ == 'Container': ACTIVE (anonymous list+read); elif ?container.properties.publicAccess == 'Blob':\
\ ACTIVE (anonymous blob read, no listing)."
confidence: '0.93'
derived_from:
- "<Contains edge_id (account \u2192 container)>"
- <container publicAccess property record>
- <account allowBlobPublicAccess property record>
false_positive_note: "CRITICAL: the account-level `allowBlobPublicAccess=false` setting OVERRIDES any\
\ container-level publicAccess \u2014 the storage service returns 409 if public access is attempted\
\ on a container under a public-blocked account. Do NOT emit ExposedToInternet if the account flag\
\ is false, even if the container property says Blob or Container. A networkAcls.defaultAction=Deny\
\ firewall with no public internet IP allowlist blocks the public endpoint \u2014 downgrade to CONDITIONAL(network_reachability).\
\ Note that publicAccess=Container allows directory listing (a superset of Blob-only access) \u2014\
\ treat it as higher impact. This is purely anonymous data exposure (no Entra identity involved);\
\ use target AnonymousIdentity, not a tenant/account boundary node."
narrative: '{container.name} in {account.name} is publicly readable from the internet without authentication:
the account has allowBlobPublicAccess=true and the container publicAccess={container.properties.publicAccess}.
Any internet user can read{if publicAccess==''Container'': '' and list''} blobs in this container.'