Query Details

Custom Agents - Observed MCP call to an unapproved target

Custom Unapproved Mcp Server Execution

Query

let Evidence=materialize(CustomAgentInlineContent(1h));
CustomAgentSpans(1h)
| where Classification !in ('Excluded managed platform', 'Unattributed GenAI') and HasLineage
| where IsTool and ExplicitMcp and Approval == 'UNAPPROVED'
| extend Signal='Observed explicit MCP tool span with an unapproved exact target'
| extend TimeGenerated=EventTime, ResourceId=_ResourceId, TraceId=OperationId,
    EventCount=tolong(column_ifexists('EventCount', 1)),
    SuccessfulCalls=tolong(column_ifexists('SuccessfulCalls', 0)),
    FailedCalls=tolong(column_ifexists('FailedCalls', 0))
| project TimeGenerated, ResourceId, Agent, AgentId, AgentKey, AppRoleName,
    TraceId, SpanId, Conversation, Model, Tool, TargetHost, ServerDisplay,
    Signal, EventCount, SuccessfulCalls, FailedCalls,
    TriggerSpanId=tostring(column_ifexists('TriggerSpanId', '')),
    TriggerTime=todatetime(column_ifexists('TriggerTime', datetime(null))),
    TriggerTool=tostring(column_ifexists('TriggerTool', '')),
    TriggerToolResult=tostring(column_ifexists('TriggerToolResult', '')),
    TriggerServerDisplay=tostring(column_ifexists('TriggerServerDisplay', '')),
    TriggerTargetHost=tostring(column_ifexists('TriggerTargetHost', '')),
    RiskyOutputKind=tostring(column_ifexists('RiskyOutputKind', '')),
    RelatedToolResults=tolong(column_ifexists('RelatedToolResults', 0)),
    MatchedIntentPrompt=tostring(column_ifexists('IntentPrompt', '')),
    MatchedIntentSpanId=tostring(column_ifexists('IntentSpanId', ''))
| join kind=leftouter (Evidence
    | project ResourceId=_ResourceId, TraceId=OperationId, SpanId, AgentKey, Prompt, Output, ToolArguments, ToolResult, InputMessages, OutputMessages,
    EvidenceSource, PromptEvidenceOrigin, OutputEvidenceOrigin, PromptEvidenceSpanId, OutputEvidenceSpanId,
    InputParseState, OutputParseState, ToolArgumentsState, ToolResultState)
    on ResourceId, TraceId, SpanId, AgentKey
| extend Prompt=coalesce(MatchedIntentPrompt, Prompt),
    PromptEvidenceOrigin=iff(isnotempty(MatchedIntentPrompt), 'Matched intent span: user role', PromptEvidenceOrigin),
    PromptEvidenceSpanId=coalesce(MatchedIntentSpanId, PromptEvidenceSpanId)
| extend PreviewCharacters=toint(min_of(160, max_of(0, (1500 - string_size(tostring(bag_pack(
    'AgentId', AgentId, 'AgentKey', AgentKey, 'TraceId', TraceId, 'SpanId', SpanId,
    'Conversation', Conversation, 'ResourceId', ResourceId, 'Signal', Signal)))) / 12)))
| extend PromptPreview=substring(Prompt, 0, PreviewCharacters),
    OutputPreview=substring(Output, 0, PreviewCharacters)
| extend EvidencePreviewStatus=case(PreviewCharacters == 0, 'Metadata exhausted preview budget; inspect full query results',
    strlen(Prompt) > PreviewCharacters or strlen(Output) > PreviewCharacters, 'Inline evidence; previews shortened; full text in query results',
    isempty(Prompt) and isempty(Output), 'No prompt/output captured or parsed; not a protection verdict',
    'Inline evidence; full recorded text in query results')
| project TimeGenerated, ResourceId, Agent, AgentId, AgentKey, AppRoleName,
    TraceId, SpanId, Conversation, Model, Tool, TargetHost, ServerDisplay,
    Signal, EventCount, SuccessfulCalls, FailedCalls, Prompt, Output, ToolArguments, ToolResult, InputMessages, OutputMessages,
    EvidenceSource, PromptEvidenceOrigin, OutputEvidenceOrigin, PromptEvidenceSpanId, OutputEvidenceSpanId,
    InputParseState, OutputParseState, ToolArgumentsState, ToolResultState,
    PromptPreview, OutputPreview, EvidencePreviewStatus, TriggerSpanId, TriggerTime, TriggerTool, TriggerToolResult,
    TriggerServerDisplay, TriggerTargetHost, RiskyOutputKind, RelatedToolResults
| order by TimeGenerated desc

Explanation

This query is part of a scheduled alert rule in Azure Sentinel designed to detect when a custom agent makes a Managed Control Plane (MCP) call to an unapproved target. Here's a simplified breakdown:

  • Purpose: The query identifies instances where a custom agent explicitly uses MCP tools to interact with targets that are not approved, potentially indicating unauthorized activities.

  • Data Sources: It requires data from Application Insights, specifically from "AppRequests" and "AppDependencies".

  • Frequency and Duration: The query runs every hour and looks back over the past 14 days.

  • Severity: The alert generated by this query is considered to have a "Medium" severity level.

  • Query Logic:

    • It first collects evidence of custom agent activities over the past hour.
    • It filters out activities that are not relevant, focusing on those with explicit MCP usage and unapproved targets.
    • It enriches the data with additional details like event counts, success and failure of calls, and various identifiers.
    • It joins this data with additional evidence to provide context, such as prompts and outputs related to the activity.
    • It generates previews of the prompts and outputs to help quickly assess the situation.
  • Output: The results are ordered by the time they were generated, and they include detailed information about the agent, the tool used, the target, and the evidence collected.

  • Entity Mappings: The query maps certain fields to Azure resources and cloud applications for better integration with Azure Sentinel's entity model.

  • Custom Details and Grouping: It includes custom details for better identification and groups alerts by specific keys to manage incidents effectively.

  • Incident Handling: If an alert is triggered, it will create an incident in Azure Sentinel, with specific configurations for grouping and managing these incidents.

  • Version and Tags: The rule is versioned as "1.2.0" and tagged for categorization and management purposes.

Overall, this query is part of a security monitoring setup to ensure that custom agents are not making unauthorized MCP calls, helping to maintain the security and integrity of the system.