先把当时的情况摆出来。提示词模板用 Jinja2 3.1 渲染,用户输入里带了 {{ 7*7 }},输出里直接变成 49,另一个用户输入 {{ 之后请求报 jinja2.exceptions.UndefinedError: 'x' is undefined。
这类报错背后常见的原因有几处,逐个排除就行。用户内容被拼进模板字符串一起渲染,等于让用户写模板。还有一种是没启用 SandboxedEnvironment,属性和过滤器都能被调用。
少数情况下是autoescape 只处理 HTML,对模板语法没有作用。不容易想到的是,变量用字符串拼接传入,没有走参数绑定。
复现一次最小输入
python -c "from jinja2 import Template;print(Template('总结: '+'{{ 7*7 }}').render())"
随后把用户内容作为变量传入而不是拼进模板
python -c "from jinja2 import Template;print(Template('总结: {{ q }}').render(q='{{ 7*7 }}'))"
接着改用沙箱环境渲染外部输入
python -c "from jinja2.sandbox import SandboxedEnvironment;e=SandboxedEnvironment();print(e.from_string('{{ q }}').render(q='{{ 7*7 }}'))"
等前面都确认过,再在入口加一次花括号检测并记录
python -c "import re;s=open('input.txt').read();print(bool(re.search(r'{{|}}',s)))"
恢复的判定其实很直接。用户输入原样出现在输出里,不再被求值,UndefinedError 消失,沙箱环境下越界属性访问会抛 SecurityError 并被业务层捕获。
这里还有个坑。模板和用户数据要严格分开,拼接渲染等于把模板引擎的执行权交给用户,沙箱只是第二道防线。报错文案容易误导,真正的原因往往在相邻那一层的日志里。
