From 376306d4ffc607b909671fa72f9f81735e7d2b60 Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Fri, 23 Oct 2026 14:15:34 +0200 Subject: [PATCH 1/2] docs(python): clarify prompt template trust boundary Document that template source can invoke registered kernel functions and add a regression test showing template-like argument values remain literal. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- python/semantic_kernel/agents/agent.py | 3 ++- .../prompt_template/kernel_prompt_template.py | 6 ++++- .../semantic_kernel/template_engine/README.md | 22 +++++++++++++++++++ .../test_kernel_prompt_template.py | 21 ++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/python/semantic_kernel/agents/agent.py b/python/semantic_kernel/agents/agent.py index f9d236ab26c9..a941f9897441 100644 --- a/python/semantic_kernel/agents/agent.py +++ b/python/semantic_kernel/agents/agent.py @@ -257,7 +257,8 @@ class Agent(KernelBaseModel, ABC): description: The description of the agent id: The unique identifier of the agent If no id is provided, a new UUID will be generated. - instructions: The instructions for the agent (optional) + instructions: The instructions for the agent (optional). Instructions are rendered as a prompt template and + may invoke functions registered in the kernel, so the template source must be trusted. kernel: The kernel instance for the agent name: The name of the agent prompt_template: The prompt template for the agent diff --git a/python/semantic_kernel/prompt_template/kernel_prompt_template.py b/python/semantic_kernel/prompt_template/kernel_prompt_template.py index 9a7eeccd1763..734749eb2b97 100644 --- a/python/semantic_kernel/prompt_template/kernel_prompt_template.py +++ b/python/semantic_kernel/prompt_template/kernel_prompt_template.py @@ -25,7 +25,11 @@ class KernelPromptTemplate(PromptTemplateBase): - """Create a Kernel prompt template.""" + """Create a Kernel prompt template. + + Template source is executable configuration: function expressions invoke functions registered in the Kernel + during rendering. Use only trusted template source and pass untrusted content through template arguments. + """ _blocks: list[Block] = PrivateAttr(default_factory=list) diff --git a/python/semantic_kernel/template_engine/README.md b/python/semantic_kernel/template_engine/README.md index 163ac319e533..b40119a09ee2 100644 --- a/python/semantic_kernel/template_engine/README.md +++ b/python/semantic_kernel/template_engine/README.md @@ -1,5 +1,27 @@ # Prompt Template Engine +## Security + +Semantic Kernel prompt templates are executable configuration. Function expressions such as +`{{plugin.function}}` invoke functions registered in the `Kernel` while the template is rendered, before the model +request. Use template source only from trusted sources. + +Pass user-, network-, file-, or service-provided text as a template argument instead of inserting it into the template +source. Argument values are rendered as text and are not parsed as template expressions: + +```python +arguments = KernelArguments(input=untrusted_text) +template = KernelPromptTemplate( + prompt_template_config=PromptTemplateConfig(template="User input: {{$input}}") +) +rendered = await template.render(kernel, arguments) +``` + +Content-escaping settings, including `allow_dangerously_set_content`, do not disable function execution from template +source. + +## Grammar + The Semantic Kernel uses the following grammar to parse prompt templates: ``` diff --git a/python/tests/unit/prompt_template/test_kernel_prompt_template.py b/python/tests/unit/prompt_template/test_kernel_prompt_template.py index 6d8b1a1b2bf0..66032d66879c 100644 --- a/python/tests/unit/prompt_template/test_kernel_prompt_template.py +++ b/python/tests/unit/prompt_template/test_kernel_prompt_template.py @@ -81,6 +81,27 @@ def my_function(arguments: KernelArguments) -> str: assert result == "foo-F(INPUT-BAR)-baz" +async def test_it_renders_template_syntax_in_arguments_as_literal_text(kernel: Kernel): + calls = 0 + + @kernel_function(name="function") + def my_function() -> str: + nonlocal calls + calls += 1 + return "unexpected" + + func = KernelFunction.from_method(my_function, "test") + assert func is not None + kernel.add_function("test", func) + + template_syntax = "{{test.function}}" + target = create_kernel_prompt_template("{{$input}}") + result = await target.render(kernel, KernelArguments(input=template_syntax)) + + assert result == template_syntax + assert calls == 0 + + async def test_it_renders_code_using_variables(kernel: Kernel): arguments = KernelArguments() From ed93d4455441b9d03aff9ddfa531904d90d1012d Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Fri, 23 Oct 2026 19:37:31 +0200 Subject: [PATCH 2/2] chore: retrigger CI Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>