Detection rules › Kusto
Power Apps - Multiple apps deleted
Identifies mass delete activity where multiple Power Apps are deleted, matching a predefined threshold of total apps deleted or app delete events across multiple Power Platform environments.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Impact | |
| Impact |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | any: Power Platform administrator activity (catch-all) |
Rule body
id: ed88638d-8627-4c20-ba08-67c13807a9b1
kind: Scheduled
name: Power Apps - Multiple apps deleted
description: Identifies mass delete activity where multiple Power Apps are deleted,
matching a predefined threshold of total apps deleted or app delete events across
multiple Power Platform environments.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: PowerPlatformAdmin
dataTypes:
- PowerPlatformAdminActivity
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1485
- T0826
query: |
let total_app_mass_delete_threshold = 25;
let cross_environment_delete_threshold = 10;
let query_frequency = 1h;
let app_delete_events = materialize(
PowerPlatformAdminActivity
| where TimeGenerated >= ago (query_frequency)
| where EventOriginalType == "DeletePowerApp"
| extend Properties = tostring(PropertyCollection)
| extend AppId = extract(@'"powerplatform.analytics.resource.power_app.id","Value":"([^"]+)"', 1, Properties)
| extend AppId = tolower(replace_string(AppId, '/providers/Microsoft.PowerApps/apps/', ''))
| extend EnvironmentId = extract(@'"powerplatform.analytics.resource.environment.id","Value":"([^"]+)"', 1, Properties)
);
app_delete_events
| summarize AppCount = count(), EnvCount = dcount(EnvironmentId) by ActorName
| where AppCount >= total_app_mass_delete_threshold or EnvCount >= cross_environment_delete_threshold
| join kind=inner app_delete_events on ActorName
| summarize
Apps = make_set(AppId, 1000),
Environments = make_set(EnvironmentId, 1000),
StartTime = min(TimeGenerated)
by AppCount, EnvCount, ActorName
| extend
PowerAppsEntityId = 27593,
DataverseId = 32780,
AccountName = tostring(split(ActorName, '@')[0]),
UPNSuffix = tostring(split(ActorName, '@')[1])
| project
StartTime,
ActorName,
AppCount,
Apps,
EnvCount,
Environments,
PowerAppsEntityId,
DataverseId,
AccountName,
UPNSuffix
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: PowerAppsEntityId
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
alertDetailsOverride:
alertDisplayNameFormat: Power Apps - mass deletion of apps
alertDescriptionFormat: '{{AppCount}} apps were deleted in {{EnvCount}} environments
by {{ActorName}} , exceeding the mass delete threshold.'
customDetails:
EnvironmentsImpacted: Environments
AppsDeleted: Apps
AppDeleteCount: AppCount
EnvironmentsCount: EnvCount
version: 3.2.0
Stages and Predicates
Parameters
let total_app_mass_delete_threshold = 25;
let cross_environment_delete_threshold = 10;
let query_frequency = 1h;
let app_delete_events is inlined into the numbered stages below.
Stages 1 to 4 define let app_delete_events (the rule's main pipeline source); stages 5 to 10 run on it.
Stage 1: source
PowerPlatformAdminActivity
Stage 2: where
| where TimeGenerated >= ago (query_frequency)
Stage 3: where
| where EventOriginalType == "DeletePowerApp"
Stage 4: extend (4 consecutive steps)
| extend Properties = tostring(PropertyCollection)
| extend AppId = extract(@'"powerplatform.analytics.resource.power_app.id","Value":"([^"]+)"', 1, Properties)
| extend AppId = tolower(replace_string(AppId, '/providers/Microsoft.PowerApps/apps/', ''))
| extend EnvironmentId = extract(@'"powerplatform.analytics.resource.environment.id","Value":"([^"]+)"', 1, Properties)
Stage 5: summarize
app_delete_events
| summarize AppCount = count(), EnvCount = dcount(EnvironmentId) by ActorName
Stage 6: where
| where AppCount >= total_app_mass_delete_threshold or EnvCount >= cross_environment_delete_threshold
Stage 7: join
| join kind=inner app_delete_events on ActorName
Stage 8: summarize
| summarize
Apps = make_set(AppId, 1000),
Environments = make_set(EnvironmentId, 1000),
StartTime = min(TimeGenerated)
by AppCount, EnvCount, ActorName
Stage 9: extend
| extend
PowerAppsEntityId = 27593,
DataverseId = 32780,
AccountName = tostring(split(ActorName, '@')[0]),
UPNSuffix = tostring(split(ActorName, '@')[1])
Stage 10: project
| project
StartTime,
ActorName,
AppCount,
Apps,
EnvCount,
Environments,
PowerAppsEntityId,
DataverseId,
AccountName,
UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AppCount | ge |
| field:"AppCount" kind:ge value:"25" |
EnvCount | ge |
| field:"EnvCount" kind:ge value:"10" |
EventOriginalType | eq |
| field:"EventOriginalType" kind:eq value:"DeletePowerApp" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
ActorName | project |
AppCount | project |
Apps | project |
DataverseId | project |
EnvCount | project |
Environments | project |
PowerAppsEntityId | project |
StartTime | project |
UPNSuffix | project |