Blog

The Jinja2 Sandbox: How MockBase Templates Can't Read Your Filesystem

September 5, 2026Xerat022 min read
Share

Dynamic mock responses need a templating language. Jinja2 is the obvious choice — but a full Jinja2 environment lets a template call arbitrary Python: read files, import modules, walk object internals via __class__.__mro__. Handing that to anyone who can type a response body is not an option on a public service, so MockBase renders every template through Jinja2's SandboxedEnvironment instead of the default one.

What sandboxing actually blocks

jinja2.sandbox.SandboxedEnvironment restricts attribute and item access at render time: template code can't reach "unsafe" attributes (dunder methods, memory internals) or call the small set of things Jinja2 flags as dangerous. It's a real, load-bearing security boundary — not a lint rule — and it's the same class Jinja2 itself recommends for exactly this situation: rendering templates you don't fully trust the author of.

The context passed into every template is deliberately narrow. A template only ever sees:

  • request.method, request.path
  • request.query.<name>, request.headers.<name>, request.body.<path>, request.raw_body
  • request.path_params.<name>
  • three helper functions: now_iso(), uuid4(), random_int(low, high)

That's the entire surface. No filesystem, no network, no imports, no access to anything outside the current request.

Random numbers, done properly

random_int() doesn't call Python's random module. It uses secrets.randbelow() — a CSPRNG — because a mock response is exactly the kind of thing someone might script against for testing token generation, sampling, or retry logic, and a predictable PRNG seed is a bad thing to bake into a tool people use for security-adjacent testing. It's a small detail, but it's the kind of detail that's easy to get wrong by defaulting to random.randint.

The limits nobody sees until they hit them

Sandboxing stops a template from escaping to do something dangerous. It doesn't stop a template from being merely expensive — a deeply nested {% for %} loop or a huge input string can still burn CPU and memory even inside a sandbox. MockBase enforces three separate ceilings on every render:

LimitDefaultWhat it catches
Input template size20,000 charactersAn oversized template string
Rendered output size100,000 characters (cumulative across the whole response)A template that expands into something huge
Structure depth20 levelsRunaway recursion in nested JSON

Any violation raises immediately and the request fails closed — MockBase returns an error rather than a partially-rendered or truncated response. The output-size budget is cumulative across the entire response body, not per-field, so ten fields each rendering 15,000 characters of output still trips the limit even though no single field looks abusive on its own.

Why this matters for a mock API specifically

A "mock API" sounds like a toy, but the templates run server-side, on shared infrastructure, on every request to the mock — including anonymous ones nobody has reviewed. Sandboxing plus hard resource limits is what makes it safe to let arbitrary users write arbitrary Jinja2 and have it execute on your servers thousands of times a day.

X

Xerat02

Building MockBase.