Skip to content

Commit ed496f6

Browse files
jonnybottlesclaude
andcommitted
Add Entra Agent ID and Copilot/AI activity collectors
All-GA "agentic" pilot for Hawk: - Get-HawkTenantAgentIdentity: inventory Microsoft Entra Agent ID identities via Microsoft Graph; flag orphaned, secret-based, or disabled agents (recent creation noted as supporting context). - Get-HawkTenantAIInteraction / Get-HawkUserAIInteraction: collect Copilot and AI application/agent activity from the Unified Audit Log; flag jailbreak attempts, third-party AI apps, and sensitivity-labeled resource access. - Get-HawkAllGraphResult: Invoke-MgGraphRequest paging helper (v1.0 + beta). - Test-SuspiciousAgentIdentity: agent identity flagging helper. Wired into Start-HawkTenant/UserInvestigation, registered in the manifest (bumped to 4.1.0), with Pester unit tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a44536f commit ed496f6

11 files changed

Lines changed: 775 additions & 3 deletions

Hawk/Hawk.psd1

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
@{
1+
@{
22
# Script module or binary module file associated with this manifest
33
RootModule = 'Hawk.psm1'
44

55
# Version number of this module.
6-
ModuleVersion = '4.0'
6+
ModuleVersion = '4.1.0'
77

88
# ID used to uniquely identify this module
99
GUID = '1f6b6b91-79c4-4edf-83a1-66d2dc8c3d85'
@@ -90,7 +90,10 @@
9090
'Get-HawkUserEntraIDSignInLog',
9191
'Get-HawkTenantEntraIDAuditLog',
9292
'Get-HawkTenantRiskyUsers',
93-
'Get-HawkTenantRiskDetections'
93+
'Get-HawkTenantRiskDetections',
94+
'Get-HawkTenantAgentIdentity',
95+
'Get-HawkTenantAIInteraction',
96+
'Get-HawkUserAIInteraction'
9497
# Cmdlets to export from this module
9598
# CmdletsToExport = ''
9699

Hawk/changelog.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,12 @@
108108
- Added log pull of user SharePoint Search activity to the User Investigation (Get-HawkUserSharePointSearchQuery)
109109
- Added telemetry discloser on Readme and updated license
110110
- Added AppInsight GUID
111+
112+
## 4.1.0 (2026-06-10)
113+
114+
- Added Get-HawkTenantAgentIdentity, which inventories Microsoft Entra Agent ID (AI agent) identities via Microsoft Graph and flags orphaned, secret-based, or disabled agents (recent creation is noted as supporting context, not a standalone flag).
115+
- Added Get-HawkTenantAIInteraction, which collects tenant-wide Microsoft Copilot and AI application / agent interactions from the Unified Audit Log and flags jailbreak attempts, third-party AI app use, and sensitivity-labeled resource access.
116+
- Added Get-HawkUserAIInteraction, which collects per-user Copilot and AI application / agent interactions from the Unified Audit Log; added to Start-HawkUserInvestigation.
117+
- Added Get-HawkTenantAgentIdentity and Get-HawkTenantAIInteraction to Start-HawkTenantInvestigation.
118+
- Added internal helper Get-HawkAllGraphResult, an Invoke-MgGraphRequest paging wrapper (with include-unknown-enum-members) that reaches the agentIdentity Graph cast and /beta agent log endpoints and degrades gracefully when unavailable.
119+
- Added internal helper Test-SuspiciousAgentIdentity, which detects agent identities warranting investigation.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
Function Get-HawkTenantAIInteraction {
2+
<#
3+
.SYNOPSIS
4+
Collects Microsoft Copilot and AI application / agent interaction events from the Unified Audit Log.
5+
6+
.DESCRIPTION
7+
Searches the Microsoft 365 Unified Audit Log for Copilot and AI application activity across
8+
the tenant, including interactions with Microsoft Copilot, Copilot Studio agents, connected
9+
(registered) AI applications, third-party AI applications, and Teams Copilot. These events
10+
record who interacted with which agent or Copilot, in which host application, and which
11+
resources were accessed during the interaction.
12+
13+
The function aggregates the AI-related record types (CopilotInteraction,
14+
ConnectedAIAppInteraction, AIAppInteraction, and TeamCopilotInteraction), parses the audit
15+
data, and flags interactions that warrant review: detected jailbreak attempts, interactions
16+
with third-party AI applications (a data egress concern), and interactions that accessed
17+
sensitivity-labeled resources.
18+
19+
.OUTPUTS
20+
File: AI_Interactions.csv / .json
21+
Path: \Tenant
22+
Description: All Copilot and AI application interaction events found in the search window.
23+
24+
File: _Investigate_AI_Interactions.csv / .json
25+
Path: \Tenant
26+
Description: Subset of interactions flagged for investigation.
27+
28+
.EXAMPLE
29+
Get-HawkTenantAIInteraction
30+
31+
Searches the Unified Audit Log for Copilot and AI application activity within the configured
32+
time window and writes the results to the Tenant output folder.
33+
34+
.NOTES
35+
Author: Hawk Forensics
36+
37+
.LINK
38+
https://learn.microsoft.com/en-us/purview/audit-copilot
39+
#>
40+
[CmdletBinding()]
41+
param()
42+
43+
BEGIN {
44+
# Check if Hawk object exists and is fully initialized
45+
if (Test-HawkGlobalObject) {
46+
Initialize-HawkGlobalObject
47+
}
48+
49+
Out-LogFile "Initiating collection of Copilot and AI application activity from the UAL." -Action
50+
51+
Test-EXOConnection
52+
Send-AIEvent -Event "CmdRun"
53+
}
54+
55+
PROCESS {
56+
# AI / agent interaction record types in the Unified Audit Log.
57+
[array]$RecordTypes = "CopilotInteraction", "ConnectedAIAppInteraction", "AIAppInteraction", "TeamCopilotInteraction"
58+
59+
[array]$AIEvents = $null
60+
foreach ($Type in $RecordTypes) {
61+
Out-LogFile ("Searching Unified Audit Log for AI interaction records of type: " + $Type) -Action
62+
try {
63+
$AIEvents += Get-AllUnifiedAuditLogEntry -UnifiedSearch ("Search-UnifiedAuditLog -RecordType " + $Type)
64+
}
65+
catch {
66+
Out-LogFile ("Unable to search Unified Audit Log for record type " + $Type + ": " + $_.Exception.Message) -isError
67+
}
68+
}
69+
70+
if ($null -eq $AIEvents -or $AIEvents.Count -eq 0) {
71+
Out-LogFile "No Copilot or AI application activity found in the search time frame." -Information
72+
return
73+
}
74+
75+
Out-LogFile ("Found " + $AIEvents.Count + " Copilot/AI interaction records. Parsing audit data.") -Information
76+
77+
$parsed = @()
78+
foreach ($event in $AIEvents) {
79+
try {
80+
$data = $event.AuditData | ConvertFrom-Json
81+
}
82+
catch {
83+
continue
84+
}
85+
86+
# Summarize accessed resources and any sensitivity labels.
87+
$accessedCount = 0
88+
$sensitivityLabels = $null
89+
if ($data.AccessedResources) {
90+
$accessedCount = ($data.AccessedResources | Measure-Object).Count
91+
$sensitivityLabels = (
92+
$data.AccessedResources | ForEach-Object { $_.SensitivityLabelId } | Where-Object { $_ }
93+
) -join ';'
94+
}
95+
96+
# Detect jailbreak attempts recorded on the prompt messages.
97+
$jailbreak = $false
98+
if ($data.Messages) {
99+
if ($data.Messages | Where-Object { $_.JailbreakDetected -eq $true }) {
100+
$jailbreak = $true
101+
}
102+
}
103+
104+
$parsed += [PSCustomObject]@{
105+
CreationTime = $data.CreationTime
106+
RecordType = $event.RecordType
107+
Operation = $data.Operation
108+
Workload = $data.Workload
109+
UserId = $data.UserId
110+
AppIdentity = $data.AppIdentity
111+
AppHost = $data.AppHost
112+
AgentName = $data.AgentName
113+
AgentVersion = $data.AgentVersion
114+
AccessedResources = $accessedCount
115+
SensitivityLabels = $sensitivityLabels
116+
JailbreakDetected = $jailbreak
117+
ClientIP = $data.ClientIP
118+
}
119+
}
120+
121+
if ($parsed.Count -gt 0) {
122+
$parsed | Out-MultipleFileType -FilePrefix "AI_Interactions" -csv -json
123+
}
124+
125+
# Flag interactions that warrant review.
126+
$flagged = $parsed | Where-Object {
127+
($_.JailbreakDetected -eq $true) -or
128+
($_.RecordType -eq 'ConnectedAIAppInteraction') -or
129+
($_.RecordType -eq 'AIAppInteraction') -or
130+
($_.Workload -eq 'ConnectedAIApp') -or
131+
($_.Workload -eq 'AIApp') -or
132+
(-not [string]::IsNullOrEmpty($_.SensitivityLabels))
133+
}
134+
135+
if ($flagged) {
136+
$flaggedCount = ($flagged | Measure-Object).Count
137+
Out-LogFile ("Found " + $flaggedCount + " AI interactions that warrant review (jailbreak attempts, third-party AI apps, or sensitivity-labeled resource access).") -Notice
138+
Out-LogFile "Please review _Investigate_AI_Interactions.csv for details." -Notice
139+
$flagged | Out-MultipleFileType -FilePrefix "_Investigate_AI_Interactions" -csv -json -Notice
140+
}
141+
}
142+
143+
END {
144+
Out-LogFile "Completed collection of Copilot and AI application activity from the UAL." -Information
145+
}
146+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
Function Get-HawkTenantAgentIdentity {
2+
<#
3+
.SYNOPSIS
4+
Collects Microsoft Entra Agent ID (AI agent) identities and flags those warranting review.
5+
6+
.DESCRIPTION
7+
Enumerates Microsoft Entra Agent ID agent identities in the tenant using the Microsoft Graph
8+
agentIdentity service principal cast (GET /servicePrincipals/microsoft.graph.agentIdentity).
9+
AI agents - such as those created by Microsoft Copilot Studio and managed through Microsoft
10+
Agent 365 - are represented as a dedicated service principal subtype in Microsoft Entra, so
11+
this function gives responders an inventory of the agent identity attack surface alongside
12+
Hawk's existing application and service principal collection.
13+
14+
For each agent identity the function records core directory properties, a credential
15+
summary, and resolved owners, then flags agents that warrant review (no owner,
16+
password-secret credentials, creation inside the investigation window, or
17+
disabled-but-present) using the Test-SuspiciousAgentIdentity helper.
18+
19+
The function degrades gracefully: if the agent identity endpoint is not available in the
20+
tenant, or the AgentIdentity.Read.All permission has not been consented, it logs an
21+
informational message and returns without error.
22+
23+
.OUTPUTS
24+
File: AgentIdentities.csv / .json
25+
Path: \Tenant
26+
Description: Inventory of all Entra Agent ID agent identities and their properties.
27+
28+
File: _Investigate_AgentIdentities.csv / .json
29+
Path: \Tenant
30+
Description: Subset of agent identities flagged for investigation, with reasons.
31+
32+
.EXAMPLE
33+
Get-HawkTenantAgentIdentity
34+
35+
Enumerates all Entra Agent ID agent identities in the tenant and writes the inventory and any
36+
flagged agents to the Tenant output folder.
37+
38+
.NOTES
39+
Requires the following Microsoft Graph permission:
40+
- AgentIdentity.Read.All
41+
42+
.LINK
43+
https://learn.microsoft.com/en-us/graph/api/agentidentity-list
44+
#>
45+
[CmdletBinding()]
46+
param()
47+
48+
BEGIN {
49+
# Check if Hawk object exists and is fully initialized
50+
if (Test-HawkGlobalObject) {
51+
Initialize-HawkGlobalObject
52+
}
53+
54+
# Create Tenant folder path if it doesn't exist
55+
$tenantPath = Join-Path -Path $Hawk.FilePath -ChildPath "Tenant"
56+
if (-not (Test-Path -Path $tenantPath)) {
57+
New-Item -Path $tenantPath -ItemType Directory -Force | Out-Null
58+
}
59+
60+
Out-LogFile "Initiating collection of Entra Agent ID identities from Microsoft Graph." -Action
61+
62+
Test-GraphConnection
63+
Send-AIEvent -Event "CmdRun"
64+
}
65+
66+
PROCESS {
67+
try {
68+
# Enumerate agent identities (a service principal subtype). GA on the v1.0 endpoint.
69+
$agentUri = "https://graph.microsoft.com/v1.0/servicePrincipals/microsoft.graph.agentIdentity"
70+
$agents = Get-HawkAllGraphResult -Uri $agentUri
71+
72+
if ($null -eq $agents) {
73+
Out-LogFile "Entra Agent ID identities are not available in this tenant, or the AgentIdentity.Read.All permission has not been consented. Skipping." -Information
74+
return
75+
}
76+
77+
if ($agents.Count -eq 0) {
78+
Out-LogFile "No Entra Agent ID identities found in the tenant." -Information
79+
return
80+
}
81+
82+
Out-LogFile ("Found " + $agents.Count + " agent identities. Processing details.") -Information
83+
84+
$agentResults = @()
85+
foreach ($agent in $agents) {
86+
87+
# Resolve owners so we can spot orphaned / shadow agents.
88+
$ownerNames = $null
89+
if ($agent.id) {
90+
$owners = Get-HawkAllGraphResult -Uri ("https://graph.microsoft.com/v1.0/servicePrincipals/" + $agent.id + "/owners")
91+
if ($owners) {
92+
$ownerNames = (
93+
$owners | ForEach-Object {
94+
if ($_.userPrincipalName) { $_.userPrincipalName } else { $_.displayName }
95+
}
96+
) -join ';'
97+
}
98+
}
99+
100+
$passwordCount = ($agent.passwordCredentials | Measure-Object).Count
101+
$keyCount = ($agent.keyCredentials | Measure-Object).Count
102+
103+
$agentResults += [PSCustomObject]@{
104+
DisplayName = $agent.displayName
105+
Id = $agent.id
106+
AppId = $agent.appId
107+
ServicePrincipalType = $agent.servicePrincipalType
108+
AgentType = $agent.agentType
109+
BlueprintId = $agent.blueprintId
110+
AccountEnabled = $agent.accountEnabled
111+
CreatedDateTime = $agent.createdDateTime
112+
Owners = $ownerNames
113+
HasPasswordSecret = ($passwordCount -gt 0)
114+
HasKeyCredential = ($keyCount -gt 0)
115+
PasswordCredentialCount = $passwordCount
116+
KeyCredentialCount = $keyCount
117+
}
118+
}
119+
120+
# Write the full inventory
121+
$agentResults | Out-MultipleFileType -FilePrefix "AgentIdentities" -csv -json
122+
123+
# Flag agents that warrant review
124+
$flagged = @()
125+
foreach ($record in $agentResults) {
126+
$reasons = @()
127+
if (Test-SuspiciousAgentIdentity -Agent $record -Reasons ([ref]$reasons)) {
128+
$flagged += ($record | Select-Object -Property *, @{Name = 'InvestigateReasons'; Expression = { $reasons -join '; ' } })
129+
}
130+
}
131+
132+
if ($flagged.Count -gt 0) {
133+
Out-LogFile ("Found " + $flagged.Count + " agent identities that warrant review (orphaned, secret-based, newly created, or disabled).") -Notice
134+
Out-LogFile "Please review _Investigate_AgentIdentities.csv to ensure these agent identities are expected." -Notice
135+
$flagged | Out-MultipleFileType -FilePrefix "_Investigate_AgentIdentities" -csv -json -Notice
136+
}
137+
}
138+
catch {
139+
Out-LogFile "Error collecting Entra Agent ID identities: $($_.Exception.Message)" -isError
140+
Write-Error -ErrorRecord $_ -ErrorAction Continue
141+
}
142+
}
143+
144+
END {
145+
Out-LogFile "Completed collection of Entra Agent ID identities from Microsoft Graph." -Information
146+
}
147+
}

Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,18 @@
253253
Out-LogFile "Running Get-HawkTenantAppAndSPNCredentialDetail." -action
254254
Get-HawkTenantAppAndSPNCredentialDetail
255255
}
256+
257+
if ($PSCmdlet.ShouldProcess("Agent Identities", "Get Entra Agent ID identities")) {
258+
Write-Output ""
259+
Out-LogFile "Running Get-HawkTenantAgentIdentity." -action
260+
Get-HawkTenantAgentIdentity
261+
}
262+
263+
if ($PSCmdlet.ShouldProcess("AI Interactions", "Get Copilot and AI application activity")) {
264+
Write-Output ""
265+
Out-LogFile "Running Get-HawkTenantAIInteraction." -action
266+
Get-HawkTenantAIInteraction
267+
}
256268

257269
if ($PSCmdlet.ShouldProcess("Entra ID Users", "Get Entra ID user list")) {
258270
Write-Output ""

0 commit comments

Comments
 (0)