Skip to content

Commit a6fe773

Browse files
committed
Merge branch 'sandbox-jinja-templating-backport' into 'wayflow-25.4.x'
[Backport] Restricted sandboxed jinja templating
2 parents 2f7780a + 79a617b commit a6fe773

File tree

59 files changed

+772
-169
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+772
-169
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ licenses_example/choosealicense.com
1212
.idea/
1313
*.iml
1414
.gradle/
15-
venv/
1615
.venv-*
1716
lib64/
1817
bin/
@@ -201,3 +200,8 @@ Thumbs.db
201200

202201
*.pklz
203202
*.sqlite
203+
204+
205+
# environment variables such as private keys
206+
*env/
207+
examples/*

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
26.1.0.dev0
1+
25.4.3

docs/wayflowcore/source/core/changelog.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
Changelog
22
=========
33

4+
WayFlow 25.4.3
5+
--------------
6+
7+
Security
8+
^^^^^^^^
9+
10+
* **Stricter environment for jinja templates rendering:**
11+
12+
We now use a stricter version of the SandboxedEnvironment for rendering jinja templates.
13+
No access to object attributes is allowed, only key-based access to python dictionaries and main jinja LoopContext properties are allowed.
14+
15+
Check the guide on :ref:`How to write secure prompts with Jinja templating <securejinjatemplating>` for more information.
16+
17+
418
WayFlow 25.4.2
519
--------------
620

docs/wayflowcore/source/core/code_examples/howto_prompttemplate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
prompt_text = """You are a helpful assistant. Answer the user questions.
7070
For context, the conversation was:
7171
{% for msg in __CHAT_HISTORY__ %}
72-
{{ msg.message_type.value }} >> {{msg.content}}
72+
{{ msg.message_type }} >> {{msg.content}}
7373
{%- endfor %}
7474
7575
Just answer the user question.
@@ -203,7 +203,7 @@ def some_tool(param1: Annotated[str, "name of the user"]) -> Annotated[str, "too
203203
# .. start-###_With_custom_structured_generation
204204
text_template = """Extract information about a person. The person is 65 years old, named Johnny.
205205
Just return a json document that respects this JSON Schema:
206-
{{__RESPONSE_FORMAT__.to_json_schema() | tojson }}
206+
{{__RESPONSE_FORMAT__ | tojson }}
207207
208208
Reminder: only output the required json document, no need to repeat the title of the description, just the properties are required!
209209
"""

docs/wayflowcore/source/core/howtoguides/agents.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ Sometimes, there is contextual information relevant to the conversation.
4141
Assume a user is interacting with the assistant named "Jerry."
4242
To make the assistant more context-aware, define a variable or expression in the ``custom_instruction`` Jinja template, and pass it when creating the conversation:
4343

44+
.. note::
45+
46+
Jinja templating introduces security concerns that are addressed by WayFlow by restricting Jinja's rendering capabilities.
47+
Please check our guide on :ref:`How to write secure prompts with Jinja templating <securejinjatemplating>` for more information.
48+
4449
.. literalinclude:: ../code_examples/howto_agents.py
4550
:language: python
4651
:start-after: .. start-conversation:

docs/wayflowcore/source/core/howtoguides/conditional_flows.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ For scenarios requiring branching based on more advanced conditions, consider us
154154
:start-after: .. start-##_Branching_with_a_template
155155
:end-before: .. end-##_Branching_with_a_template
156156

157+
.. note::
158+
159+
Jinja templating introduces security concerns that are addressed by WayFlow by restricting Jinja's rendering capabilities.
160+
Please check our guide on :ref:`How to write secure prompts with Jinja templating <securejinjatemplating>` for more information.
161+
157162
Pattern 3: Branching using an LLM
158163
---------------------------------
159164

docs/wayflowcore/source/core/howtoguides/promptexecutionstep.rst

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,162 @@ You can instruct the agent to generate specific outputs, and use them in the ``A
8989
:start-after: .. start-agent:
9090
:end-before: .. end-agent
9191

92+
How to write secure prompts with Jinja templating
93+
=================================================
94+
95+
.. _securejinjatemplating:
96+
97+
`Jinja2 <https://jinja.palletsprojects.com/en/stable/intro/>`_ is a fast and flexible templating engine for Python,
98+
enabling dynamic generation of text-based formats by combining templates with data.
99+
100+
However, enabling all Jinja templating capabilities poses some security challenges.
101+
For this reason, WayFlow relies on a stricter implementation of the Jinja's SandboxedEnvironment for higher security.
102+
Every callable is considered unsafe, and every attribute and item access is prevented, except for:
103+
104+
* The attributes ``index0``, ``index``, ``first``, ``last``, ``length`` of the ``jinja2.runtime.LoopContext``;
105+
* The entries of a python dictionary (only native type is accepted);
106+
* The items of a python list (only native type is accepted).
107+
108+
You should never write a template that includes a function call, or access to any internal attribute or element of
109+
an arbitrary variable: that is considered unsafe, and it will raise a ``SecurityException``.
110+
111+
Moreover, WayFlow performs additional checks on the inputs provided for rendering.
112+
In particular, only elements and sub-elements that are of basic python types
113+
(``str``, ``int``, ``float``, ``bool``, ``list``, ``dict``, ``tuple``, ``set``, ``NoneType``) are accepted.
114+
In any other case, a ``SecurityException`` is raised.
115+
116+
What you can write
117+
------------------
118+
119+
Here's a set of common patters that are accepted by WayFlow's restricted Jinja templating.
120+
121+
Templates that access variables of base python types:
122+
123+
.. code-block:: python
124+
125+
my_var: str = "simple string"
126+
template = "{{ my_var }}"
127+
# Expected outcome: "simple string"
128+
129+
Templates that access elements of a list of base python types:
130+
131+
.. code-block:: python
132+
133+
my_var: list[str] = ["simple string"]
134+
template = "{{ my_var[0] }}"
135+
# Expected outcome: "simple string"
136+
137+
Templates that access dictionary entries of base python types:
138+
139+
.. code-block:: python
140+
141+
my_var: dict[str, str] = {"k1": "simple string"}
142+
template = "{{ my_var['k1'] }}"
143+
# Expected outcome: "simple string"
144+
145+
my_var: dict[str, str] = {"k1": "simple string"}
146+
template = "{{ my_var.k1 }}"
147+
# Expected outcome: "simple string"
148+
149+
Builtin functions of Jinja, like ``length`` or ``format``:
150+
151+
.. code-block:: python
152+
153+
my_var: list[str] = ["simple string"]
154+
template = "{{ my_var | length }}"
155+
# Expected outcome: "1"
156+
157+
Simple expressions:
158+
159+
.. code-block:: python
160+
161+
template = "{{ 7*7 }}"
162+
# Expected outcome: "49"
163+
164+
``For`` loops, optionally accessing the ``LoopContext``:
165+
166+
.. code-block:: python
167+
168+
my_var: list[int] = [1, 2, 3]
169+
template = "{% for e in my_var %}{{e}}{{ ', ' if not loop.last }}{% endfor %}"
170+
# Expected outcome: "1, 2, 3"
171+
172+
``If`` conditions:
173+
174+
.. code-block:: python
175+
176+
my_var: int = 4
177+
template = "{% if my_var % 2 == 0 %}even{% else %}odd{% endif %}"
178+
# Expected outcome: "even"
179+
180+
Our general recommendation is to avoid complex logic in templates, and to pre-process the data you want to render instead.
181+
For example, in case of complex objects, in order to comply with restrictions above, you should conveniently
182+
transform them recursively into a dictionary of entries of basic python types (see list of accepted types above).
183+
184+
What you cannot write
185+
---------------------
186+
187+
Here's a set of common patters that are **NOT** accepted by WayFlow's restricted Jinja templating.
188+
189+
Templates that access arbitrary objects:
190+
191+
.. code-block:: python
192+
193+
my_var: MyComplexObject = MyComplexObject()
194+
template = "{{ my_var }}"
195+
# Expected outcome: SecurityException
196+
197+
Templates that access attributes of arbitrary objects:
198+
199+
.. code-block:: python
200+
201+
my_var: MyComplexObject = MyComplexObject(attribute="my string")
202+
template = "{{ my_var.attribute }}"
203+
# Expected outcome: SecurityException
204+
205+
Templates that access internals of any type and object:
206+
207+
.. code-block:: python
208+
209+
my_var: dict = {"k1": "my string"}
210+
template = "{{ my_var.__init__ }}"
211+
# Expected outcome: SecurityException
212+
213+
Templates that access non-existing keys of a dictionary:
214+
215+
.. code-block:: python
216+
217+
my_var: dict = {"k1": "my string"}
218+
template = "{{ my_var['non-existing-key'] }}"
219+
# Expected outcome: SecurityException
220+
221+
Templates that access keys of a dictionary of type different from ``int`` or ``str``:
222+
223+
.. code-block:: python
224+
225+
my_var: dict = {("complex", "key"): "my string"}
226+
template = "{{ my_var[('complex', 'key')] }}"
227+
# Expected outcome: SecurityException
228+
229+
Templates that access callables:
230+
231+
.. code-block:: python
232+
233+
my_var: Callable = lambda x: f"my value {x}"
234+
template = "{{ my_var(2) }}"
235+
# Expected outcome: SecurityException
236+
237+
my_var: list = [1, 2, 3]
238+
template = "{{ len(my_var) }}"
239+
# Expected outcome: SecurityException
240+
241+
my_var: MyComplexObject = MyComplexObject()
242+
template = "{{ my_var.to_string() }}"
243+
# Expected outcome: SecurityException
244+
245+
246+
For more information, please check our :doc:`Security considerations page <../security>`.
247+
92248
Recap
93249
=====
94250

docs/wayflowcore/source/core/misc/glossary.rst

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,17 @@ Prompt Template
228228
===============
229229

230230
A prompt template is a standardized prompt structure with placeholders for variable inputs, designed to maintain consistency across similar queries
231-
while allowing for customization. WayFlow uses jinja-style placeholders to specify the input variables to the prompt (for more information check
232-
the `reference of jinja2 <https://jinja.palletsprojects.com/en/stable/templates>`_).
231+
while allowing for customization. WayFlow uses Jinja-style placeholders to specify the input variables to the prompt (for more information check
232+
the `reference of Jinja2 <https://jinja.palletsprojects.com/en/stable/templates>`_).
233233

234234
See the :doc:`Tutorials and Use-Case Examples <../tutorials/index>` for concrete examples, or check the
235235
:ref:`TemplateRenderingStep API reference <templaterenderingstep>`.
236236

237+
.. note::
238+
239+
Jinja templating introduces security concerns that are addressed by WayFlow by restricting Jinja's rendering capabilities.
240+
Please check our guide on :ref:`How to write secure prompts with Jinja templating <securejinjatemplating>` for more information.
241+
237242
Prompt templates can be used in WayFlow components that use LLMs, such as :ref:`Agents <agent>` and the :ref:`PromptExecutionStep <promptexecutionstep>`.
238243

239244
Properties

docs/wayflowcore/source/core/security.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,21 @@ Jinja 2's automatic HTML-escaping is **disabled by design** in WayFlow to:
487487

488488
This preserves model fidelity/latency but removes default XSS/code-injection safeguards.
489489

490+
WayFlow relies on a stricter implementation of the Jinja's SandboxedEnvironment for security reasons.
491+
Every callable is considered unsafe, and every attribute access is prevented, except for:
492+
493+
* The attributes ``index0``, ``index``, ``first``, ``last``, ``length`` of the ``jinja2.runtime.LoopContext``;
494+
* The entries of a python dictionary (only native type is accepted);
495+
* The items of a python list (only native type is accepted).
496+
497+
You should never write a template that includes a function call, or access to any internal attribute or element of
498+
an arbitrary variable: that is considered unsafe, and it will raise a ``SecurityException``.
499+
500+
Moreover, WayFlow performs additional checks are performed on the inputs provided for rendering.
501+
In particular, only elements and sub-elements that are of basic python types
502+
(``str``, ``int``, ``float``, ``bool``, ``list``, ``dict``, ``tuple``, ``set``, ``NoneType``)
503+
are accepted. In any other case, a ``SecurityException`` is raised.
504+
490505
.. important::
491506

492507
**Never render the raw output of a WayFlow step directly to a browser.**

docs/wayflowcore/source/core/tutorials/basic_flow.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ Parameters in a Jinja template look like this: ``{{this_is_a_template_parameter}
120120
other sources, such as the input parameters schemas of the ``tool`` for the ``ToolExecutionStep``. There will be one input descriptor for each
121121
parameter in the template, with a name taken from the parameter. Similarly, there will be one input descriptor for each parameter required by a ``tool``.
122122

123+
.. note::
124+
125+
Jinja templating introduces security concerns that are addressed by WayFlow by restricting Jinja's rendering capabilities.
126+
Please check our guide on :ref:`How to write secure prompts with Jinja templating <securejinjatemplating>` for more information.
127+
123128
Output descriptors can also be considered names for a step's outputs. For many steps, there will be a default name for each output. For example,
124129
for a ``ToolExecutionStep`` the default name for the output of the step is ``ToolExecutionStep.TOOL_OUTPUT``.
125130

0 commit comments

Comments
 (0)