Template escaping — autoescape is OFF
Every plugin that renders user-supplied text must escape it itself
FastPluggy builds its Jinja environment without autoescaping. Nothing in the framework, your tests, or the linters will tell you when a template interpolates unescaped user input — the page simply renders, and the injection works.
What the host actually does
fastpluggy.py constructs the shared environment as:
Jinja2's Environment defaults to autoescape=False, and no autoescape= argument is
passed. The # nosec B701 comment suppresses bandit's warning for exactly this — so the
one tool that flags it by default is silenced fleet-wide. The same pattern appears in the
error and degraded-mode handlers.
This is a property of the host, not of your plugin. It applies to every plugin loaded into the app, and a plugin author cannot opt in per-plugin by changing a local setting.
What you must do
Filter every interpolation of a value that a human could have typed:
"Human could have typed" is broader than a form field — it includes anything read back from the database, a filename, a webhook payload, an LLM response, or a captured page title.
Why a review pass is not enough
A missing | e looks exactly like a present one until someone submits a payload. There is
no failing test, no lint error and no runtime warning, so the defect survives review by
being invisible rather than by being argued for.
Add a structural test that walks your plugin's own templates and asserts every interpolation of a known-tainted variable carries a filter. It runs in CI, it covers templates added later, and it does not depend on anyone remembering the rule.
Escaping is not the only control
| e handles HTML context. A value interpolated into an inline <script>, a style
attribute, or a URL needs the escaping appropriate to that context — HTML-escaping a
string inside a <script> block does not make it safe.