Skip to content

Commit

Permalink
Use jinja2 sandboxing by default (#12733)
Browse files Browse the repository at this point in the history
* This is an opt-in feature, so users should be aware of risks if using
jinja2.
* Regardless we'll add sandboxing by default to jinja2 templates -- this
  sandboxing is a best effort basis.
* Best strategy is still to make sure that jinja2 templates are only
loaded from trusted sources.
  • Loading branch information
eyurtsev authored Nov 1, 2023
1 parent ab5309f commit 0e1aedb
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 8 deletions.
24 changes: 19 additions & 5 deletions libs/langchain/langchain/prompts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,33 @@
def jinja2_formatter(template: str, **kwargs: Any) -> str:
"""Format a template using jinja2.
*Security warning*: jinja2 templates are not sandboxed and may lead
to arbitrary Python code execution. Do not expand jinja2 templates
using unverified or user-controlled inputs!
*Security warning*: As of LangChain 0.0.329, this method uses Jinja2's
SandboxedEnvironment by default. However, this sand-boxing should
be treated as a best-effort approach rather than a guarantee of security.
Do not accept jinja2 templates from untrusted sources as they may lead
to arbitrary Python code execution.
https://jinja.palletsprojects.com/en/3.1.x/sandbox/
"""
try:
from jinja2 import Template
from jinja2.sandbox import SandboxedEnvironment
except ImportError:
raise ImportError(
"jinja2 not installed, which is needed to use the jinja2_formatter. "
"Please install it with `pip install jinja2`."
"Please be cautious when using jinja2 templates. "
"Do not expand jinja2 templates using unverified or user-controlled "
"inputs as that can result in arbitrary Python code execution."
)

return Template(template).render(**kwargs)
# This uses a sandboxed environment to prevent arbitrary code execution.
# Jinja2 uses an opt-out rather than opt-in approach for sand-boxing.
# Please treat this sand-boxing as a best-effort approach rather than
# a guarantee of security.
# We recommend to never use jinja2 templates with untrusted inputs.
# https://jinja.palletsprojects.com/en/3.1.x/sandbox/
# approach not a guarantee of security.
return SandboxedEnvironment().from_string(template).render(**kwargs)


def validate_jinja2(template: str, input_variables: List[str]) -> None:
Expand Down
25 changes: 22 additions & 3 deletions libs/langchain/langchain/prompts/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@ class PromptTemplate(StringPromptTemplate):
The template can be formatted using either f-strings (default) or jinja2 syntax.
*Security warning*: Prefer using `template_format="f-string"` instead of
`template_format="jinja2"`, since jinja2 templates are not sandboxed and may
lead to arbitrary Python code execution. Do not construct a jinja2 `PromptTemplate`
from unverified or user-controlled inputs!
`template_format="jinja2"`, or make sure to NEVER accept jinja2 templates
from untrusted sources as they may lead to arbitrary Python code execution.
As of LangChain 0.0.329, Jinja2 templates will be rendered using
Jinja2's SandboxedEnvironment by default. This sand-boxing should
be treated as a best-effort approach rather than a guarantee of security,
as it is an opt-out rather than opt-in approach.
Despite the sand-boxing, we recommend to never use jinja2 templates
from untrusted sources.
Example:
Expand Down Expand Up @@ -196,6 +203,18 @@ def from_template(
) -> PromptTemplate:
"""Load a prompt template from a template.
*Security warning*: Prefer using `template_format="f-string"` instead of
`template_format="jinja2"`, or make sure to NEVER accept jinja2 templates
from untrusted sources as they may lead to arbitrary Python code execution.
As of LangChain 0.0.329, Jinja2 templates will be rendered using
Jinja2's SandboxedEnvironment by default. This sand-boxing should
be treated as a best-effort approach rather than a guarantee of security,
as it is an opt-out rather than opt-in approach.
Despite the sand-boxing, we recommend to never use jinja2 templates
from untrusted sources.
Args:
template: The template to load.
template_format: The format of the template. Use `jinja2` for jinja2,
Expand Down
11 changes: 11 additions & 0 deletions libs/langchain/tests/unit_tests/prompts/test_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,17 @@ def test_prompt_from_jinja2_template() -> None:
assert prompt == expected_prompt


@pytest.mark.requires("jinja2")
def test_basic_sandboxing_with_jinja2() -> None:
"""Test basic sandboxing with jinja2."""
import jinja2

template = " {{''.__class__.__bases__[0] }} " # malicious code
prompt = PromptTemplate.from_template(template, template_format="jinja2")
with pytest.raises(jinja2.exceptions.SecurityError):
assert prompt.format() == []


@pytest.mark.requires("jinja2")
def test_prompt_from_jinja2_template_multiple_inputs() -> None:
"""Test with multiple input variables."""
Expand Down

0 comments on commit 0e1aedb

Please sign in to comment.