diff --git a/libs/langchain/langchain/prompts/base.py b/libs/langchain/langchain/prompts/base.py index 04b522f733b..f2744c43a14 100644 --- a/libs/langchain/langchain/prompts/base.py +++ b/libs/langchain/langchain/prompts/base.py @@ -15,19 +15,33 @@ from langchain.utils.formatting import formatter 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: diff --git a/libs/langchain/langchain/prompts/prompt.py b/libs/langchain/langchain/prompts/prompt.py index 27ee992f8e2..e935d15aae3 100644 --- a/libs/langchain/langchain/prompts/prompt.py +++ b/libs/langchain/langchain/prompts/prompt.py @@ -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: @@ -196,6 +203,18 @@ class PromptTemplate(StringPromptTemplate): ) -> 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, diff --git a/libs/langchain/tests/unit_tests/prompts/test_prompt.py b/libs/langchain/tests/unit_tests/prompts/test_prompt.py index 7966a3067b9..8e522519495 100644 --- a/libs/langchain/tests/unit_tests/prompts/test_prompt.py +++ b/libs/langchain/tests/unit_tests/prompts/test_prompt.py @@ -177,6 +177,17 @@ Will it get confused{ }? 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."""