Workiq Copilot
github/awesome-copilot
Guides the Copilot CLI on how to use the WorkIQ CLI/MCP server to query Microsoft 365 Copilot data (emails, meetings, docs, Teams, people) for live context, summaries, and recommendations.
Audit or report on AI agent security posture across Copilot Studio, Microsoft 365 Copilot, Microsoft Foundry, and third-party agents.
$ npx skills add SCStelz/security-investigator --skill ai-agent-posture -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install SCStelz/security-investigator ai-agent-posture --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.github/skills/ai-agent-posture .claude/skills/ai-agent-posture && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "ai-agent-posture" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/ai-agent-posture into .claude/skills/ai-agent-posture/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ai-agent-posture", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/SCStelz/security-investigator/tree/main/.github/skills/ai-agent-postureType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add SCStelz/security-investigator --skill ai-agent-posture -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install SCStelz/security-investigator ai-agent-posture --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.github/skills/ai-agent-posture .agents/skills/ai-agent-posture && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "ai-agent-posture" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/ai-agent-posture into .agents/skills/ai-agent-posture/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ai-agent-posture", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add SCStelz/security-investigator --skill ai-agent-posture -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install SCStelz/security-investigator ai-agent-posture --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.github/skills/ai-agent-posture .cursor/skills/ai-agent-posture && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "ai-agent-posture" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/ai-agent-posture into .cursor/skills/ai-agent-posture/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ai-agent-posture", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/SCStelz/security-investigator.git --path .github/skills/ai-agent-posture--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add SCStelz/security-investigator --skill ai-agent-posture -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install SCStelz/security-investigator ai-agent-posture --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.github/skills/ai-agent-posture .gemini/skills/ai-agent-posture && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "ai-agent-posture" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/ai-agent-posture into .gemini/skills/ai-agent-posture/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ai-agent-posture", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install SCStelz/security-investigator ai-agent-postureInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add SCStelz/security-investigator --skill ai-agent-posture -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .github/skills && cp -r skills-src/.github/skills/ai-agent-posture .github/skills/ai-agent-posture && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "ai-agent-posture" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/ai-agent-posture into .github/skills/ai-agent-posture/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ai-agent-posture", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add SCStelz/security-investigator --skill ai-agent-posture -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install SCStelz/security-investigator ai-agent-posture --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.github/skills/ai-agent-posture .opencode/skills/ai-agent-posture && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "ai-agent-posture" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/ai-agent-posture into .opencode/skills/ai-agent-posture/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ai-agent-posture", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
ai-agent-postureAudit or report on AI agent security posture across Copilot Studio, Microsoft 365 Copilot, Microsoft Foundry, and third-party agents.
AI Agent Posture is an agent skill from SCStelz/security-investigator. Audit or report on AI agent security posture across Copilot Studio, Microsoft 365 Copilot, Microsoft Foundry, and third-party agents. Triggers on "AI agent posture", "agent security audit", "Copilot Studio agents", "agent inventory", "broadly accessible agents", "agent tools", "MCP tools on agents", "XPIA risk", "agent sprawl", "agent governance", "agent identity", "dormant agents", "ownerless agents", or investigating agent configs, access posture, tool permissions, credential exposure, or Entra agent…
Its SKILL.md is about 21k tokens, which your agent loads only when the skill is triggered. The skill folder holds 1 other file (for example `svg-widgets.yaml`).
It sits in Documents & Office, covering Prompt injection and agent security, MCP servers and Cloud office suites. It works with Microsoft Copilot Studio and Microsoft 365. The repository describes itself as: Automated security investigation tool using Microsoft MCP Servers, GitHub Copilot, Python Modules and custom copilot-instructions. The licence is MIT.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 51e1385. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are kql and markdown).
From the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
learn.microsoft.commicrosoft.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
AI Agent Posture loads about 21k tokens when it runs. Until then it costs about 259 tokens; SKILL.md has 6,593 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from SCStelz/security-investigator at commit 51e1385, republished under its MIT licence (© SCStelz). 6,593 words, ~20,651 tokens.
.claude/skills/ai-agent-posture/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.This skill audits the security posture of AI agents (Copilot Studio, Microsoft 365 Copilot / Agent Builder, Microsoft Foundry, and third-party platforms) across your organization using the AgentsInfo table in Microsoft Defender XDR Advanced Hunting.
🔄 Table migration (AIAgentsInfo → AgentsInfo): This skill was migrated from the deprecated
AIAgentsInfotable to the unified multi-platformAgentsInfotable.AIAgentsInforemains queryable until July 1, 2026, but it is Copilot Studio-only and uses a different schema. All queries in this skill targetAgentsInfo. The new table is a different data model, not a rename — see Table Schema Reference and Known Pitfalls for the differences that shaped these queries.
AI agents are autonomous or semi-autonomous applications that can access organizational data, send emails, call external APIs, and use MCP tools. Misconfigured agents — missing authentication, overly broad access, AI-controlled email sending, hard-coded credentials — represent a growing attack surface. This skill systematically evaluates that surface.
What this skill covers:
| Domain | Key Questions Answered |
|---|---|
| 🔍 Agent Inventory | How many agents exist? What's their status, platform, environment? |
| 🔐 Access Posture | Which agents are broadly accessible (allowForAllUsers)? How are agents shared (appType: lob/shared)? |
| 🛠️ Tools & MCP | Which agents have MCP tools? What operations can they perform? |
| 📚 Knowledge Sources | What data sources are agents connected to? |
| 📧 XPIA Email Risk | Which agents can send email (data exfil precondition)? |
| 🔑 Credential Exposure | Are credentials hard-coded in agent instructions or connector metadata? |
| 🌐 External Endpoint Risk | What external hosts do agent connectors reach? Any insecure schemes or non-standard ports? |
| 👥 Creator Governance | Who creates agents? Is there naming hygiene? Abandoned agents? |
Data source: AgentsInfo table (Advanced Hunting) — currently in Preview.
References:
MANDATORY: When generating reports, copy URLs verbatim from this registry. NEVER construct, guess, or paraphrase a URL. If a URL is not in this registry, omit the hyperlink entirely and use plain text.
| Label | Canonical URL |
|---|---|
BLOG_RUNTIME_RISK | https://www.microsoft.com/en-us/security/blog/2026/01/23/runtime-risk-realtime-defense-securing-ai-agents/ |
BLOG_AGENT_365 | https://www.microsoft.com/en-us/microsoft-365/blog/2025/11/18/microsoft-agent-365-the-control-plane-for-ai-agents/ |
DOCS_AGENTSINFO | https://learn.microsoft.com/en-us/defender-xdr/advanced-hunting-agentsinfo-table |
DOCS_AGENT_PROTECTION | https://learn.microsoft.com/en-us/defender-cloud-apps/ai-agent-protection |
DOCS_RUNTIME_PROTECTION | https://learn.microsoft.com/en-us/defender-cloud-apps/real-time-agent-protection-during-runtime |
Usage in reports: When referencing attack scenarios, link to BLOG_RUNTIME_RISK. When referencing Agent 365 governance, link to BLOG_AGENT_365. When referencing runtime protection, link to DOCS_RUNTIME_PROTECTION.
Microsoft Defender Security Research has identified that AI agents represent a fundamentally new attack surface where the agent's capabilities are effectively equivalent to code execution. When a tool is invoked, it can read/write data, send emails, update records, or trigger workflows — and an attacker who can influence the agent's plan can indirectly cause the execution of unintended operations within the agent's capability sandbox.
The core risk: the agent's orchestrator depends on natural language input to determine which tools to use and how to use them. This creates exposure to prompt injection and reprogramming failures, where malicious prompts, embedded instructions, or crafted documents can manipulate the decision-making process.
This skill's queries map directly to three attack scenarios documented by Microsoft:
| Element | Detail |
|---|---|
| Vector | Crafted email sent to an agent-monitored mailbox (event trigger) |
| Mechanism | Email contains hidden instructions telling the agent to search knowledge base for sensitive data and exfiltrate via email to attacker |
| Preconditions | Agent can send email (email connector) + has an event/email trigger + a knowledge source |
| Detection | Q5 (XPIA Email Risk) detects email-capable agents via connector operations; Q7 (Knowledge Sources) identifies data exposure |
| Skill Signal | Agents with an email-send operation (e.g., Office 365 Outlook Send an email (V2)) + knowledge sources, especially if broadly accessible (allowForAllUsers == "true") = highest risk |
| Element | Detail |
|---|---|
| Vector | Malicious insider edits a SharePoint document with crafted instructions |
| Mechanism | Agent processing the document is tricked into reading a sensitive file on a different SharePoint site (that the agent has access to but the attacker doesn't) and emailing contents to attacker-controlled domain |
| Preconditions | Agent has a knowledge/data source + an email-send connector operation |
| Detection | Q5 (XPIA) + Q7 (Knowledge Sources) identifies the attack surface |
| Skill Signal | A declared data source + an email-send operation (e.g., Send an email (V2)) on the same agent = classic XPIA vector |
| Element | Detail |
|---|---|
| Vector | Attacker interacts with publicly accessible chatbot (no authentication required) |
| Mechanism | Series of crafted prompts to probe and enumerate the agent's tools and knowledge sources, then exploit them to extract sensitive data |
| Preconditions | Agent is broadly accessible (allowForAllUsers == "true", e.g., shared tenant-wide or website embed) |
| Detection | Q4 (Broadly-Accessible Agents) identifies exposed agents; cross-reference with Q7 (knowledge sources with customer data) |
| Skill Signal | allowForAllUsers == "true" + knowledge sources containing sensitive data = reconnaissance target |
⚠️ Authentication-type telemetry gap: The deprecated
AIAgentsInfotable exposedUserAuthenticationType(None/Integrated/Custom), which let this skill directly flag unauthenticated agents. The newAgentsInfotable has no populated authentication-type column in current telemetry (ToolsAuthenticationTypeis empty). The closest available exposure signal isRawAgentInfo.allowForAllUsers == "true"(broadly accessible to all tenant users). This is a proxy, not an equivalent — it measures broad reach, not absence of authentication. Treat broadly-accessible agents as the highest-exposure cohort and recommend Entra-based access policies (Agent 365) to close the gap.
Microsoft Defender provides webhook-based runtime inspection for Copilot Studio agents. Before every tool, topic, or knowledge action is executed, the generative orchestrator sends a webhook to Defender containing the planned invocation context. Defender analyzes intent and destination in real time and can allow or block the action before execution.
This is the primary runtime defense against all three scenarios above. When reviewing posture findings from this skill, always recommend enabling Defender Runtime Protection for agents flagged as high-risk. See Real-time agent protection during runtime.
Microsoft Agent 365 is the enterprise control plane for AI agents — the platform-level answer to the governance gaps this skill detects. It provides five capabilities that directly map to this skill's risk dimensions:
| Agent 365 Capability | What It Does | Skill Dimensions Addressed |
|---|---|---|
| 1. Registry | Single source of truth for all agents (Entra agent ID). IT can quarantine unsanctioned agents and detect shadow agents. Agent Store for governed discovery. | Agent Inventory (Q1), Creator Governance (Q10), Agent Sprawl (Q11) |
| 2. Access Control | Unique agent IDs via Entra. Agent Policy Templates enforce security from day one. Adaptive, risk-based access policies. Least-privilege enforcement. | Broadly-Accessible Agents (Q4), Access Posture (Q3) |
| 3. Visualization | Unified dashboard mapping agents ↔ users ↔ resources. Role-based reporting. Compliance logging, e-discovery, and audit trail. | MCP Tool Exposure (Q6), Knowledge Sources (Q7), Creator Governance (Q10) |
| 4. Interoperability | Agents access Work IQ (org data, relationships, context). Works across Copilot Studio, Microsoft Foundry, Agent Framework, Agent 365 SDK, and partner platforms. | Knowledge Source Risk (Q7), Tools Inventory (Q12) |
| 5. Security | Defense-in-depth via Microsoft Defender (posture + threat detection + runtime protection), Entra (real-time blocking), and Purview (data exposure risk, sensitive data leak prevention, compliance). | XPIA Email Risk (Q5), Credential Hygiene (Q8), External Endpoint Risk (Q9) |
How to reference Agent 365 in reports: When this skill identifies governance gaps (sprawl, missing authentication, uncontrolled tool access), recommend Agent 365 as the strategic platform to address them. Specific mappings:
ALWAYS use RunAdvancedHuntingQuery — The AgentsInfo table is an Advanced Hunting table. It is NOT available in Sentinel Data Lake (query_lake). All queries in this skill MUST use RunAdvancedHuntingQuery.
ALWAYS deduplicate agents with arg_max — The table contains multiple records per agent (state snapshots over time). Every query that analyzes current agent state MUST use | summarize arg_max(Timestamp, *) by AgentId to get the latest record per agent. Note AgentId is a guid.
ALWAYS exclude deleted agents (unless specifically auditing deletions) — Add | where LifecycleStatus != "Deleted" after deduplication. LifecycleStatus is blank for active agents and only set to Deleted for removed ones, so this filter keeps active agents.
ASK the user for output format before generating the report:
reports/ai-agent-posture/)⛔ MANDATORY: Evidence-based analysis only — Report ONLY what query results show. Use the explicit absence pattern (✅ No [finding] detected) when queries return 0 results. Never guess or assume.
🔴 The rich agent detail lives in RawAgentInfo (dynamic), not in flat columns — Governance signals (creatorId, allowForAllUsers, appType, scope) and deep tool/connector detail (declarativeCopilotMetadata) are nested inside the RawAgentInfo dynamic column. The normalized columns (DeclaredTools, McpServers, DeclaredDataSources) are sparse and flat. Parse RawAgentInfo with mv-expand/dot-notation — never assume a flat column holds the value. See Known Pitfalls.
Run queries in parallel batches where possible — Phase 1 queries (Q1–Q3) are independent and can run in parallel. Phase 2 queries (Q4–Q9) are independent and can run in parallel. Phase 3 (Q10–Q12) can run in parallel.
Time tracking — Report elapsed time after each phase completion.
🔴 DATA PLANE AWARENESS — This skill's baseline is AgentsInfo (Advanced Hunting), available to every Defender XDR tenant — Phases 1–5 depend only on it. When Microsoft Sentinel Data Lake is present, an optional enrichment tier (the EntraAgent* identity tables, Phase 6) adds lifecycle state, blueprint governance, agentic-user accounts, and clean dormant-agent detection. Probe for the enrichment plane before using it and skip gracefully if absent — never block or degrade the baseline assessment for a Defender-only tenant, and never change the /100 score based on enrichment availability. See Data Plane Detection & Selection.
Agent posture spans three planes. Only the baseline is required; detect and use the others when present.
| Plane | Table(s) | Query tool | Availability | Role |
|---|---|---|---|---|
| Baseline — config posture | AgentsInfo | RunAdvancedHuntingQuery | All Defender XDR tenants | Inventory, tools/MCP, knowledge, owner/creator, Entra linkage. Drives Phases 1–5 and the /100 score. |
| Enrichment — Entra identity | EntraAgentIdentities, EntraAgentIdentityBlueprints, EntraAgentUsers | mcp_sentinel-data_query_lake (workspaceId:"default") | Sentinel Data Lake only | Lifecycle/compliance, blueprint app-reg governance, agentic-user accounts, duplicate/dormant identities (Phase 6). |
| Runtime | CopilotActivity (AH) / UnifiedAgentObservability (Data Lake) | AH / query_lake | AH everywhere; UAO Data-Lake-only | Which flagged agents are actually active (Phase 5). |
Enrichment-plane probe (run once, before Phase 6):
// mcp_sentinel-data_query_lake, workspaceId: "default"
EntraAgentIdentities | where TimeGenerated > ago(7d) | summarize Rows = count()SemanticError: Failed to resolve table or 0 rows → Defender-only tenant, skip Phase 6 and state the gap in the report banner.🔴 MANDATORY report banner: State which planes were available, e.g. "Baseline: AgentsInfo ✅ · Entra identity enrichment: ✅ (14 identities) · Runtime: CopilotActivity ✅" or "…Entra identity enrichment: ❌ not available (no Sentinel Data Lake) — lifecycle, blueprint governance, agentic-user, and identity-based dormancy findings were not assessed."
Full enrichment query library:
queries/cloud/entra_agent_identity.md— Queries 1–6, the validated cross-plane join map, and the Defender-only fallback. Phase 6 references it rather than duplicating the KQL.
The AgentsInfo table (Preview) contains configuration snapshots of AI agents across Copilot Studio, Microsoft 365 Copilot (Agent Builder), Microsoft Foundry, and third-party platforms. The schema below reflects the live table (which differs from the published docs in several places — column casing, types, and which columns are actually populated).
| Column | Type | Description |
|---|---|---|
Timestamp | datetime | Last recorded date/time for this agent snapshot |
AgentId | guid | Unique agent identifier (dedup key) |
Name | string | Display name of the agent |
Description | string | Agent description |
Platform | string | Copilot Studio, Agent Builder in Microsoft 365 Copilot, Microsoft Foundry, Other, SharePoint, Amazon Bedrock, LocalAgents |
Version | string | Agent version |
PublishedStatus | string | Published, Draft |
LifecycleStatus | string | Blank for active agents; Deleted for removed agents |
CreatedDateTime | datetime | When the agent was created |
LastUpdatedDateTime | datetime | When last updated |
LastPublishedDateTime | datetime | When last published |
Owners | dynamic | Owner identities (sparse) |
SharedWith | dynamic | Sharing targets (sparse) |
InstanceCount | int | Blueprint instance count |
Instructions | string | System prompt / agent instructions (well populated) |
Model | string | Backing LLM model (sparse) |
Capabilities | dynamic | Declared capabilities (sparse) |
DeclaredDataSources | dynamic | Knowledge/data sources — array of filename/source strings (sparse) |
DeclaredTools | dynamic | Declared tools — array of {type, name} (sparse, flat) |
McpServers | dynamic | MCP servers — array of {name, description} (sparse) |
Skills, ConnectedAgents, Memory, Guardrails | dynamic | Additional declared config (sparse) |
EntraAgentID / EntraBlueprintID / ObservabilityID | string | Entra + observability linkage (note capital ID) |
RawAgentInfo | dynamic | Primary detail source — full governance + connector manifest (populated for ~all agents). See nested keys below |
TenantId, Type, SourceSystem | string | Standard envelope columns |
These columns exist but are not populated in observed data — do NOT build detections on them without first confirming population:
ToolsAuthenticationType (auth-type gap — see below), Availability, Endpoints, Triggers, Permissions, Model (mostly), and most of Owners/SharedWith.
🔴 Authentication-type gap: The deprecated
AIAgentsInfo.UserAuthenticationType(None/Integrated/Custom) has no populated equivalent inAgentsInfo. There is no reliable way to flag "unauthenticated" agents from this table. UseRawAgentInfo.allowForAllUsers == "true"as a broad-exposure proxy (Q4) and document the gap.
RawAgentInfo nested keys (the rich data)For Copilot Studio agents, RawAgentInfo is a marketplace/governance manifest. Key fields the queries below rely on:
| Path | Meaning |
|---|---|
RawAgentInfo.creatorId | Creator GUID (resolve to UPN via IdentityInfo join). Replaces CreatorAccountUpn. Sparse |
RawAgentInfo.allowForAllUsers | "true" = broadly accessible to all tenant users (exposure signal). Replaces AccessControlPolicy == "Any" |
RawAgentInfo.appType | lob (line-of-business, owner-scoped), shared, thirdParty, firstParty |
RawAgentInfo.scope | Sharing scope (e.g., tenant) |
RawAgentInfo.declarativeCopilotMetadata | Deep connector/tool detail (DCM). Present only for the connector-sourced subset (~10% of Copilot Studio agents) |
DCM nesting (recovers deep tool, operation, and endpoint detail):
RawAgentInfo.declarativeCopilotMetadata[]
.actions[]
.apis[] // .type = OpenApi | RemoteMCPServer | api_action
.serverUrls[] // populated for OpenApi + RemoteMCPServer (external hosts)
.operations[]
.operationId // e.g., "Office 365 Outlook Send an email (V2)"DCM siblings also carry instructions, llmModels (model), and sourceIds (incl. EnvironmentId, SourceAgentId).
The Agent Security Score is a composite risk indicator that summarizes the security posture of an organization's AI agent fleet. Higher scores indicate greater risk.
$$ \text{AgentSecurityScore} = \sum_{i} \text{DimensionScore}_i $$
Each dimension contributes 0–20 points to a maximum of 100:
| Dimension | Max | 🟢 Low (0–5) | 🟡 Medium (6–12) | 🔴 High (13–20) |
|---|---|---|---|---|
| Broadly-Accessible Agents | 20 | 0 agents with allowForAllUsers == "true" | 1–2 broadly-accessible agents | ≥3 broadly-accessible agents, especially if Published with knowledge sources or email capability |
| XPIA Email Risk | 20 | 0 email-capable agents | 1–2 email-capable agents (scoped access) | ≥1 email-capable agent that is also broadly accessible or has knowledge sources |
| Tool & Endpoint Exposure | 20 | 0–2 MCP agents, known creators, no external endpoints | 3–10 MCP agents, external endpoints all HTTPS/standard-port | >10 MCP agents, OR MCP/endpoint agents that are broadly accessible, OR any insecure-scheme / non-standard-port external endpoint (Q9 escalators) |
| Knowledge Source Risk | 20 | 0 agents with data sources + broad access | 1–3 agents with data sources + scoped access | Agents with data sources + allowForAllUsers == "true". Compounding rule: When agents have data sources + an email-send operation + broad access (the full XPIA chain from Q5 + Q7), score at maximum (20) for this dimension AND score XPIA Email Risk at maximum (20) — the combination is the documented attack pattern |
| Credential Hygiene | 20 | 0 credential patterns detected | Patterns found but agent is Draft (unpublished) | Patterns found in Published agents |
| Score | Rating | Action |
|---|---|---|
| 0–20 | ✅ Healthy | Normal posture, no immediate concerns |
| 21–45 | 🟡 Elevated | Review — minor misconfigurations detected |
| 46–70 | 🟠 Concerning | Investigate — multiple risk signals present |
| 71–100 | 🔴 Critical | Immediate remediation — significant agent security risk |
The Tool & Endpoint Exposure dimension folds external-endpoint risk (Q9) into the MCP exposure signal: an insecure scheme, a non-standard port, or an external endpoint on a broadly-accessible agent each escalates this dimension to its High tier regardless of MCP count.
These indicators are reported alongside the composite score for added context. They are intentionally not added to the /100 total — they enrich interpretation and feed the dimensions above as evidence.
| Indicator | Source | What it tells you |
|---|---|---|
| Capability Privilege Index | Q13 | Count of agents holding ≥1 sensitive operation (mail-send, directory-write, data-write, messaging). Split by broad access. A high count of broadly-accessible + sensitive-op agents is the strongest privilege-abuse signal and should justify maxing the Broad Access and/or XPIA dimensions. |
| Deep-Manifest Coverage | Q14 | Percentage of the fleet carrying declarativeCopilotMetadata (DCM). Because the XPIA, endpoint, and capability queries depend on DCM, this is the fraction of the estate that was fully inspectable. Every report MUST surface this so the analyst knows what was not inspected. |
| Ownership Governance — Orphaned Agents (baseline — all tenants) | Q16 | Count of agents whose assigned owner has left the org (Owners[] GUID no longer resolves in IdentityInfo) — the telemetry equivalent of the Agent 365 "Agents without owners" registry card, but cross-platform. Validated: telemetry surfaced ~30× more orphaned agents than the admin-center card (which is Agent-Builder-scoped). A live, credentialed, Published agent with no accountable human = MITRE T1098 governance risk. AH-native — reported for every tenant, unlike the Data-Lake-only indicators below. Canonical cross-platform version: entra_agent_identity.md Query 7. |
| Lifecycle Hygiene (Phase 6, Data Lake only) | Queries 1 & 5a (entra_agent_identity.md) | Count of agent identities that are disabled-but-present, lifecycle-expired, or dormant (enabled SPN with no runtime activity). In a validated lab, 11 of 14 provisioned identities were dormant. High dormancy = credentialed attack surface with no operational value; feed to decommissioning recommendations. Omitted entirely for Defender-only tenants. |
| Agent-User / Blueprint Sprawl (Phase 6, Data Lake only) | Queries 2, 3 & 6 (entra_agent_identity.md) | Agentic-user accounts (orphaned SP/blueprint links), duplicate/re-provisioned identities (same name, multiple SPNs), and multi-tenant blueprints with no verified publisher. Contextualizes the baseline sprawl signal (Q11) with Entra-identity evidence. Omitted for Defender-only tenants. |
RunAdvancedHuntingQuery is available (AgentsInfo is AH-only)Run in parallel — no dependencies between queries.
| Query | Purpose |
|---|---|
| Q1 | Global inventory summary (counts, date range, platforms, creators) |
| Q2 | Status and platform breakdown |
| Q3 | Access posture distribution (appType / allowForAllUsers) |
Run in parallel — no dependencies between queries.
| Query | Purpose |
|---|---|
| Q4 | Broadly-accessible agents (allowForAllUsers == "true" detail) |
| Q5 | XPIA email exfiltration risk (email-send connector operations) |
| Q6 | MCP tool inventory across agents |
| Q7 | Knowledge / data source audit |
| Q8 | Hard-coded credential scan |
| Q9 | External endpoint & HTTP risk (connector serverUrls) |
Run in parallel — no dependencies between queries.
| Query | Purpose |
|---|---|
| Q10 | Top creators and naming hygiene |
| Q11 | Agent creation trend over time |
| Q12 | Capability / tools inventory (all operation types) |
| Q13 | Operation-level privilege mapping (sensitive-operation matrix → Capability Privilege Index) |
| Q14 | Deep-manifest coverage (% of fleet with DCM → report coverage banner) |
| Q16 | Orphaned agents — owner departed (Owners[] ∖ IdentityInfo) → Ownership Governance indicator |
AgentsInfo describes how agents are configured; it does not show whether they are actually used or what they do at runtime. To close that gap, correlate the flagged configuration set against the CopilotActivity table (all-surface AI activity log, available in Advanced Hunting).
When to run: After Phase 4, when the user wants to know which flagged agents are actually active, which are dormant, or whether a high-risk agent shows runtime behavior.
🔴 Use a SCOPED lookup, never a fleet-wide join. A leftouter/inner join of the full AgentsInfo fleet (~15k agents, heavy RawAgentInfo dynamic) against CopilotActivity (100k+ rows) times out the Advanced Hunting endpoint. Instead:
CopilotActivity to that name set with where AgentName in (FlaggedNames) — light, no join. See Query 15.Join-key pitfall: CopilotActivity.AgentId is a composite/prefixed string (e.g., T_<tenant>.<guid>, CopilotStudio.Declarative.T_….gpt.<guid>, or literals like AgentBuilder) — it does not equal the clean AgentsInfo.AgentId GUID, so ID-based joins return 0 matches. AgentName is the reliable correlation key. Also note most CopilotActivity rows have an empty AgentId/AgentName (general M365 Copilot usage, not declarative-agent-attributed), so runtime attribution is inherently low-coverage — absence from CopilotActivity does NOT prove an agent is dormant.
Two high-value correlations:
CopilotActivity with real interactions → highest remediation priority (Query 15).CopilotActivity over the window → lower urgency, candidate for decommissioning (caveat: attribution gaps above).🔴 Do NOT present a fleet-wide Defender dormancy table.
CloudAppEvents/CopilotActivityattribute only ~0.3% of the fleet at runtime (validated: 45 of 15,038 agents), so a fleet-wideAgentsInfo ∖ runtimeleftanti reports ~99% "dormant" as a telemetry artifact, not a finding. Fleet-wide dormancy is only valid on the Data Lake plane (entra_agent_identity.mdQuery 5a, viaUnifiedAgentObservability.SrcAgentId). On Defender, present the positive runtime-attributed set (that file's Query 5b) plus the scoped flagged-list check above — never an inverted fleet-wide dormancy count.
For deeper runtime reconstruction (data accessed, tools invoked, jailbreak detections), hand off to the dedicated query library queries/cloud/copilot_activity_investigation.md rather than duplicating queries here.
Keep this phase thin and scoped: the posture skill owns configuration assessment;
copilot_activity_investigation.mdowns runtime reconstruction. Reference, don't duplicate.
AgentsInfo describes how agents are configured and owned; it does not carry the Entra identity model — the service-principal lifecycle, the app-registration (blueprint) governance, or the agentic-user accounts. When Sentinel Data Lake is present, this phase adds that layer.
When to run: After the enrichment-plane probe returns rows. If the probe fails (Defender-only tenant), skip this phase entirely and record the gap in the report banner — the baseline assessment and /100 score are unaffected.
🔴 All queries here are Data Lake — use mcp_sentinel-data_query_lake with workspaceId: "default". Full tested KQL lives in queries/cloud/entra_agent_identity.md; reference it, don't duplicate.
| Query | Purpose | Feeds |
|---|---|---|
| Query 1 | Agent identity state & lifecycle (enabled, compliance, expiration, provisioning source) | Lifecycle Hygiene indicator |
| Query 2 | Agent user account audit (agentic UPNs, orphaned SP/blueprint links) | Agent-User / Blueprint Sprawl indicator |
| Query 3 | Blueprint app-registration governance (verified publisher, multi-tenant audience, OAuth2 scopes/app-roles) | Agent-User / Blueprint Sprawl indicator |
| Query 4 | Identity graph — Blueprint → Identity → Agent User (Mermaid) | Report visualization |
| Query 5a | 🔴 Dormant / provisioned-but-inactive agents (identity ∖ UnifiedAgentObservability) | Lifecycle Hygiene indicator; decommissioning recommendations |
| Query 6 | Duplicate / re-provisioned identities (same name, multiple SPNs) | Agent-User / Blueprint Sprawl indicator |
Cross-plane enrichment: join the identity plane back to the baseline on EntraAgentIdentities.appId == AgentsInfo.EntraAgentID to attach the owner/creator (from AgentsInfo, which the Entra tables lack) to each identity finding. This makes a dormant or duplicate identity actionable — you can name who owns it.
Score impact: Phase 6 feeds the Lifecycle Hygiene and Agent-User / Blueprint Sprawl supplementary indicators only — it does NOT change the composite /100 score, so a Defender-only tenant receives the same maximum score and comparable ratings.
The dedicated hunting library
entra_agent_identity.mdowns the full identity-plane query set + the Defender-only coverage notes; this phase orchestrates it. Reference, don't duplicate.
All queries below are validated against the live
AgentsInfotable. Use them exactly as written, substituting only where noted. Because the rich agent detail lives in theRawAgentInfodynamic column, several queries parseRawAgentInfo.declarativeCopilotMetadata(DCM). DCM is present only for the connector-sourced subset of agents — queries that depend on it carry a coverage caveat.
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| extend CreatorId = tostring(RawAgentInfo.creatorId)
| summarize
UniqueAgents = dcount(AgentId),
EarliestRecord = min(Timestamp),
LatestRecord = max(Timestamp),
Published = countif(PublishedStatus == "Published"),
Draft = countif(PublishedStatus == "Draft"),
Deleted = countif(LifecycleStatus == "Deleted"),
UniquePlatforms = dcount(Platform),
UniqueCreators = dcount(CreatorId)Note:
UniqueCreatorscounts only agents with a populatedRawAgentInfo.creatorId(the connector-sourced subset). It under-counts true creators; treat it as a lower bound.
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| summarize AgentCount = count() by Platform, PublishedStatus
| order by AgentCount desc⚠️ Authentication-type gap: The deprecated
AIAgentsInfotable broke this down byUserAuthenticationType.AgentsInfohas no populated authentication-type column, so this query reports status by platform instead. For exposure, use Q3 (access posture) and Q4 (broadly-accessible agents).
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| extend AppType = tostring(RawAgentInfo.appType),
AllowAllUsers = tostring(RawAgentInfo.allowForAllUsers)
| summarize AgentCount = count() by Platform, AppType, AllowAllUsers
| order by AgentCount descInterpretation: appType == "lob" (line-of-business) agents are owner-scoped; appType == "shared" are shared more widely. allowForAllUsers == "true" (any platform) is the broad-exposure signal — these reach every tenant user. This replaces the old AccessControlPolicy distribution.
🔴 Security-critical query — agents with allowForAllUsers == "true" are accessible to all tenant users. This is the closest available proxy for the old "unauthenticated / Any access" exposure signal (see the authentication-type gap).
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| extend AllowAllUsers = tostring(RawAgentInfo.allowForAllUsers),
AppType = tostring(RawAgentInfo.appType),
CreatorId = tostring(RawAgentInfo.creatorId)
| where AllowAllUsers == "true"
| project Name, Platform, PublishedStatus, AppType, CreatorId, AgentId, CreatedDateTime, Description
| order by PublishedStatus asc, CreatedDateTime descPost-processing: For each broadly-accessible agent, note:
🔴 Capability Reconnaissance Risk (Attack Scenario 3): Broadly-accessible agents are prime targets for adversarial probing. Published agents with knowledge sources containing customer/internal data are the highest-priority findings.
🔴 Security-critical query — agents that can send email via a connector operation. A successful prompt-injection (XPIA) attack could direct the agent to exfiltrate data to arbitrary recipients.
Coverage caveat: Detects email-send operations declared in
RawAgentInfo.declarativeCopilotMetadata(DCM). DCM is present only for the connector-sourced agent subset. The oldIsGenerativeOrchestrationEnabledflag and action-levelinputs(AI-controlled vs hardcoded recipient) are not available inAgentsInfo— this query identifies capability, not orchestration mode.
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| where isnotempty(tostring(RawAgentInfo.declarativeCopilotMetadata))
| mv-expand DCM = RawAgentInfo.declarativeCopilotMetadata
| mv-expand Action = DCM.actions
| mv-expand Api = Action.apis
| mv-expand Op = Api.operations
| extend OperationId = tostring(Op.operationId)
| where OperationId has "Send an email" or OperationId has "SendEmail"
| extend AllowAllUsers = tostring(RawAgentInfo.allowForAllUsers),
CreatorId = tostring(RawAgentInfo.creatorId)
| summarize EmailOperations = make_set(OperationId)
by AgentId, Name, Platform, PublishedStatus, AllowAllUsers, CreatorId
| order by AllowAllUsers desc, PublishedStatus ascPost-processing:
AllowAllUsers == "true" → email-capable and broadly accessible = highest XPIA risk (any tenant user can trigger the chain).🔴 Attack Scenario Mapping: This query detects the agent-configuration precondition (email-send capability) for two documented scenarios — Malicious Instruction Injection via Event Trigger and Prompt Injection via Shared Document. Broadly-accessible email-capable agents (no access restriction + email) are the most dangerous.
🟠 Governance query — MCP servers give agents access to external systems, Graph API, Sentinel data, and more. Uncontrolled MCP proliferation increases the attack surface. AgentsInfo exposes a dedicated McpServers column (cleaner than the old tool-detail parse).
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| where array_length(McpServers) > 0
| mv-expand Mcp = McpServers
| extend McpName = tostring(Mcp.name)
| extend CreatorId = tostring(RawAgentInfo.creatorId),
AllowAllUsers = tostring(RawAgentInfo.allowForAllUsers)
| summarize McpServerList = make_set(McpName), McpToolCount = dcount(McpName)
by AgentId, Name, Platform, CreatorId, AllowAllUsers
| order by McpToolCount descMCP server distribution (which servers appear on the most agents):
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| where array_length(McpServers) > 0
| mv-expand Mcp = McpServers
| summarize AgentCount = dcount(AgentId) by McpServer = tostring(Mcp.name)
| order by AgentCount descNote:
McpServersis flat ({name, description}only) — no server URLs or credential config. For external MCP endpoint detail (host/scheme/port), use Q9, which parsesRemoteMCPServerserverUrlsfrom DCM.
🟡 Data exposure query — identifies what data sources agents declare. In AgentsInfo, declared sources appear in the DeclaredDataSources column as an array of source/filename strings.
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| where array_length(DeclaredDataSources) > 0
| mv-expand DS = DeclaredDataSources
| extend DataSource = tostring(DS)
| extend AllowAllUsers = tostring(RawAgentInfo.allowForAllUsers),
CreatorId = tostring(RawAgentInfo.creatorId)
| summarize DataSources = make_set(DataSource), SourceCount = dcount(DataSource)
by AgentId, Name, Platform, AllowAllUsers, CreatorId
| order by SourceCount descPost-processing — flag high-risk combinations:
allowForAllUsers == "true" → internal data potentially exposed broadly.Coverage caveat:
DeclaredDataSourcesis sparse and stores source names/filenames (e.g.,Priority-Banking-Policy.docx), not the richer$kind/site structure the oldKnowledgeDetailscolumn held. Source type classification (SharePoint vs public site vs federated) is not reliably available — report the declared source names and flag broadly-accessible agents that carry any.For actual (runtime) SharePoint sites accessed by agents — not just declared config — see
copilot_activity_investigation.mdQueries 14–16:CopilotActivity.AccessedResources.SiteUrlgives the agent-attributed site list, andCloudAppEvents(filtered to the validatedPower Virtual Agents/Enterprise Copilot Platformclient-app fingerprint) gives the full SharePoint audit trail (ClientIP, exact file,FileDownloadedgranularity) for the same OBO access.
🔴 Document Injection Risk (Attack Scenario 2): Data sources are the primary vector for indirect prompt injection (XPIA). Cross-reference with Q5: agents that combine declared data sources with an email-send operation are the textbook XPIA exfiltration pattern — flag these as highest priority in the Knowledge Source Risk dimension.
🔴 Security-critical query — scans agent Instructions and the connector metadata in RawAgentInfo for patterns matching API keys, JWTs, Basic auth headers, and embedded credentials.
let suspicious_patterns = @"(AKIA[0-9A-Z]{16})|(AIza[0-9A-Za-z_\-]{35})|(xox[baprs]-[0-9a-zA-Z]{10,48})|(ghp_[A-Za-z0-9]{36,59})|(sk_(live|test)_[A-Za-z0-9]{24})|(SG\.[A-Za-z0-9]{22}\.[A-Za-z0-9]{43})|(eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+)|(Authorization\s*:\s*Basic\s+[A-Za-z0-9=:+]+)|([A-Za-z]+:\/\/[^\/\s]+:[^\/\s]+@[^\/\s]+)";
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| extend Haystack = strcat(tostring(Instructions), " ", tostring(RawAgentInfo.declarativeCopilotMetadata))
| where Haystack matches regex suspicious_patterns
| project Name, Platform, PublishedStatus,
CreatorId = tostring(RawAgentInfo.creatorId), AgentIdPost-processing:
eyJ...) and url://user:pass@host patterns can false-positive on example payloads — manually review each match.🟠 Network risk query — inventories the external hosts that agent connectors reach, and flags insecure schemes or non-standard ports. External endpoints are declared in DCM apis[].serverUrls for OpenApi and RemoteMCPServer connector types (these are populated; api_action Power Platform connectors abstract the URL and are not covered).
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| where isnotempty(tostring(RawAgentInfo.declarativeCopilotMetadata))
| mv-expand DCM = RawAgentInfo.declarativeCopilotMetadata
| mv-expand Action = DCM.actions
| mv-expand Api = Action.apis
| extend ApiType = tostring(Api.type)
| where ApiType in ("OpenApi", "RemoteMCPServer")
| mv-expand Url = Api.serverUrls
| extend Url = tostring(Url)
| where isnotempty(Url)
| extend Host = tostring(parse_url(Url).Host),
Port = tostring(parse_url(Url).Port),
Scheme = tostring(parse_url(Url).Scheme)
| extend NonStandardPort = isnotempty(Port) and Port !in ("443", "80", ""),
InsecureScheme = Scheme != "https"
| project Name, Platform, ApiType, Scheme, Host, Port, Url,
NonStandardPort, InsecureScheme,
AllowAllUsers = tostring(RawAgentInfo.allowForAllUsers)
| order by NonStandardPort desc, InsecureScheme desc, Host ascPost-processing:
InsecureScheme == true (non-HTTPS) or NonStandardPort == true → review the connector; data may transit insecurely.AllowAllUsers == "true") → highest priority.Coverage caveat: Only
OpenApi+RemoteMCPServerconnectors declareserverUrls. Power Platformapi_actionconnectors (the majority) do not expose a URL here, so their destinations are not inventoried by this query. The old topic-levelHttpRequestActionparsing is not applicable toAgentsInfo.
👥 Governance query — identifies prolific agent creators and names lacking descriptiveness. Creator is a GUID in RawAgentInfo.creatorId; resolve to UPN via an IdentityInfo join.
let IdMap = materialize(IdentityInfo
| where isnotempty(AccountObjectId) and isnotempty(AccountUpn)
| distinct AccountObjectId, AccountUpn);
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| extend CreatorId = tostring(RawAgentInfo.creatorId)
| where isnotempty(CreatorId)
| join kind=leftouter IdMap on $left.CreatorId == $right.AccountObjectId
| extend CreatorUpn = coalesce(AccountUpn, CreatorId)
| summarize
AgentCount = count(),
PublishedCount = countif(PublishedStatus == "Published"),
GenericNameCount = countif(Name in~ ("Agent", "agent", "Test", "test", "New Agent")),
NoDescriptionCount = countif(isempty(Description)),
AgentNames = make_set(Name, 10)
by CreatorUpn
| order by AgentCount desc
| take 20Coverage caveat: Only agents with a populated
RawAgentInfo.creatorIdare attributed. Creators whose GUID does not resolve inIdentityInfofall back to the raw GUID. A single creator with a very highAgentCountis a sprawl signal worth investigating.
📈 Trend query — shows agent creation velocity over time to detect sprawl acceleration.
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| where isnotempty(CreatedDateTime)
| summarize AgentsCreated = count() by bin(CreatedDateTime, 7d)
| order by CreatedDateTime asc🛠️ Tools governance query — catalogs the operations agents can invoke across all connector types, to understand the full capability surface. Parses DCM operations (operationId + API type).
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| where isnotempty(tostring(RawAgentInfo.declarativeCopilotMetadata))
| mv-expand DCM = RawAgentInfo.declarativeCopilotMetadata
| mv-expand Action = DCM.actions
| mv-expand Api = Action.apis
| mv-expand Op = Api.operations
| extend OperationId = tostring(Op.operationId), ApiType = tostring(Api.type)
| where isnotempty(OperationId)
| summarize AgentCount = dcount(AgentId), Agents = make_set(Name, 5) by OperationId, ApiType
| order by AgentCount descAlternative for non-DCM agents — the flat DeclaredTools column ({type, name}) covers agents without DCM:
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| where array_length(DeclaredTools) > 0
| mv-expand Tool = DeclaredTools
| summarize AgentCount = dcount(AgentId)
by ToolType = tostring(Tool.type), ToolName = tostring(Tool.name)
| order by AgentCount descCoverage caveat: The DCM query yields deep operation-level detail but only for the connector-sourced subset. The
DeclaredToolsfallback is broader but flatter (tool name/type only, no operation IDs). Run both for the fullest picture.
🔐 Privilege query — buckets every declared operation into a sensitivity category (mail-send, directory-write, data-write, messaging, security-tooling, read/other) to surface where write/exfiltration capability concentrates. Feeds the Capability Privilege Index supplementary indicator.
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| where isnotempty(tostring(RawAgentInfo.declarativeCopilotMetadata))
| mv-expand DCM = RawAgentInfo.declarativeCopilotMetadata
| mv-expand Action = DCM.actions
| mv-expand Api = Action.apis
| mv-expand Op = Api.operations
| extend OperationId = tostring(Op.operationId)
| where isnotempty(OperationId)
| extend PrivilegeCategory = case(
OperationId has_any ("Send an email", "SendEmail", "Send email"), "Mail-Send",
OperationId has_any ("AddUserToGroup", "RemoveMember", "UpdatePerson", "UpdateOrganisation", "Create user", "Delete user", "Update user", "Assign"), "Directory-Write",
OperationId has_any ("unbound action", "Create a row", "Update a row", "Delete a row", "Create record", "Update record"), "Data-Write",
OperationId has_any ("Post message", "Post a message", "Send message", "Create chat", "post in a chat"), "Messaging",
OperationId has_any ("Security Copilot", "Sentinel"), "Security-Tooling",
"Other/Read")
| summarize AgentCount = dcount(AgentId) by PrivilegeCategory
| order by AgentCount descCapability Privilege Index — distinct agents holding ≥1 sensitive (write/send) operation, split by broad access:
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| where isnotempty(tostring(RawAgentInfo.declarativeCopilotMetadata))
| extend AllowAllUsers = tostring(RawAgentInfo.allowForAllUsers)
| mv-expand DCM = RawAgentInfo.declarativeCopilotMetadata
| mv-expand Action = DCM.actions
| mv-expand Api = Action.apis
| mv-expand Op = Api.operations
| extend OperationId = tostring(Op.operationId)
| where OperationId has_any ("Send an email", "SendEmail", "AddUserToGroup", "RemoveMember", "UpdatePerson", "UpdateOrganisation", "unbound action", "Create a row", "Update a row", "Delete a row", "Post message", "post in a chat")
| summarize SensitiveAgents = dcount(AgentId),
BroadAndSensitive = dcountif(AgentId, AllowAllUsers == "true")Interpretation:
BroadAndSensitive > 0is a direct privilege-abuse signal — a broadly-accessible agent that can write to the directory, write data, or send mail. These agents justify maxing the Broad Access and/or XPIA dimensions. Tune the operation keyword lists to your tenant's connector set.
📊 Coverage query — reports what fraction of the fleet carries the deep declarativeCopilotMetadata (DCM) that the XPIA, endpoint, and capability queries depend on. Run this every report and surface the result as a banner so the analyst knows what was not fully inspected.
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| summarize Total = count(),
WithDCM = countif(isnotempty(tostring(RawAgentInfo.declarativeCopilotMetadata))),
WithInstructions = countif(isnotempty(Instructions)),
WithObservabilityID = countif(isnotempty(ObservabilityID)),
WithEntraAgentID = countif(isnotempty(EntraAgentID))
| extend DcmCoveragePct = round(100.0 * WithDCM / Total, 1),
InstrCoveragePct = round(100.0 * WithInstructions / Total, 1),
ObsIdPct = round(100.0 * WithObservabilityID / Total, 1),
EntraIdPct = round(100.0 * WithEntraAgentID / Total, 1)Why both ID columns:
ObservabilityIDis near-universally populated (~100%) and is the natural runtime-correlation handle;EntraAgentIDis sparse (only agents provisioned with an Entra Agent ID). Report both so the analyst knows which runtime/identity correlations are feasible.
🎯 Runtime query (Phase 5) — confirms which flagged agents are actually active. Scoped by name list — no fleet-wide join (see Phase 5 for why a full join times out). Populate FlaggedNames from the Q4 broadly-accessible and Q13 sensitive-op results.
let FlaggedNames = dynamic(["<broadly-accessible or sensitive-op agent names from Q4/Q13>"]);
CopilotActivity
| where TimeGenerated > ago(7d)
| where AgentName in (FlaggedNames)
| summarize Interactions = count(),
DistinctUsers = dcount(ActorUserId),
LastSeen = max(TimeGenerated),
SrcIPs = dcount(SrcIpAddr) by AgentName
| order by Interactions descInterpretation: A flagged agent appearing here with real
Interactionsis active-and-dangerous — prioritize for remediation over dormant flagged agents. Join key isAgentName(CopilotActivity.AgentIdis a composite prefixed string that does NOT equalAgentsInfo.AgentId). Absence here does not prove dormancy — mostCopilotActivityrows are unattributed (emptyAgentName).AIModelNameis sparse in this table; do not rely on it for model inventory.
👥 Governance query (Phase 3) — agents whose assigned owner has left the organization. Telemetry equivalent of the Agent 365 "Agents without owners" registry card, but cross-platform (the admin-center card is Agent-Builder-scoped and materially under-reports). AH-native — runs for every tenant. Feeds the Ownership Governance supplementary indicator.
// Owner lives in AgentsInfo.Owners[] (populated for shared agents); "departed" = GUID no longer a current directory user.
let CurrentUsers = IdentityInfo | where isnotempty(AccountObjectId) | distinct AccountObjectId;
AgentsInfo
| summarize arg_max(Timestamp, *) by AgentId
| where LifecycleStatus != "Deleted"
| mv-expand OwnerGuid = parse_json(tostring(Owners)) to typeof(string)
| where isnotempty(OwnerGuid)
| where OwnerGuid !in (CurrentUsers) // owner no longer in directory = departed
| summarize OrphanedAgents = dcount(AgentId), DepartedOwners = dcount(OwnerGuid),
PublishedOrphans = countif(PublishedStatus == "Published") by Platform
| order by OrphanedAgents descInterpretation: Each row is a platform's count of agents whose owner has departed. Prioritize
PublishedOrphans(live + unowned).IdentityInfo-absence is a proxy for "departed" — it reliably catches hard-deleted creators but may also include disabled/external/unsynced identities, so corroborate a specific owner before blocking/deleting. Keys onOwners[](shared agents); it does not attempt "no owner assigned at all" —AgentsInfo.Owners/creatorIdare too sparse for that (blank ≠ no owner; use the Agent Registry / Graph/servicePrincipals/{id}/ownersfor that signal). Canonical cross-platform detail + caveats:entra_agent_identity.mdQuery 7.
Render the full analysis directly in the chat response. Best for quick review.
Save a comprehensive report to disk at:
reports/ai-agent-posture/AI_Agent_Posture_Report_YYYYMMDD_HHMMSS.mdGenerate the markdown file AND provide an inline summary in chat.
Always ask the user which mode before generating output.
Render the following sections in order. Omit sections only if explicitly noted as conditional.
🔴 URL Rule: All hyperlinks in the report MUST be copied verbatim from the URL Registry above. Do NOT generate, recall from memory, or paraphrase any URL. If a needed URL is not in the registry, use plain text (no hyperlink).
# 🤖 AI Agent Security Posture Report
**Generated:** YYYY-MM-DD HH:MM UTC
**Data Source:** AgentsInfo (Advanced Hunting)
**Analysis Period:** <EarliestRecord> → <LatestRecord>
**Platforms:** <list discovered Platform values>
---
> 📊 **Deep-Manifest Coverage (Q14):** `<WithDCM>/<Total>` agents (**<DcmCoveragePct>%**) carry `declarativeCopilotMetadata` — the XPIA, external-endpoint, and capability findings below cover **only this subset**. Instructions present on **<InstrCoveragePct>%**, ObservabilityID on **<ObsIdPct>%** (runtime-correlation handle), EntraAgentID on **<EntraIdPct>%**. The remaining `<Total - WithDCM>` agents were inventoried but not deeply inspected.
---
## Executive Summary
<2-3 sentences: total agents, key risk findings, overall score>
**Overall Risk Rating:** 🔴/🟠/🟡/✅ <RATING> (<Score>/100)
---
## Key Metrics
| Metric | Value |
|--------|-------|
| Total Agents (non-deleted) | <N> |
| Published Agents | <N> |
| Draft Agents | <N> |
| Platforms Represented | <N> |
| Resolved Creators (lower bound) | <N> |
| Broadly-Accessible Agents (allowForAllUsers) | <N> |
| Agents with MCP Servers | <N> |
| Agents with Declared Data Sources | <N> |
| Email-Capable Agents (XPIA Risk) | <N> |
> ℹ️ **Coverage note:** Creator and capability metrics are derived from `RawAgentInfo` and `declarativeCopilotMetadata`, which are sparsely populated. Counts marked "lower bound" reflect only agents with the relevant field present — see per-section caveats.
---
## 🔓 Access Posture
> **Authentication-type gap:** `AgentsInfo` has no equivalent to the old `UserAuthenticationType` (None/Microsoft/Custom). The `ToolsAuthenticationType` column is effectively empty in practice. Access exposure is assessed via the `RawAgentInfo.allowForAllUsers` governance signal instead — a **proxy for broad exposure, not an authentication state**.
### Access Distribution (Q3)
| App Type | Allow-All-Users | Count |
|----------|-----------------|-------|
| <appType> | <true/false> | <N> |
### 🔴 Broadly-Accessible Agents (Q4)
<If Q4 returns results:>
| Agent Name | Platform | App Type | Published | Created |
|------------|----------|----------|-----------|---------|
| <name> | <platform> | <appType> | <status> | <date> |
<If Q4 returns 0:>
✅ No broadly-accessible agents (`allowForAllUsers == "true"`) detected.
---
## 📧 XPIA Email Exfiltration Risk
<If Q5 returns results:>
| Agent Name | Platform | Email Operation | Broadly Accessible |
|------------|----------|-----------------|--------------------|
| <name> | <platform> | <operationId> | 🔴 Yes / 🟢 No |
**Risk Assessment:**
- 🔴 Email-capable agents can be exploited via XPIA to exfiltrate data, especially when combined with declared data sources (Q7).
- ⚠️ Recommendation: Review recipient controls; apply Power Platform DLP and Defender Runtime Protection.
> **Coverage caveat:** Email capability is detected from DCM `operations[].operationId` (e.g., "Send an email", "SendEmail"). There is no longer a GenAI-orchestration flag or an `inputs` field, so AI-controlled-vs-hardcoded recipient distinction is **not available** — treat all email-capable agents as candidates. Only the DCM-bearing subset is covered.
<If Q5 returns 0:>
✅ No email-capable agents detected in the DCM-bearing subset.
---
## 🛠️ MCP Server Exposure
<If Q6 returns results:>
| Agent Name | Platform | MCP Servers | Broadly Accessible |
|------------|----------|-------------|--------------------|
| <name> | <platform> | <server list> | <yes/no> |
**MCP Server Distribution:**
| MCP Server | Agent Count |
|------------|-------------|
| <server> | <N> |
<If Q6 returns 0:>
✅ No agents with MCP servers detected.
> **Coverage caveat:** The `McpServers` column is flat (`{name, description}` only) — no server URLs, credential config, or transport detail. Non-HTTPS/hardcoded-cred MCP detection from the old schema is not possible here.
> **Dimension note:** MCP exposure and the External Endpoint findings (below) both feed the single **Tool & Endpoint Exposure** score dimension. Any insecure scheme, non-standard port, or external endpoint on a broadly-accessible agent escalates that dimension to High regardless of MCP count.
---
## 📚 Declared Data Source Exposure
<If Q7 returns results:>
| Agent Name | Platform | Data Sources | Broadly Accessible |
|------------|----------|--------------|--------------------|
| <name> | <platform> | <source names> | <yes/no> |
**⚠️ High-Risk Combinations:**
<List agents with declared data sources + allowForAllUsers == "true", and agents combining data sources with email capability (Q5)>
<If Q7 returns 0:>
✅ No declared data sources found on any agents.
> **Coverage caveat:** `DeclaredDataSources` stores source **names/filenames** only — source *type* classification (SharePoint vs public site vs federated) is not available.
---
## 🔑 Credential Hygiene
<If Q8 returns results:>
🔴 **Hard-coded credential patterns detected in <N> agent(s):**
| Agent Name | Platform | Status | Creator |
|------------|----------|--------|---------|
| <name> | <platform> | <status> | <creatorId/upn> |
⚠️ **Recommendation:** Move secrets to Azure Key Vault; use environment variables at runtime.
<If Q8 returns 0:>
✅ No hard-coded credential patterns detected in agent instructions or connector metadata.
---
## 🌐 External Endpoint & HTTP Risk
<If Q9 returns results:>
| Agent | API Type | Scheme | Host | Port | Insecure | Non-Standard Port |
|-------|----------|--------|------|------|----------|-------------------|
| <name> | <OpenApi/RemoteMCPServer> | <scheme> | <host> | <port> | 🔴/🟢 | 🔴/🟢 |
<If Q9 returns 0:>
✅ No external endpoints with insecure schemes or non-standard ports detected.
> **Coverage caveat:** Only `OpenApi` + `RemoteMCPServer` connectors declare `serverUrls`. Power Platform `api_action` connectors do not expose destination URLs.
---
## 👥 Creator Governance
### Top Creators
| Creator | Agents | Published | Generic Names | No Description |
|---------|--------|-----------|---------------|----------------|
| <upn/creatorId> | <N> | <N> | <N> | <N> |
### Naming Hygiene
- Agents with generic names ("Agent", "Test"): <N>
- Agents with no description: <N>
> **Coverage caveat:** Only agents with a populated `RawAgentInfo.creatorId` are attributed; GUIDs unresolved in `IdentityInfo` fall back to the raw GUID.
### Ownerless / Orphaned Agents (Q16)
- **Owner departed** (assigned owner left the org): **<N>** agents across **<P>** platforms (**<PublishedN>** Published)
- Most affected platform: **<Platform>** (<N>)
> The Agent 365 admin-center *"Agents without owners"* card is Agent-Builder-scoped; this telemetry view is **cross-platform** and typically surfaces materially more orphaned agents (validated ~30× in one large tenant). Owner-departed = **MITRE T1098** governance risk (live, credentialed, unowned). Corroborate `IdentityInfo`-absence (may include disabled/external identities) before blocking or reassigning.
---
## 📈 Agent Creation Trend
<ASCII bar chart or summary table of Q11 results — weekly agent creation counts>
---
## 🛠️ Full Capability / Tools Inventory
| Operation / Tool | API / Tool Type | Agent Count | Example Agents |
|------------------|-----------------|-------------|----------------|
| <operationId/name> | <type> | <N> | <agent names> |
---
## 🔐 Capability Privilege Index (Supplementary — not summed into score)
**Operation sensitivity distribution (Q13):**
| Privilege Category | Agent Count |
|--------------------|-------------|
| Mail-Send | <N> |
| Directory-Write | <N> |
| Data-Write | <N> |
| Messaging | <N> |
| Security-Tooling | <N> |
| Other/Read | <N> |
**Index:** <SensitiveAgents> agent(s) hold ≥1 sensitive (write/send) operation; **<BroadAndSensitive>** of those are also broadly accessible (`allowForAllUsers == "true"`).
<If BroadAndSensitive > 0:>
🔴 **<BroadAndSensitive> broadly-accessible agent(s) with sensitive write/send capability** — direct privilege-abuse exposure. These justify maxing the Broad Access and/or XPIA dimensions.
<If BroadAndSensitive == 0:>
✅ No broadly-accessible agents hold sensitive write/send operations (within the DCM-bearing subset).
> Supplementary indicator — provides privilege context but is **not** added to the /100 composite. Coverage limited to the DCM-bearing subset (see banner).
---
## 🎯 Runtime Correlation — Active-and-Dangerous (Q15, Optional)
<If Phase 5 was run — flagged agents correlated against CopilotActivity:>
| Agent Name | Interactions | Distinct Users | Source IPs | Last Seen |
|------------|--------------|----------------|------------|-----------|
| <name> | <N> | <N> | <N> | <date> |
🔴 **Active-and-dangerous:** Flagged agents (broadly accessible / sensitive ops) confirmed active at runtime — prioritize for remediation over dormant flagged agents.
<If no flagged agents appear in CopilotActivity:>
✅ No flagged agents showed runtime activity in the window. *(Caveat: most `CopilotActivity` rows are unattributed — absence does not prove dormancy.)*
> Scoped name-based lookup (`AgentName` key). Runtime attribution is inherently low-coverage; this section confirms presence, not absence.
---
## Agent Security Score Card
```
┌──────────────────────────────────────────────────────┐
│ AGENT SECURITY SCORE: <NN>/100 │
│ Rating: <EMOJI> <RATING> │
├──────────────────────────────────────────────────────┤
│ Broad Access [<bar>] <N>/20 (<detail>) │
│ XPIA Email Risk [<bar>] <N>/20 (<detail>) │
│ Tool & Endpt Expo[<bar>] <N>/20 (<detail>) │
│ Data Source Risk [<bar>] <N>/20 (<detail>) │
│ Credential Hygn [<bar>] <N>/20 (<detail>) │
├──────────────────────────────────────────────────────┤
│ Supplementary (not scored): │
│ Capability Privilege Index: <S> sensitive / <B> broad│
│ Deep-Manifest Coverage: <DcmCoveragePct>% │
└──────────────────────────────────────────────────────┘
```
---
## Security Assessment
| Factor | Finding |
|--------|---------|
| <emoji> **<Factor>** | <Evidence-based finding> |
---
## Recommendations
> **Key mitigation — Runtime:** For all high-risk agents, recommend enabling **Microsoft Defender Runtime Protection** — webhook-based real-time inspection that can block malicious tool invocations before execution. See [Real-time agent protection during runtime](https://learn.microsoft.com/en-us/defender-cloud-apps/real-time-agent-protection-during-runtime).
> **Key mitigation — Governance:** For fleet-wide governance gaps (sprawl, missing auth, uncontrolled tools), recommend adopting **[Microsoft Agent 365](https://www.microsoft.com/en-us/microsoft-365/blog/2025/11/18/microsoft-agent-365-the-control-plane-for-ai-agents/)** as the enterprise control plane — providing centralized Registry (inventory + quarantine), Access Control (Entra agent IDs + Policy Templates), Visualization (agent ↔ resource mapping), and Security (Defender + Purview integration).
1. <emoji> **<Priority action>** — <evidence and rationale>
2. ...
---
## Appendix: Query Execution Summary
| Query | Description | Records | Time |
|-------|-------------|---------|------|
| Q1 | Global Inventory | <N> | <time> |
| Q2 | Status & Auth Breakdown | <N> | <time> |
| ... | ... | ... | ... |
| Q13 | Operation-Level Privilege Mapping | <N> | <time> |
| Q14 | Deep-Manifest Coverage | <N> | <time> |
| Q15 | Runtime Correlation (scoped, optional) | <N> | <time> |When outputting to markdown file, use the same structure as the Inline Report Template above, saved to:
reports/ai-agent-posture/AI_Agent_Posture_Report_YYYYMMDD_HHMMSS.mdInclude the following additional sections in the file report that are omitted from inline:
Platform)# AI Agent Security Posture Report
**Generated:** YYYY-MM-DD HH:MM UTC
**Data Source:** AgentsInfo (Advanced Hunting)
**Analysis Period:** <EarliestRecord> → <LatestRecord> (<N> days)
**Platforms:** <list discovered Platform values>
**Total Agents:** <N> (Published: <N>, Draft: <N>)
---
> 📊 **Deep-Manifest Coverage (Q14):** `<WithDCM>/<Total>` agents (**<DcmCoveragePct>%**) carry `declarativeCopilotMetadata`; XPIA/endpoint/capability findings cover only this subset. ObservabilityID **<ObsIdPct>%**, EntraAgentID **<EntraIdPct>%**.
---Include the Capability Privilege Index and (if Phase 5 ran) Runtime Correlation sections from the inline template in the file report as well.
Problem: The AgentsInfo table does NOT exist in Sentinel Data Lake. Querying via mcp_sentinel-data_query_lake returns SemanticError: Failed to resolve table.
Solution: Always use RunAdvancedHuntingQuery. The table has 30-day retention in AH.
Problem: The table logs configuration snapshots over time. Querying without deduplication returns inflated counts and duplicate agent entries.
Solution: Always use | summarize arg_max(Timestamp, *) by AgentId to get the latest state per agent before any analysis. Note AgentId is a guid and the column is Name/Description (not AgentName/AgentDescription as some docs state).
Problem: The normalized columns (DeclaredTools, McpServers, DeclaredDataSources, Owners, Capabilities) are sparsely populated and flat. The rich governance/configuration detail lives in the RawAgentInfo dynamic column (populated for ~all agents) and, for the connector-sourced subset, in RawAgentInfo.declarativeCopilotMetadata (DCM).
Solution: For creator (RawAgentInfo.creatorId), broad access (RawAgentInfo.allowForAllUsers), app type (RawAgentInfo.appType), and deep capability/endpoint detail, parse RawAgentInfo. RawAgentInfo is dynamic — no double-parse needed; access nested keys directly with tostring(RawAgentInfo.key).
Problem: The EntraAgentIdentities / EntraAgentIdentityBlueprints / EntraAgentUsers tables are Sentinel Data Lake system tables — they do NOT exist in Advanced Hunting. A Defender-only tenant has no access to them, and querying them via RunAdvancedHuntingQuery or with a workspace GUID fails.
Solution: Always probe first and query with mcp_sentinel-data_query_lake + workspaceId: "default". Skip Phase 6 gracefully when absent and note it in the report banner. Additional Entra-table pitfalls (snapshot dedup with arg_max(TimeGenerated,*) by id, id == appId, dynamic lifecycle/tags, blueprint table containing non-agent platform apps, no native owner field) are documented in queries/cloud/entra_agent_identity.md. Owner/creator is NOT in these tables — cross-reference AgentsInfo (EntraAgentIdentities.appId == AgentsInfo.EntraAgentID) or Graph /servicePrincipals/{id}/owners.
Problem: Deep capability queries (Q5 email, Q9 endpoints, Q12 operations) depend on RawAgentInfo.declarativeCopilotMetadata, which is present for only ~10% of Copilot Studio agents (the connector-sourced subset). The majority have only a shallow manifest.
Solution: Always state the coverage caveat in reports. DCM path: declarativeCopilotMetadata[].actions[].apis[] with .type (OpenApi/RemoteMCPServer/api_action), .serverUrls[], and .operations[].operationId. Results from these queries are a floor, not a complete inventory.
Problem: The old UserAuthenticationType (None/Microsoft/Custom) is gone. The ToolsAuthenticationType column exists in schema but is effectively empty (~100% blank). There is no way to classify agents as "unauthenticated" the way the old skill did.
Solution: Use RawAgentInfo.allowForAllUsers == "true" as a broad-exposure proxy (documented as a proxy, NOT an authentication state). Never claim an agent is "unauthenticated" — say "broadly accessible".
Problem: ToolsAuthenticationType, Availability, Endpoints, Triggers, Permissions, and Model are present in the schema but empty/null in practice. Queries built on them silently return 0 rows.
Solution: Do not build core logic on these columns. Validate population with a quick summarize countif(isnotempty(<col>)) before relying on a column. LifecycleStatus is blank for active agents (only Deleted is populated) — LifecycleStatus != "Deleted" correctly passes blanks.
Problem: RawAgentInfo.creatorId is an Entra object GUID, not a UPN. There is no CreatorAccountUpn, LastModifiedByUpn, or LastPublishedByUpn equivalent.
Solution: Resolve via leftouter join to IdentityInfo on AccountObjectId, then coalesce(AccountUpn, CreatorId). Creator attribution is a lower bound — creatorId is sparse.
Problem: External endpoint URLs in DCM apis[].serverUrls are populated for OpenApi and RemoteMCPServer connector types, but not for api_action (Power Platform connectors, the majority). Filtering all API types yields mostly empty URLs.
Solution: Filter ApiType in ("OpenApi", "RemoteMCPServer") before expanding serverUrls. State that api_action destinations are not inventoried.
Problem: The dedicated McpServers column contains only {name, description} — no server URLs, credential configuration, or transport detail. Non-HTTPS MCP detection and hardcoded-cred-in-MCP detection from the old design are not possible.
Solution: Use McpServers for inventory/exposure counts only. For MCP server endpoints, fall back to the DCM RemoteMCPServer API type (Q9).
Problem: Since Feb 25, 2026, Advanced Hunting boolean results render as textual True/False, not 1/0. Governance flags from RawAgentInfo (e.g., allowForAllUsers) are JSON strings ("true"/"false").
Solution: Compare against the string form: tostring(RawAgentInfo.allowForAllUsers) == "true". Avoid == 1 / == true numeric/bool comparisons on parsed JSON values.
Problem: Phase 5 runtime correlation against CopilotActivity has three traps: (1) CopilotActivity.AgentId is a composite/prefixed string (e.g., T_<tenant>.<guid>, CopilotStudio.Declarative.T_….gpt.<guid>, or literals like AgentBuilder) that does not equal the clean AgentsInfo.AgentId GUID — ID joins return 0 matches. (2) A fleet-wide AgentsInfo ↔ CopilotActivity join (~15k agents × 100k+ rows, heavy RawAgentInfo) times out the AH endpoint. (3) Most CopilotActivity rows have an empty AgentName/AgentId (general M365 Copilot usage), so runtime attribution is low-coverage.
Solution: Use a scoped name-based lookup (Query 15): build a small flagged-name list from Q4/Q13, then CopilotActivity | where AgentName in (FlaggedNames) — no join. AgentName is the reliable cross-table key. Never join the full fleet. Treat absence from CopilotActivity as unconfirmed, not proof of dormancy. AIModelName is sparse here — do not use it for model inventory.
Before delivering the report, verify:
arg_max(Timestamp, *) by AgentId for deduplicationLifecycleStatus != "Deleted" (unless auditing deletions)RunAdvancedHuntingQuery (not Data Lake)AgentName (Query 15) — never a fleet-wide join; absence is described as unconfirmed, not dormantcreatorId GUIDs via IdentityInfo and notes the lower-bound caveat📊 Optional post-report step. After an AI Agent Security Posture report is generated, the user can request a visual SVG dashboard.
Trigger phrases: "generate SVG dashboard", "create a visual dashboard", "visualize this report", "SVG from the report"
#file:reports/ai-agent-posture/AI_Agent_Posture_Report_<org>_<date>.mdStep 1: Read svg-widgets.yaml (this skill's widget manifest)
Step 2: Read .github/skills/svg-dashboard/SKILL.md (rendering rules — Manifest Mode)
Step 3: Read the completed report file (data source)
Step 4: Render SVG → save to reports/ai-agent-posture/{report_name}_dashboard.svgThe YAML manifest is the single source of truth for layout, widgets, field mappings, colors, and data source documentation. All customization happens there.
© SCStelz, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file in .github/skills/ai-agent-posture of SCStelz/security-investigator.
Open the folder on GitHubat commit 51e1385
AI Agent Posture next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| AI Agent Posture this skillSCStelz/security-investigator | 250 | — | ~21k | Automated safety check: Pass | MIT | |
| Workiq Copilotgithub/awesome-copilot | 40k | 1 repos | ~1.4k | Automated safety check: Pass | MIT | |
| Microsoft 365 Agents ToolkitOfficeDev/microsoft-365-agents-toolkit | 781 | — | ~2.8k | Automated safety check: Notes | Custom licence | |
| M365 Agents Pymicrosoft/skills | 3.1k | 5 repos | ~3.5k | Automated safety check: Notes | MIT | |
| M365 Agents Dotnetmicrosoft/skills | 3.1k | 5 repos | ~2.5k | Automated safety check: Pass | MIT | |
| M365 Agents TSmicrosoft/skills | 3.1k | 5 repos | ~1.7k | Automated safety check: Pass | MIT |
github/awesome-copilot
Guides the Copilot CLI on how to use the WorkIQ CLI/MCP server to query Microsoft 365 Copilot data (emails, meetings, docs, Teams, people) for live context, summaries, and recommendations.
OfficeDev/microsoft-365-agents-toolkit
Builds, tests, and deploys Microsoft 365 apps and agents for Teams and Copilot.
microsoft/skills
Microsoft 365 Agents SDK for Python. An agent skill from microsoft/skills.
microsoft/skills
Microsoft 365 Agents SDK for .NET. An agent skill from microsoft/skills.
microsoft/skills
Microsoft 365 Agents SDK for TypeScript/Node.js. An agent skill from microsoft/skills.
vinayaklatthe/microsoft-security-skills
Guidance for Microsoft Purview Data Security Posture Management for AI (DSPM for AI) - discovering, monitoring, and protecting sensitive data interactions with generative AI apps like Microsoft 365…
SCStelz/security-investigator
A skill your agent uses when asked to investigate Conditional Access policy changes, sign-in failures related to CA policies (error codes 53000, 50074, 530032), or suspected policy…
SCStelz/security-investigator
Weekly review of an investigation tenant-context memory file against the most recent SOC scan reports (e.g.
SCStelz/security-investigator
A skill your agent uses when asked to create heatmaps, visualize patterns over time, show activity grids, or display aggregated data in a matrix format.
SCStelz/security-investigator
Report/investigate RUNTIME ACTIVITY of AI agents (Agent 365 / Copilot Studio / M365 Copilot / Work IQ) — agents used, tools/connectors, channels, tokens, prompt/reply content, and Prompt Shield…
SCStelz/security-investigator
Audit Entra ID app registration and service principal security posture.
SCStelz/security-investigator
A skill your agent uses when asked to trace authentication flows, analyze SessionId chains, investigate token reuse vs interactive MFA, or assess geographic anomalies in sign-ins.
Works with
Audit or report on AI agent security posture across Copilot Studio, Microsoft 365 Copilot, Microsoft Foundry, and third-party agents. AI Agent Posture is an agent skill from SCStelz/security-investigator. Audit or report on AI agent security posture across Copilot Studio, Microsoft 365 Copilot, Microsoft Foundry, and third-party agents.
AI Agent Posture fits situations like: AI agent posture; agent security audit; copilot Studio agents; agent inventory.
Run `npx skills add SCStelz/security-investigator --skill ai-agent-posture -a claude-code`. Or copy the skill folder (.github/skills/ai-agent-posture in SCStelz/security-investigator) into .claude/skills/ai-agent-posture in your project. Claude Code loads it when a task matches its description.
Run `npx skills add SCStelz/security-investigator --skill ai-agent-posture -a codex`. Or copy the skill folder (.github/skills/ai-agent-posture in SCStelz/security-investigator) into .agents/skills/ai-agent-posture in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add SCStelz/security-investigator --skill ai-agent-posture -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/ai-agent-posture, .gemini/skills/ai-agent-posture, .github/skills/ai-agent-posture and .opencode/skills/ai-agent-posture in your project.
SKILL.md names no scripts, command-line tools or credentials: AI Agent Posture is instructions for the agent only.
SKILL.md names 2 domains. In commands or code: learn.microsoft.com and microsoft.com; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
AI Agent Posture is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 21k tokens (SKILL.md is roughly 83k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with AI Agent Posture: Workiq Copilot (github/awesome-copilot, 40k stars), Microsoft 365 Agents Toolkit (OfficeDev/microsoft-365-agents-toolkit, 781 stars), M365 Agents Py (microsoft/skills, 3.1k stars) and M365 Agents Dotnet (microsoft/skills, 3.1k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
SCStelz (a GitHub user) maintains it in SCStelz/security-investigator, which has 250 GitHub stars. The repository holds 22 skills in this directory. The repository was last updated on October 8, 2026.
Source: SCStelz/security-investigator on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.