Security¶
A .docx template is executable input¶
This is the whole of it, and everything below follows from it.
A template engine executes its template. Jinja2 templates can call methods, walk
attributes and reach objects the context never mentioned; that is what makes
{% for %} and {{ x|filter }} work. Putting the template inside a .docx does
not change any of it — it only makes it look like a document, which is exactly
why people are surprised.
So a .docx template deserves the trust you would give a .py file, not the
trust you would give a .pdf.
That is not a defect in this package or in the one it replaces. It is what a template is. What has been missing is anybody saying so.
The case that matters¶
An application that accepts an uploaded .docx and renders it server-side is
running attacker-supplied template code. Upstream reports it as remote code
execution — elapouya/python-docx-template#632 and #549 — and they are right
about the consequence, whatever one calls the cause.
The classic payload needs nothing but a text box in Word:
From there the reader walks to something with a __init__.__globals__ and
reaches os.
What to do about it¶
If templates come from outside, do not render them¶
Not "sandbox them" — do not accept them. Let users choose from templates you ship, and let them supply the data. Data has never been the problem here.
Where that is genuinely impossible, the rest of this page is about reducing the blast radius, and none of it makes hostile input safe.
The sandbox¶
This renders in jinja2.sandbox.SandboxedEnvironment, which refuses attribute
access it considers unsafe — everything beginning with _, and a list of names
like str.format — so the payload above raises rather than running.
Ordinary templates are unaffected. The suite renders the fixtures with the sandbox on and off and compares the bytes.
The sandbox is a mitigation, not a boundary
Jinja2's own documentation is careful about this and so is this page. The sandbox is a denylist of attribute names on a language that can call arbitrary objects; bypasses have been found before and it is reasonable to assume more exist.
Do not put untrusted templates behind it and consider the problem solved. Put them behind a process boundary — a container with no network, no credentials and a timeout — and treat the sandbox as the second lock.
An environment you supply that is already sandboxed stays sandboxed, whatever
sandboxed= says: your choice is not this package's to weaken.
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()
env.filters["money"] = format_money
tpl.render(context, jinja_env=env) # -- still sandboxed --
The reverse cannot work. A sandbox is a subclass of Environment, so
overlaying a plain one cannot add it, and asking for both raises ValueError
rather than rendering unsandboxed for a caller who asked for a sandbox.
The command line¶
$ docxtpl-ng template.docx context.json out.docx # -- sandboxed --
$ docxtpl-ng template.docx context.json out.docx --no-sandbox
The sandbox is on by default here and off in the library, and the difference is the context.
A library caller's context holds their own objects, whose methods a template may quite legitimately call — that is why the library cannot presume. The command's context is a JSON file, so it holds nothing but dicts, lists, strings and numbers, and a sandbox blocks nothing a template over plain data needs. For a command that renders an arbitrary .docx from a shell, the safe default costs nothing to have.
--no-sandbox is there for the template that does need it, and having to type
it is the point.
Escaping is a different problem¶
Template.render() XML-escapes values by default; DocxTemplate.render() does
not, because docxtpl does not and code depending on that surface cannot be
changed by us.
Escaping protects the document from the data — a value containing < closing
a w:t early and producing a file Word will not open. It is not a security
control against a hostile template, which needs no < at all.
See Migrating for which surface does what.
What is not a risk here¶
Rendering a template you wrote against data you did not. Values are substituted, not executed. The worst untrusted data can do is produce a document that will not open, which escaping already prevents.
Opening a .docx to read it. No template code runs until render().
A macro-enabled document. .docm macros are Word's business and this
package neither reads nor writes them.
Reporting something¶
Open an issue on the tracker if it is a bug in this package. If you believe you have a bypass of the sandbox itself, that is a Jinja2 matter and belongs with the Pallets project — but say so here too, so this page can stop recommending something that no longer helps.