Share via


Build a runtime threat detection system for Copilot Studio agents

Organizations can add a layer of security to their Copilot Studio agents by connecting them to a runtime threat detection system. Once connected, the agent calls this system at runtime. The agent provides the system with data so that the system can determine if a tool the agent plans to invoke is legitimate or not. The system then replies to Copilot Studio with a response of either "approve" or "block," causing the agent to invoke or skip the tool accordingly. For more information on how to connect agents to an existing external threat detection system, see Enable external threat detection and protection for Copilot Studio custom agents.

This article is targeted at developers, and describes how to integrate your own threat detection capabilities as a security provider for Copilot Studio agents.

The integration is based on an API consisting of two endpoints. The main endpoint you need to implement is the analyze-tool-execution endpoint. You need to expose this endpoint as an interface to your threat detection system. Once customers configure your system as their external threat detection system, the agent calls this API every time it intends to invoke a tool.

Aside from the analyze-tool-execution endpoint, you also need to expose a second endpoint, called validate. The validate endpoint is used to check the health and readiness of the endpoint as part of the system setup.

The following sections describe each endpoint in detail.

POST /validate

Purpose: Verifies that the threat detection endpoint is reachable and functioning. Used for initial setup and configuration testing.

Validate request

  • Method: POST

  • URL: https://{threat detection endpoint}/validate?api-version=2025-05-01

  • Headers:

    • Authorization: Bearer token for API authentication

    • x-ms-correlation-id: GUID for tracing

  • Body: Empty

Validate response

200 OK response example

{
  "isSuccessful": true,
  "status": "OK"
}

Error response example

If an error occurs (unsuccessful HTTP code), the endpoint returns an error code, a message, and optional diagnostics.

{
  "errorCode": 5031,
  "message": "Validation failed. Webhook service is temporarily unavailable.",
  "httpStatus": 503,
  "diagnostics": "{\\reason\\:\\Upstream dependency timeout\\}"
}

POST /analyze-tool-execution

Purpose: Submits tool execution context for risk evaluation. Evaluates the tool execution request and responds whether to allow or block the tool execution.

Analyze-tool-execution request

  • Method: POST

  • URL: https://{threat detection endpoint}/analyze-tool-execution?api-version=2025-05-01

  • Headers:

    • Authorization: Bearer token for API authentication
    • Content-Type: application/json
  • Body: JSON object

Example analyze-tool-execution request

POST https://security.contoso.com/api/agentSecurity/analyze-tool-execution?api-version=2025-05-01
Authorization: Bearer XXX……
x-ms-correlation-id: fbac57f1-3b19-4a2b-b69f-a1f2f2c5cc3c
Content-Type: application/json

{
  "plannerContext": {
    "userMessage": "Send an email to the customer",
    "thought": "User wants to notify customer",
    "chatHistory": [
      {
        "id": "m1",
        "role": "user",
        "content": "Send an email to the customer",
        "timestamp": "2025-05-25T08:00:00Z"
      },
      {
        "id": "m2",
        "role": "assistant",
        "content": "Which customer should I email?",
        "timestamp": "2025-05-25T08:00:01Z"
      },
      {
        "id": "m3",
        "role": "user",
        "content": "The customer is John Doe",
        "timestamp": "2025-05-25T08:00:02Z"
      }
    ],
    "previousToolOutputs": [
      {
        "toolId": "tool-123",
        "toolName": "Get customer email by name",
        "outputs": {
          "name": "email",
          "description": "Customer's email address",
          "type": {
            "$kind": "String"
          },
          "value": "customer@foobar.com"
        },
        "timestamp": "2025-05-25T08:00:02Z"
      }
    ]
  },
  "toolDefinition": {
    "id": "tool-123",
    "type": "PrebuiltToolDefinition",
    "name": "Send email",
    "description": "Sends an email to specified recipients.",
    "inputParameters": [
      {
        "name": "to",
        "description": "Receiver of the email",
        "type": {
          "$kind": "String"
        }
      },
      {
        "name": "bcc",
        "description": "BCC of the email",
        "type": {
          "$kind": "String"
        }
      }
    ],
    "outputParameters": [
      {
        "name": "result",
        "description": "Result",
        "type": {
          "$kind": "String"
        }
      }
    ]
  },
  "inputValues": {
    "to": "customer@foobar.com",
    "bcc": "hacker@evil.com"
  },
  "conversationMetadata": {
    "agent": {
      "id": "agent-guid",
      "tenantId": "tenant-guid",
      "environmentId": "env-guid",
      "isPublished": true
    },
    "user": {
      "id": "user-guid",
      "tenantId": "tenant-guid"
    },
    "trigger": {
      "id": "trigger-guid",
      "schemaName": "trigger-schema"
    },
    "conversationId": "conv-id",
    "planId": "plan-guid",
    "planStepId": "step-1"
  }
}

Analyze-tool-execution response

200 OK

When the request is valid, the tool use specified in the request is evaluated and either allowed or blocked, based on the defined criteria. The response can include the following fields:

  • blockAction (Boolean): Whether the action should be blocked
  • reasonCode (integer, optional): Numeric code explaining the reason for the block
  • reason (string, optional): Human-readable explanation
  • diagnostics (object, optional): Other details for tracing or debugging

Example allow response

{
  "blockAction": false
}

Example block response

{
  "blockAction": true,
  "reasonCode": 112,
  "reason": "The action was blocked because there is a noncompliant email address in the BCC field.",
  "diagnostics": "{\\flaggedField\\:\\bcc\\,\\flaggedValue\\:\\hacker@evil.com\\}"
}

Example error response

If the request isn't valid, an error response is returned with an error code, message, HTTP status, and optional diagnostics.

{
  "errorCode": 4001,
  "message": "Missing required field: toolDefinition",
  "httpStatus": 400,
  "diagnostics": "{\\missingField\\:\\toolDefinition\\,\\traceId\\:\\abc-123\\}"
}

Request and response body structures reference

The following tables describe the contents of various objects used within the request and response bodies for the endpoints.

ValidationResponse

Name Type Required Description
isSuccessful Boolean Yes Indicates whether the validation passed.
status string Yes Optional status message or partner-specific detail.

AnalyzeToolExecutionResponse

Name Type Required Description
blockAction Boolean Yes Indicates whether the action should be blocked.
reasonCode integer No Optional numeric reason code, determined by partner.
reason string No Optional human-readable explanation.
diagnostics string No Optional freeform diagnostic info for debugging or telemetry. Must be preserialized.

ErrorResponse

Name Type Required Description
errorCode integer Yes Numeric identifier for the error (for example, 1001 = missing field, 2003 = auth failure).
message string Yes Human-readable explanation of the error.
httpStatus integer Yes HTTP status code returned by the partner.
diagnostics string No Optional freeform diagnostic info for debugging or telemetry. Must be preserialized.

EvaluationRequest

Name Type Required Description
plannerContext PlannerContext Yes Planner context data.
toolDefinition ToolDefinition Yes Tool definition details.
inputValues JSON object Yes Dictionary of key-value pairs provided to the tool.
conversationMetadata ConversationMetadata Yes Metadata about the conversation context, user, and plan tracking.

PlannerContext

Name Type Required Description
userMessage string Yes The original message sent by the agent.
thought string No Planner explanation for why this tool was selected.
chatHistory ChatMessage[] No List of recent chat messages exchanged with the user.
previousToolsOutputs ToolExecutionOutput[] No List of recent tool outputs.

ChatMessage

Name Type Required Description
id string Yes Unique identifier for this message in the conversation.
role string Yes Source of the message (for example, user, assistant).
content string Yes The message text.
timestamp string (date-time) No ISO 8601 time stamp indicating when the message was sent.

ToolExecutionOutputs

Name Type Required Description
toolId string Yes Unique identifier for this message in the conversation.
toolName string Yes Name of the tool.
outputs ExecutionOutput[] Yes List of the tool execution outputs.
timestamp string (date-time) No ISO 8601 time stamp indicating when the tool execution was finished.

ExecutionOutput

Name Type Required Description
name string Yes Name of the output parameter.
description string No Explanation for the output value.
type object No Data type of the output.
value JSON data value Yes The output value.

ToolDefinition

Name Type Required Description
id string Yes Unique identifier of the tool.
type string Yes Specifies the kind of tool used in the planner.
name string Yes Human-readable name of the tool.
description string Yes Summary of what the tool does.
inputParameters ToolInput[] No Input parameters of the tool.
outputParameters ToolOutput[] No Output parameters the tool returns after execution.

ToolInput

Name Type Required Description
name string Yes Name of the input parameter.
description string No Explanation of the expected value for this input parameter.
type JSON object No Data type of the input parameter.

ToolOutput

Name Type Required Description
name string Yes Name of the output parameter.
description string No Explanation of the output value.
type JSON object No Type of the output value.

ConversationMetadata

Name Type Required Description
agent AgentContext Yes Agent context information.
user UserContext No Information about the user interacting with the agent.
trigger TriggerContext No Information about what triggered the planner execution.
conversationId string Yes ID of the ongoing conversation.
planId string No ID of the plan used to fulfill the user request.
planStepId string No Step within the plan corresponding to this tool execution.
parentAgentComponentId string No ID of the parent agent component.

AgentContext

Name Type Required Description
id string Yes ID of the agent.
tenantId string Yes Tenant where the agent resides.
environmentId string Yes Environment in which the agent is published.
version string No Agent version (optional if isPublished is false).
isPublished Boolean Yes Whether this execution context is a published version.

UserContext

Name Type Required Description
id string No Microsoft Entra object ID of the user.
tenantId string No Tenant ID of the user.

TriggerContext

Name Type Required Description
id string No The id of the trigger that triggered the planner.
schemaName string No The name of the trigger schema that triggered the planner.

Authentication

The integration you develop should use Microsoft Entra ID authentication. Follow instructions on Integrate apps your developers build.

Steps to perform include the following:

  • Create an app registration for your resource in your tenant.
  • Expose a scope for your web API. The exposed scope must be the base URL for the resource the customers call. For example, if the API URL is https://security.contoso.com/api/threatdetection, then the exposed scope must be https://security.contoso.com.
  • Depending on how you implement your service, you need to implement authorization logic and validate incoming tokens. You need to document how the customer must authorize their apps. There are multiple ways of doing that, for example, using an allowlist of app IDs, or role-based access control (RBAC).

Response time requirements

The agent expects a response from the threat detection system within less than 1,000 ms. You should ensure your endpoint replies to the call within this time frame. If your system doesn't respond in time, the agent behaves as if your response is "allow," invoking the tool.

API Versioning

In requests, the API version is specified via a api-version query parameter (for example, api-version=2025-05-01). Your implementation should be tolerant of other unexpected fields and shouldn't fail if new values are added in the future. Partners shouldn't verify the API version, as all the versions at the moment are considered non-breaking. Partners should track the API versions but not fail the request on seeing a new version.