↳ GitHub sourceAnalytics ruleMedium
Power Apps - Bulk sharing of Power Apps to newly created guest users
Description
Identifies unusual bulk sharing, based on a predefined threshold in the query, of Power Apps to newly created Microsoft Entra guest users.
- Rule type
- Scheduled
- Version
- 3.2.0
- Declared status
- Available
- Query frequency
- 1h
- Query period
- 14d
- Trigger
- gt 0
Declared MITRE coverage
Declared sources
Metadata from the source file. No dependencies inferred from KQL.
Connectors
Data types
KQL query
Original query, unchanged.
////////////
// threshold = If the number of unique accounts that a power app is shared with is greater than
// threshold than it'll trigger an alert. A threshold of 5 is good to start with.
// However, if this is giving too many false positives, please adjust the threshold.
////////////
let threshold = 5;
////////////
// Please replace the allowed_domains with a list of domains of your partners/sibling orgs
// with whom you generally share power apps with. This will allow us to filter
// legitimate bulk sharing attempts. Avoid using domains such as gmail, outlook, etc.
///////////
let allowed_domains = pack_array("contoso.com");
let query_frequency = 1h;
let query_lookback = 14d;
PowerPlatformAdminActivity
| where TimeGenerated >= ago(query_frequency)
| where EventOriginalType == "PowerAppPermissionEdited"
| 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 TargetPrincipalId = extract(@'"targetuser.id","Value":"([^"]+)"', 1, Properties)
| join kind=leftouter (
AuditLogs
| where ActivityDateTime >= ago(query_lookback)
| where SourceSystem =~ "Azure AD" and OperationName == "Invite external user"
| where Result =~ "success"
| extend InvitedOrgEmail = tostring(parse_json(AdditionalDetails[5])['value'])
| extend InvitedOrgDomain = tostring(split(InvitedOrgEmail, "@")[1])
| where not(InvitedOrgDomain has_any(allowed_domains))
| extend
InvitedById = tostring(parse_json(InitiatedBy)['user']['id']),
InvitedByUPN = tostring(parse_json(InitiatedBy)['user']['userPrincipalName']),
InvitedEmail = tostring(parse_json(TargetResources[0])['userPrincipalName']),
InvitedId = tostring(parse_json(TargetResources[0])['id'])
| summarize by InvitedById, InvitedByUPN, InvitedEmail, InvitedId, InvitedOrgDomain)
on $left.TargetPrincipalId == $right.InvitedId
| where isnotempty(InvitedId)
| summarize
StartTime = min(TimeGenerated),
EndTime = max(TimeGenerated),
TargetedUsersCount=dcount(TargetPrincipalId),
TargetedObjectIds = make_set(TargetPrincipalId, 1000),
InvitedDomains = make_set(InvitedOrgDomain, 1000),
InvitedEmailAddresses = make_set(InvitedEmail, 1000)
by AppId, InvitedById, InvitedByUPN
| extend
PowerAppsEntityId = 27593,
AccountName = tostring(split(InvitedByUPN, '@')[0]),
UPNSuffix = tostring(split(InvitedByUPN, '@')[1])
| project
StartTime,
EndTime,
InvitedByUPN,
InvitedById,
InvitedDomains,
InvitedEmailAddresses,
TargetedUsersCount,
TargetedObjectIds,
AppId,
PowerAppsEntityId,
AccountName,
UPNSuffix
Declared entities
Related content
Links established from declared identifiers and solution manifests.
Source provenance
GitHubDisplayed values come from files in Azure/Azure-Sentinel. They describe the published template, not your workspace configuration.
- Commit
9800e51↗- Source identifier
943acfa0-9285-4eb0-a9c0-42e36177ef19
GSTEP / CATALOG TRACKING
Added to catalog : 16 Sept 2026 · 05:49 UTC
Last change observed : 16 Sept 2026 · 05:49 UTC