Skip to content

Python: NAMED_ARG_REGEX character-class bug [${1}] lets non-$-prefixed values ({, 1, }) be silently parsed as variable references #14491

Description

What happens

NamedArgBlock's parsing regex, NAMED_ARG_REGEX, is supposed to require a named argument's variable form to start with a literal $ (per the grammar: [parameter] ::= [variable] | [value] and [variable] ::= "$" [valid-name]). Because of a regex mistake, it instead accepts a variable reference whose first character is $, {, 1, or } and always treats the rest as the variable name, silently dropping that first character. This means unquoted, non-$-prefixed argument text that starts with 1, {, or } is silently reinterpreted as a variable substitution instead of raising a syntax error, and the value actually used at render time comes from an unrelated kernel argument instead of the text the template author wrote.

Where

python/semantic_kernel/template_engine/blocks/named_arg_block.py, line 21:

NAMED_ARG_REGEX = r"^(?P<name>[0-9A-Za-z_]+)[=]{1}(?P<value>[${1}](?P<var_name>[0-9A-Za-z_]+)|(?P<quote>[\"'])(?P<val>.[^\"^']*)(?P=quote))$"

The variable-form alternative is [${1}](?P<var_name>...). Inside a character class, {1} is not a quantifier, it is three literal characters {, 1, }. So [${1}] is a 4-character class matching any one of $, {, 1, } — not "a literal $, exactly once" as the {1} clearly was intended to express (that would need to be written outside the brackets, e.g. \$ or [$]).

Why

Confirmed directly against the regex and the real class, on semantic-kernel==1.44.1 (byte-identical to current main for this file):

from semantic_kernel.template_engine.blocks.named_arg_block import NamedArgBlock

for content in ["arg1=$var", "arg1={var", "arg1=1var", "arg1=}var"]:
    b = NamedArgBlock(content=content)
    print(content, "-> name=", b.name, "variable=", b.variable)

Output:

arg1=$var -> name= arg1 variable= content='$var' name='var'
arg1={var -> name= arg1 variable= content='{var' name='var'
arg1=1var -> name= arg1 variable= content='1var' name='var'
arg1=}var -> name= arg1 variable= content='}var' name='var'

All four are accepted and all four produce a VarBlock pointing at the variable named var, even though only the first one actually starts with $. The {, 1, and } prefixes are silently swallowed instead of causing a NamedArgBlockSyntaxError (which is what happens for any other non-$/non-quote prefix, e.g. arg1=Xvar is correctly rejected).

Full end-to-end repro (real rendering, not just the regex)

import asyncio
from semantic_kernel import Kernel
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.functions.kernel_function_decorator import kernel_function
from semantic_kernel.prompt_template.kernel_prompt_template import KernelPromptTemplate
from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig

class MyPlugin:
    @kernel_function(name="echo")
    def echo(self, arg1: str) -> str:
        return f"GOT[{arg1}]"

kernel = Kernel()
kernel.add_plugin(MyPlugin(), plugin_name="my")

async def main():
    args = KernelArguments(var="SECRET_VALUE")
    template_str = "{{ my.echo arg1=1var }}"   # arg1 is meant to be the literal text "1var"
    tpl = KernelPromptTemplate(prompt_template_config=PromptTemplateConfig(template=template_str))
    print(await tpl.render(kernel, args))

asyncio.run(main())

Output:

GOT[SECRET_VALUE]

Expected vs actual

  • Expected: arg1=1var does not match the [variable] grammar rule ("$" [valid-name]), so it should either be rejected with NamedArgBlockSyntaxError (consistent with arg1=Xvar, which is correctly rejected) or, if unquoted plain-text arguments were meant to be legal, treated as the literal text 1var. Either way arg1 should never end up holding the value of an unrelated kernel argument called var.
  • Actual: the call silently succeeds and arg1 is substituted with args["var"] ("SECRET_VALUE"), not the text the template author wrote. The same happens for any unquoted, non-quoted value that happens to start with { or }.

Suggested fix

Move the quantifier out of the character class so only a literal $ starts the variable form, e.g.:

NAMED_ARG_REGEX = r"^(?P<name>[0-9A-Za-z_]+)=(?P<value>\$(?P<var_name>[0-9A-Za-z_]+)|(?P<quote>[\"'])(?P<val>.[^\"^']*)(?P=quote))$"

Environment

  • semantic-kernel 1.44.1 (PyPI), verified byte-for-byte identical against python/semantic_kernel/template_engine/blocks/named_arg_block.py on the current main branch.
  • Python 3.13, Windows.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pythonPull requests for the Python Semantic Kerneltriage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions