azure-appconfig-connstring-reads-all-kv
A listKeys holder uses the access-key connection string to read every secret key-value, bypassing data-plane RBAC.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'principal': None}
HasPermission {'store': None}
{'store': None} Contains {'dataplane': None}
{'dataplane': None} ExposesCredential {'connstring': None}
{'connstring': None} CredentialsFor {'dataplane': None}
{'dataplane': None} ContainsSecret {'keyvalue': None}
where
node_class(?principal) == Identity
node_class(?store) == ManagementService
provider_type(?store) == 'Microsoft.AppConfiguration/configurationStores'
node_class(?dataplane) == Data
node_class(?connstring) ==
Secret # ?connstring is Secret-class (subtype Credential), so it is a valid CredentialsFor source (source set [Secret, Credential, AccessKey, Token, Certificate]) and a valid ExposesCredential target (Credential)
node_type(?connstring) == Credential
?principal has EFFECTIVE Microsoft.AppConfiguration/configurationStores/listKeys/action at the store scope
node_type(?keyvalue) in [Secret, ConnectionString, APIKey, Password]
?keyvalue is a plaintext key-value (not a keyvaultref)
emit
| source | ?principal |
|---|---|
| target | ?keyvalue |
| permissions | Microsoft.AppConfiguration/configurationStores/listKeys/action |
| conditions | iam_permission service_state |
| state logic | ACTIVE when listKeys/action is effective AND disableLocalAuth == false (a valid connection string is obtainable). A read-only OR read-write key both read key-values. BLOCKED if disableLocalAuth == true (no key) or the read action is denied. This is the RBAC-BYPASS: it does NOT require a data-plane role. |
Narrative
{principal.name} can read secret key-value {keyvalue.name} in {store.name} via its access-key connection string (listKeys), bypassing data-plane RBAC.
Raw rule rules/derived/azure/appconfig.yaml
id: azure-appconfig-connstring-reads-all-kv
emits: CanReadSecret
description: A listKeys holder uses the access-key connection string to read every secret key-value, bypassing
data-plane RBAC.
match:
- - principal: null
- HasPermission
- store: null
- - store: null
- Contains
- dataplane: null
- - dataplane: null
- ExposesCredential
- connstring: null
- - connstring: null
- CredentialsFor
- dataplane: null
- - dataplane: null
- ContainsSecret
- keyvalue: null
where:
- node_class(?principal) == Identity
- node_class(?store) == ManagementService
- provider_type(?store) == 'Microsoft.AppConfiguration/configurationStores'
- node_class(?dataplane) == Data
- 'node_class(?connstring) == Secret # ?connstring is Secret-class (subtype Credential), so it is a valid
CredentialsFor source (source set [Secret, Credential, AccessKey, Token, Certificate]) and a valid ExposesCredential
target (Credential)'
- node_type(?connstring) == Credential
- ?principal has EFFECTIVE Microsoft.AppConfiguration/configurationStores/listKeys/action at the store
scope
- node_type(?keyvalue) in [Secret, ConnectionString, APIKey, Password]
- ?keyvalue is a plaintext key-value (not a keyvaultref)
emit:
source: ?principal
target: ?keyvalue
permissions:
- Microsoft.AppConfiguration/configurationStores/listKeys/action
conditions:
- iam_permission
- service_state
state_logic: 'ACTIVE when listKeys/action is effective AND disableLocalAuth == false (a valid connection
string is obtainable). A read-only OR read-write key both read key-values. BLOCKED if disableLocalAuth
== true (no key) or the read action is denied. This is the RBAC-BYPASS: it does NOT require a data-plane
role.'
confidence: '0.95'
derived_from:
- ?principal HasPermission ?store (listKeys/action)
- ?dataplane ExposesCredential ?connstring
- ?connstring CredentialsFor ?dataplane
- ?dataplane ContainsSecret ?keyvalue
false_positive_note: 'The connection string reads the WHOLE data plane, so emit once per secret-bearing
key-value. This is what makes a listKeys holder (Contributor/Owner) equivalent to a data reader for
secrets WITHOUT any App Configuration data role. BLOCKED when disableLocalAuth=true. KV-reference
key-values are still excluded (their value is a URI). Source is the Identity principal (schema: CanReadSecret
source is [Identity, Compute]), not the connection string. Depends on the ExposesCredential edge from
azure-appconfig-listkeys-exposes-connstring (fixpoint ordering); ?connstring is a Secret-class Credential
node, conformant as the CredentialsFor source.'
narrative: '{principal.name} can read secret key-value {keyvalue.name} in {store.name} via its access-key
connection string (listKeys), bypassing data-plane RBAC.'