Safeguard
Vulnerability Analysis

Jinja2 sandbox escape (CVE-2022-29361)

CVE-2022-29361 lets attackers bypass Jinja2's SandboxedEnvironment via str.format, reaching unsafe attributes and risking RCE in untrusted-template apps.

Nayan Dey
Security Researcher
7 min read

A sandbox escape in Jinja2, tracked as CVE-2022-29361, allowed attackers who could submit a template string to an application's SandboxedEnvironment to break out of that sandbox and reach objects and methods the sandbox is explicitly designed to block — a path that, depending on how the host application uses Jinja2, can lead to arbitrary code execution on the server rendering the template. Jinja2's sandboxed environment exists specifically so that applications can safely render templates supplied by end users (report builders, email composers, low-code platforms, CMS themes, and similar features); a flaw that defeats the sandbox undermines the core guarantee those applications depend on.

What went wrong

Jinja2's SandboxedEnvironment (and its stricter sibling, ImmutableSandboxedEnvironment) works by intercepting attribute and item access — getattr, getitem, and friends — and refusing to resolve anything the sandbox's allowlist considers unsafe, such as dunder attributes that lead back to __globals__, __class__, or __subclasses__(). That interception is enforced at the Python level, inside Jinja's own attribute-resolution hooks.

The problem fixed by CVE-2022-29361 is that Python's string formatting machinery — str.format() and the related format filter/format-map semantics — resolves attribute and index access described in a format spec (things like "{0.__class__}".format(obj)) using its own internal C-level lookup path, not Jinja's sandboxed getattr. Prior to the fix, the sandbox did not treat format/format_map on string objects as unsafe callables, so a malicious template author could construct a format string that walks the object graph — through __class__, __init__, __globals__, and similar chains — entirely outside the sandbox's visibility. Because the walk never calls Jinja's intercepted attribute accessor, none of the sandbox's blocklists ever fire. The net effect is a classic sandbox-bypass gadget chain applied to a templating engine: template authors can go from "user-supplied string" to "arbitrary attribute access on live Python objects" to, in the worst case, remote code execution in the context of the process rendering the template.

This is functionally a server-side template injection (SSTI) primitive that specifically defeats the mitigation (sandboxing) that applications adopt precisely to prevent SSTI. It is commonly discussed alongside CWE-1336 (Improper Neutralization of Special Elements Used in a Template Engine) and CWE-693 (Protection Mechanism Failure).

Affected versions and components

  • Jinja2 versions prior to 3.1.2 are affected, spanning both the 2.x and 3.x release lines wherever SandboxedEnvironment / ImmutableSandboxedEnvironment is used to render templates containing content an attacker can influence.
  • Fixed in Jinja2 3.1.2, which marks str.format and str.format_map as unsafe when invoked from inside the sandbox, forcing that path back through Jinja's attribute checks.
  • Exposure is entirely dependent on usage pattern: applications that only render trusted, developer-authored templates are not meaningfully at risk. The exposure is real for any product feature that renders user-supplied or otherwise untrusted Jinja2 templates under the assumption that the sandbox makes this safe — think custom email/report template editors, CMS "theme" fields, low-code workflow builders, chatbot or notification templating, and any internal tool that lets non-engineering users write "just a template."
  • Jinja2 is a transitive dependency of a large slice of the Python ecosystem — Flask and Flask extensions, Ansible, Salt, Fabric, Certbot, MkDocs, and countless internal build/reporting tools — so the practical blast radius of "do we ship Jinja2" is frequently larger than what shows up in a top-level requirements.txt.

CVSS, EPSS, and KEV context

NVD lists CVE-2022-29361 as High severity, with a CVSS v3.1 base score commonly reported around 7.5; scoring nuances vary slightly across sources depending on how attack complexity and impact scope are modeled for a sandbox-escape primitive, so treat the exact decimal as directional rather than gospel and confirm the current record on NVD/GHSA before using it in a risk report. The vulnerability has a low EPSS score — unsurprising, since exploitation is not a generic internet-scannable condition; it requires an application to expose a code path that feeds attacker-influenced input into a Jinja2 sandboxed template render, which is an application-design-specific precondition rather than a network-reachable service flaw. As of this writing, CVE-2022-29361 does not appear on CISA's Known Exploited Vulnerabilities (KEV) catalog. None of that should be read as "low priority" — it means the risk is concentrated and severe in the specific applications that render untrusted templates through the sandbox, and effectively irrelevant everywhere else. Security teams should score it in context of their own usage of Jinja2's sandbox, not by CVSS alone.

Timeline

  • Pre-disclosure: The bypass technique — using str.format's internal attribute resolution to escape sandboxed attribute-access hooks — was identified as a gap in Jinja2's SandboxedEnvironment protections and reported to the Pallets project (documented in GitHub Security Advisory GHSA-h5c8-rqwp-cp95).
  • April 28, 2022: Pallets released Jinja2 3.1.2, which closes the gap by disallowing format/format_map calls on sandboxed string objects.
  • Mid-2022: The issue was assigned and published as CVE-2022-29361 in the National Vulnerability Database, formalizing tracking for scanners and dependency-management tooling.
  • Ongoing: The fix has since propagated through downstream frameworks and pinned dependency chains, though — as with most sandbox-escape CVEs in widely vendored libraries — stale vendored copies, containers built from old base images, and pinned lockfiles continue to carry the vulnerable version years after the fix shipped.

Remediation steps

  1. Upgrade Jinja2 to 3.1.2 or later (current releases are newer still) across every service, container image, and virtual environment — not just the top-level application repo. Check pip show jinja2, lockfiles (requirements.txt, poetry.lock, Pipfile.lock), and any vendored/bundled copies inside third-party tools.
  2. Inventory every use of SandboxedEnvironment / ImmutableSandboxedEnvironment in your codebase and confirm which of them render input that originates from end users, customers, or any party outside your trust boundary. This is the population of code paths where the CVE actually matters.
  3. Regenerate and re-scan your SBOM after upgrading, and diff it against your previous SBOM to confirm the vulnerable Jinja2 version has been eliminated from both direct and transitive dependency trees, including inside build tools, CI images, and framework dependencies (Flask, Ansible, Salt, etc.) that ship their own pinned Jinja2 requirement.
  4. Treat sandboxing as defense-in-depth, not a guarantee. Where feasible, avoid rendering fully untrusted template syntax at all — offer a restricted templating language, a fixed set of variables/macros, or a non-Jinja templating approach for user-facing customization features. If the sandbox must remain the primary control, pair it with process-level isolation (containers with dropped privileges, seccomp, no outbound network, minimal filesystem access) so that a future sandbox bypass doesn't translate directly into host or infrastructure compromise.
  5. Add dependency and SCA gating in CI so a reintroduction of a pre-3.1.2 Jinja2 pin — via a downgrade, a stale lockfile merge, or a new third-party integration — fails the build rather than reaching production.
  6. Log and monitor template-rendering endpoints that accept user-supplied template content, watching for anomalous attribute-chain patterns (__class__, __globals__, __subclasses__, __init__, repeated format/format_map usage) in submitted template bodies, which are strong indicators of exploitation attempts against this or similar sandbox-bypass classes.

How Safeguard Helps

Safeguard turns a CVE like this from a spreadsheet line item into an answered question: is this actually exploitable in our code. Our reachability analysis traces whether the vulnerable Jinja2 SandboxedEnvironment/format code path is genuinely invoked from a function that touches attacker-influenced input in your application, so teams can prioritize the handful of services actually at risk instead of patching everything with equal urgency. Griffin, Safeguard's AI security agent, triages the finding alongside your codebase context, explains the sandbox-bypass mechanism in plain language for the engineer who owns the affected service, and drafts the fix. Continuous SBOM generation and ingestion — across first-party repos, containers, and third-party/vendored dependencies — surfaces every instance of the vulnerable Jinja2 version hiding in transitive dependency trees, not just the ones declared at the top level. And when it's time to act, Safeguard opens an auto-fix pull request that bumps Jinja2 to a patched release and re-verifies the reachability finding closes out, cutting remediation time from a manual dependency hunt to a reviewed merge.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.