From c5437829fa5936659d2e3c23b0c00893d54cb234 Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Mon, 6 Jul 2026 16:59:05 +0000 Subject: [PATCH 1/2] Add security information to harness features inline docs --- .../core/agent_framework/_compaction.py | 16 +++++++- .../core/agent_framework/_harness/_agent.py | 7 ++++ .../_harness/_background_agents.py | 13 +++++++ .../core/agent_framework/_harness/_loop.py | 14 +++++++ .../packages/core/agent_framework/_skills.py | 38 +++++++++++++++++++ .../core/agent_framework/observability.py | 11 ++++++ python/samples/02-agents/compaction/README.md | 11 ++++++ python/samples/02-agents/harness/README.md | 24 ++++++++++++ python/samples/02-agents/middleware/README.md | 9 +++++ .../samples/02-agents/observability/README.md | 22 ++++++++--- .../skills/mcp_based_skill/README.md | 10 +++++ 11 files changed, 169 insertions(+), 6 deletions(-) diff --git a/python/packages/core/agent_framework/_compaction.py b/python/packages/core/agent_framework/_compaction.py index 3644bf4a9cc..00c610a0a98 100644 --- a/python/packages/core/agent_framework/_compaction.py +++ b/python/packages/core/agent_framework/_compaction.py @@ -955,6 +955,17 @@ class SummarizationStrategy: ``target_count`` (subject to atomic group boundaries). It writes trace metadata in both directions: summary -> original message/group IDs and original -> summary ID. + + Security considerations: + Unlike strategies that only remove or reorder existing messages (which carry no additional + risk), this strategy calls out to an LLM to produce replacement summary content that + permanently becomes part of chat history and is trusted the same as any other assistant + message going forward. Using it is an explicit opt-in — it must be constructed with a + summarization ``client``. A compromised or malicious summarization service could therefore + return a summary containing unsafe instructions, which become a persistent part of the + conversation — a form of indirect prompt injection that survives beyond the turn in which it + was introduced. Only point ``client`` at a summarization service you trust as much as the + primary model. """ def __init__( @@ -969,7 +980,10 @@ def __init__( Keyword Args: client: A chat client compatible with ``SupportsChatGetResponse`` - used to generate summary text. + used to generate summary text. **Security:** its output permanently replaces the + original messages in chat history, so only use a summarization service you trust as + much as the primary model — see the class-level security considerations for the + indirect-prompt-injection risk of an untrusted summarizer. target_count: Target number of included non-system messages to retain after summarization. Must be greater than 0. threshold: Extra included non-system messages allowed above diff --git a/python/packages/core/agent_framework/_harness/_agent.py b/python/packages/core/agent_framework/_harness/_agent.py index 107f2708037..53b32296aeb 100644 --- a/python/packages/core/agent_framework/_harness/_agent.py +++ b/python/packages/core/agent_framework/_harness/_agent.py @@ -401,6 +401,9 @@ def create_harness_agent( (default), they require approval. Ignored when disable_file_access is True. skills_provider: Custom SkillsProvider instance for code-defined skills. Can be combined with ``skills_paths`` to aggregate file and code-based skills. + **Security:** if the provider is configured with an external skill source (e.g. + :class:`~agent_framework.MCPSkillsSource`), the skill content it loads is untrusted input + — only enable sources you trust; see :class:`~agent_framework.SkillsSource`. skills_paths: Paths for file-based skill discovery (looks for SKILL.md files). Accepts a single ``str`` or :class:`~pathlib.Path`, or a sequence of ``str | Path``. Can be combined with ``skills_provider``. When neither @@ -410,6 +413,10 @@ def create_harness_agent( When provided, a ``BackgroundAgentsProvider`` is automatically included, enabling the agent to start, monitor, and retrieve results from background tasks. Each agent must have a non-empty, unique name (case-insensitive). + **Security:** supplied agents receive text input from this agent and their output is fed + back into its context, so only supply agents you have vetted and trust — see + :class:`~agent_framework.BackgroundAgentsProvider` for the exfiltration and + prompt-injection risks of untrusted agents. background_agents_instructions: Optional instruction override for the ``BackgroundAgentsProvider``. May include ``{background_agents}`` placeholder which will be replaced with the agent listing. diff --git a/python/packages/core/agent_framework/_harness/_background_agents.py b/python/packages/core/agent_framework/_harness/_background_agents.py index 4bd7d65975a..22fac3bbd44 100644 --- a/python/packages/core/agent_framework/_harness/_background_agents.py +++ b/python/packages/core/agent_framework/_harness/_background_agents.py @@ -253,6 +253,15 @@ class BackgroundAgentsProvider(ContextProvider): - ``background_agents_get_all_tasks`` — List all background tasks with their IDs, statuses, and descriptions. - ``background_agents_continue_task`` — Send follow-up input to a completed task's session to resume work. - ``background_agents_clear_completed_task`` — Remove a completed task and release its session. + + Security considerations: + The agents passed to the constructor are delegated arbitrary work by the parent agent — the + parent sends them text input (which may include content derived from the parent's own + untrusted context) and receives back whatever text they produce. A compromised or malicious + supplied agent (for example, one with a compromised system prompt, tools, or upstream model) + could exfiltrate that input to an external system, or return adversarial output designed to + influence the parent agent via indirect prompt injection once its result is retrieved. Only + supply background agents you have vetted and trust with the data the parent may pass to them. """ def __init__( @@ -267,6 +276,10 @@ def __init__( Args: agents: Collection of background agents available for delegation. Each agent must have a non-empty, unique name (case-insensitive). + **Security:** each supplied agent should be vetted and trusted, since it will receive + text input from the parent agent and its output is fed back into the parent's + context — see the class-level security considerations for the exfiltration and + prompt-injection risks of untrusted agents. Keyword Args: source_id: Unique source ID for serializable task state in session. diff --git a/python/packages/core/agent_framework/_harness/_loop.py b/python/packages/core/agent_framework/_harness/_loop.py index 8f9a5e7be9e..7640d624cfd 100644 --- a/python/packages/core/agent_framework/_harness/_loop.py +++ b/python/packages/core/agent_framework/_harness/_loop.py @@ -364,8 +364,22 @@ def with_judge( why its previous answer was judged incomplete. See :meth:`__init__` for the full meaning of each argument. + Security considerations: + Using a judge is an explicit opt-in — the caller must supply a ``judge_client`` — and + introduces a second external LLM boundary in addition to the agent's own model. On every + iteration the judge is sent the original request and the agent's latest response, both of + which may contain sensitive or untrusted content. A compromised or malicious judge + endpoint could exfiltrate that data, or return a manipulated :class:`JudgeVerdict` / gap + analysis that is fed back into the loop as feedback, potentially steering the agent via + indirect prompt injection. Only configure a ``judge_client`` that points at a service you + trust as much as the primary model. + Args: judge_client: Chat client used to judge whether the original request was answered. + **Security:** this client is sent the original request and the agent's latest + response on every iteration, so only point it at a service you trust as much as the + primary model — see the security considerations above for the exfiltration and + prompt-injection risks of an untrusted judge. Keyword Args: criteria: Optional list of criteria the response must satisfy. When provided, they are diff --git a/python/packages/core/agent_framework/_skills.py b/python/packages/core/agent_framework/_skills.py index f33dfb2aaf5..92844fb55ed 100644 --- a/python/packages/core/agent_framework/_skills.py +++ b/python/packages/core/agent_framework/_skills.py @@ -1850,6 +1850,17 @@ class SkillsProvider(ContextProvider): and file-based resource reads are guarded against path traversal and symlink escape. Only use skills from trusted sources. + **Security considerations (external skill sources):** which skills are + available, and how much trust to place in them, is entirely determined by + the :class:`SkillsSource` instances this provider is configured with — see + :class:`SkillsSource` for source-level trust-boundary guidance (this + includes external sources such as skills discovered over MCP via + :class:`MCPSkillsSource`). Skill content (names, descriptions, and full + bodies loaded via ``load_skill``) is injected into the agent's context + as-is, so a compromised or adversarial source can attempt indirect prompt + injection, and the ``run_skill_script`` tool executes scripts supplied by + the source. Only enable script-capable or external sources you trust. + **Tool approval:** every tool exposed by this provider (``load_skill``, ``read_skill_resource``, and ``run_skill_script``) is registered with ``approval_mode="always_require"``, so each skill operation @@ -2567,6 +2578,20 @@ class SkillsSource(ABC): are discovered (filesystem, memory, network, etc.). Subclass this to create custom skill sources. + + Security considerations: + A skill source is a trust boundary. The skills it returns — their + names, descriptions, instructions, and any scripts or resources — are + injected into the agent's context and tool surface, and may be + executed (for sources that support script execution). Skills only + reach the agent when a source is explicitly registered, so this is + opt-in. Sources that read from a remote or third-party origin (e.g. a + remote MCP server via :class:`MCPSkillsSource`, a shared filesystem, or + a database) can be compromised or adversarial, and may return skill + content designed to manipulate the agent (indirect prompt injection) or + to exfiltrate data through instructions or scripts the agent is induced + to run. Only register skill sources for origins you trust, and evaluate + the content they can return before enabling them in production. """ @abstractmethod @@ -3906,6 +3931,19 @@ class MCPSkillsSource(SkillsSource): If ``skill://index.json`` is absent, unreadable, empty, or fails to parse, this source returns an empty list. + Security considerations: + Discovering skills over MCP means an *external* MCP server controls + what skill content (including instructions and, for script-capable + skills, the scripts the agent may run) reaches the agent. This source + is never enabled by default; it is only used when the caller connects + it to a server explicitly. A compromised, malicious, or simply + untrustworthy server can return adversarial skill content designed to + manipulate the agent through indirect prompt injection, or + instructions/scripts designed to exfiltrate data once loaded and, for + script-capable skills, executed. Only connect this source to MCP + servers you have vetted and trust, and treat their responses as + untrusted input. + Examples: .. code-block:: python diff --git a/python/packages/core/agent_framework/observability.py b/python/packages/core/agent_framework/observability.py index 8bbde33e7da..347cd1940ac 100644 --- a/python/packages/core/agent_framework/observability.py +++ b/python/packages/core/agent_framework/observability.py @@ -692,6 +692,17 @@ class ObservabilitySettings: Warning: Sensitive events should only be enabled on test and development environments. + Security considerations: + Agent Framework emits telemetry via the standard OpenTelemetry APIs — it does not itself + contact any external system. Where that telemetry is sent (a local collector, a hosted + observability backend, the VS Code extension port, etc.) is entirely determined by the + exporters and pipeline the developer configures. By default, emitted telemetry is limited to + metadata (e.g. token counts, operation names, durations) and does not include message + content. Enabling ``enable_sensitive_data`` (env var ``ENABLE_SENSITIVE_DATA``) is an + explicit, separate opt-in that additionally emits raw chat message content, function-call + arguments, and function-call results — treat that data as sensitive and ensure it is not sent + to, or retained by, a telemetry backend you have not secured appropriately. + Keyword Args: enable_instrumentation: Enable OpenTelemetry diagnostics. Default is True. Can be disabled by setting environment variable ENABLE_INSTRUMENTATION=false. diff --git a/python/samples/02-agents/compaction/README.md b/python/samples/02-agents/compaction/README.md index 42806bd6dec..779893eefeb 100644 --- a/python/samples/02-agents/compaction/README.md +++ b/python/samples/02-agents/compaction/README.md @@ -23,3 +23,14 @@ uv run samples/02-agents/compaction/custom.py uv run samples/02-agents/compaction/tiktoken_tokenizer.py uv run samples/02-agents/compaction/compaction_provider.py # requires OPENAI_API_KEY ``` + +## Security Considerations + +Most compaction strategies in this folder (`TruncationStrategy`, `SlidingWindowStrategy`, +`SelectiveToolCallCompactionStrategy`, `ToolResultCompactionStrategy`) only remove or reorder +existing messages and carry no additional risk. `SummarizationStrategy` is the exception: it +calls out to an LLM to produce replacement summary content that permanently becomes part of +chat history. A compromised or malicious summarization service could return a summary +containing unsafe instructions, creating a persistent indirect-prompt-injection vector. Using +`SummarizationStrategy` is optional and requires explicit configuration — only point its +chat client at a summarization service you trust as much as the primary model. diff --git a/python/samples/02-agents/harness/README.md b/python/samples/02-agents/harness/README.md index 62f30f27536..4bda12e4032 100644 --- a/python/samples/02-agents/harness/README.md +++ b/python/samples/02-agents/harness/README.md @@ -138,3 +138,27 @@ async with LocalShellTool(acknowledge_unsafe=True) as shell: ) ``` + +## Security Considerations + +Several harness capabilities extend the agent's trust boundary to external systems the developer +configures. Each is opt-in and requires explicit configuration by the developer, who is responsible +for vetting the external service, agent, skill source, or provider before enabling it: + +- **`background_agents`** (`BackgroundAgentsProvider`) — delegates work to developer-supplied agents, + which receive input from the parent and whose output is fed back into its context. A compromised + agent could exfiltrate data or inject adversarial content via indirect prompt injection. Vet all + supplied agents. +- **External skill sources** (`skills_provider` with e.g. `MCPSkillsSource`) — load skill content, + and potentially scripts, from a remote source. A compromised source could return adversarial skills + (indirect prompt injection) or exfiltrate data. Only enable sources you trust. +- **`AgentLoopMiddleware.with_judge`** — sends the request and the agent's latest response to a second, + external judge chat client on every iteration. A compromised judge could exfiltrate that data or + return manipulated feedback. Trust the judge as much as the primary model. +- **`SummarizationStrategy`** (via `before_compaction_strategy` / `after_compaction_strategy`) — calls + out to an LLM whose output permanently becomes chat history. A compromised summarization service + could inject unsafe, persistent instructions. Only use a service you trust as much as the primary + model. +- **Telemetry** — when observability is enabled, telemetry destinations are developer-configured. + Default telemetry is metadata only; enabling sensitive data additionally emits raw message content, + tool arguments, and tool results. See the [observability samples](../observability/README.md). diff --git a/python/samples/02-agents/middleware/README.md b/python/samples/02-agents/middleware/README.md index cf9eb4ebe50..3a272bd23af 100644 --- a/python/samples/02-agents/middleware/README.md +++ b/python/samples/02-agents/middleware/README.md @@ -39,3 +39,12 @@ uv run samples/02-agents/middleware/usage_tracking_middleware.py ``` The sample forces a tool call so you can see middleware output for each inner model call in both non-streaming and streaming modes. + +## Security Considerations + +`AgentLoopMiddleware.with_judge` (used by `agent_loop_middleware_judge.py` and +`agent_loop_middleware_report.py`) is an explicit opt-in to sending the original request and the +agent's latest response to a second, external judge chat client on every iteration. A compromised +or malicious judge endpoint could exfiltrate that data, or return a manipulated verdict/gap +analysis that gets fed back into the loop as feedback — a form of indirect prompt injection. Only +configure a judge client that points at a service you trust as much as the primary model. diff --git a/python/samples/02-agents/observability/README.md b/python/samples/02-agents/observability/README.md index d6a251eef59..2d3eaa6ee46 100644 --- a/python/samples/02-agents/observability/README.md +++ b/python/samples/02-agents/observability/README.md @@ -2,16 +2,16 @@ These samples show how to send Agent Framework observability data to the Application Performance Management (APM) backend of your choice, based on the OpenTelemetry standard. -The samples target [Application Insights](https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview), the [Aspire Dashboard](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/overview?tabs=bash), and the console, but any OTLP-compatible backend works. +The samples target [Application Insights](https://learn.microsoft.com/azure/azure-monitor/app/app-insights-overview), the [Aspire Dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/overview?tabs=bash), and the console, but any OTLP-compatible backend works. -> **Quick Start**: For local development without Azure setup, use the [Aspire Dashboard](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/standalone) (runs locally via Docker), or the built-in tracing module of the [AI Toolkit for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-windows-ai-studio.windows-ai-studio). +> **Quick Start**: For local development without Azure setup, use the [Aspire Dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) (runs locally via Docker), or the built-in tracing module of the [AI Toolkit for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-windows-ai-studio.windows-ai-studio). > Other backends such as [Prometheus](https://prometheus.io/docs/introduction/overview/) are also supported. See the [OpenTelemetry Python exporters](https://opentelemetry.io/docs/languages/python/exporters/) page for the full list. For more information, please refer to the following resources: 1. [Azure Monitor OpenTelemetry Exporter](https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/monitor/azure-monitor-opentelemetry-exporter) -2. [Aspire Dashboard for Python Apps](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/standalone-for-python?tabs=flask%2Cwindows) +2. [Aspire Dashboard for Python Apps](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone-for-python?tabs=flask%2Cwindows) 3. [AI Toolkit for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-windows-ai-studio.windows-ai-studio) 4. [Python Logging](https://docs.python.org/3/library/logging.html) 5. [Observability in Python](https://www.cncf.io/blog/2022/04/22/opentelemetry-and-python-a-complete-instrumentation-guide/) @@ -418,7 +418,7 @@ enable_sensitive_telemetry() ## Aspire Dashboard -The [Aspire Dashboard](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/standalone) is a local telemetry viewing tool that provides an excellent experience for viewing OpenTelemetry data without requiring Azure setup. +The [Aspire Dashboard](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/standalone) is a local telemetry viewing tool that provides an excellent experience for viewing OpenTelemetry data without requiring Azure setup. ### Setting up Aspire Dashboard with Docker @@ -456,4 +456,16 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 python configure_otel_provider > Make sure you have the dashboard running to receive telemetry data. -Once your sample finishes running, navigate to in a web browser to see the telemetry data. Follow the [Aspire Dashboard exploration guide](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/explore) to authenticate to the dashboard and start exploring your traces, logs, and metrics! +Once your sample finishes running, navigate to in a web browser to see the telemetry data. Follow the [Aspire Dashboard exploration guide](https://learn.microsoft.com/dotnet/aspire/fundamentals/dashboard/explore) to authenticate to the dashboard and start exploring your traces, logs, and metrics! + +## Security Considerations + +Agent Framework emits telemetry via the standard OpenTelemetry APIs — it does not itself +contact any external system. Where that telemetry is sent (a local collector, a hosted +observability backend, the VS Code extension port, etc.) is entirely determined by the +exporters and pipeline the developer configures. By default, emitted telemetry is limited to +metadata (e.g. token counts, operation names, durations) and does not include message content. +Enabling sensitive data (`ENABLE_SENSITIVE_DATA` / `enable_sensitive_data`) is an explicit, +separate opt-in that additionally emits raw chat message content, function-call arguments, and +function-call results — treat that data as sensitive and only send it to a telemetry backend +you have secured appropriately. diff --git a/python/samples/02-agents/skills/mcp_based_skill/README.md b/python/samples/02-agents/skills/mcp_based_skill/README.md index 994fa2d7fba..0f7f7ff2d66 100644 --- a/python/samples/02-agents/skills/mcp_based_skill/README.md +++ b/python/samples/02-agents/skills/mcp_based_skill/README.md @@ -49,3 +49,13 @@ resources (`skill://index.json` plus per-skill `SKILL.md`). - The Model Context Protocol working group maintains reference MCP-skills servers at [`modelcontextprotocol/experimental-ext-skills`](https://github.com/modelcontextprotocol/experimental-ext-skills). + +## Security Considerations + +Discovering skills over MCP means an *external* MCP server controls what skill content +(including instructions and, for script-capable skills, the scripts the agent may run) +reaches the agent. A compromised or untrustworthy server could return adversarial content +designed to manipulate the agent (indirect prompt injection) or to exfiltrate data through +skill instructions/scripts. This source is never enabled by default — connecting +`MCPSkillsSource` to a server is an explicit opt-in. Only connect to MCP servers you have +vetted and trust, and treat their responses as untrusted input. From b98f5c757e89039a19071a36e0f5e0a7bb82a31f Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:16:17 +0000 Subject: [PATCH 2/2] Address PR comments --- python/samples/02-agents/observability/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/python/samples/02-agents/observability/README.md b/python/samples/02-agents/observability/README.md index 2d3eaa6ee46..a94a199a535 100644 --- a/python/samples/02-agents/observability/README.md +++ b/python/samples/02-agents/observability/README.md @@ -465,7 +465,8 @@ contact any external system. Where that telemetry is sent (a local collector, a observability backend, the VS Code extension port, etc.) is entirely determined by the exporters and pipeline the developer configures. By default, emitted telemetry is limited to metadata (e.g. token counts, operation names, durations) and does not include message content. -Enabling sensitive data (`ENABLE_SENSITIVE_DATA` / `enable_sensitive_data`) is an explicit, -separate opt-in that additionally emits raw chat message content, function-call arguments, and -function-call results — treat that data as sensitive and only send it to a telemetry backend +Enabling sensitive-data capture — via `enable_sensitive_telemetry()` or the +`ENABLE_SENSITIVE_DATA` environment variable — is an explicit, separate opt-in that +additionally emits raw chat message content, function-call arguments, and function-call +results — treat that data as sensitive and only send it to a telemetry backend you have secured appropriately.