Skip to content

Sandboxing

Every make publish runs inside a locked-down container: no network, a read-only filesystem, and dropped privileges. Two things follow — builds are reproducible (the same source and pinned image produce the same output years apart), and it is safe to build a book from source you didn't write.

This holds for every template. Some run the engine from a prebuilt image and some build that image from the engine source they ship; the hardening is the same either way.

What the container can't do

Restriction What it means
No network The build runs fully offline. Nothing reaches the internet, in either direction.
Read-only filesystem Your manuscript, fonts, and configuration mount read-only, and artifacts/ is the only writable path you own. The engine keeps its hook cache in a volume of its own. Scratch space under /tmp is in-memory and vanishes when the build ends.
No privileges Every Linux capability is dropped, privilege escalation is blocked, and the build runs as your user — never root.

The one door, and it is yours to open

A hook is the single way anything reaches out of a build, and every one is declared in your compose file. A service there that is not the engine's is a hook — so what reaches out of a build is a list you can read in one file. With none wired, nothing leaves the container at all.

A declared hook runs under the same hardening as the engine, with one exception it needs: it keeps its own image's user rather than yours, which is what lets the engine reach the socket it binds. Hooks have no use for the network and compose leaves it off; one that asks for it is asking you to widen the sandbox.

Engine and hook speak a fixed protocol over that socket, and what crosses is data — the text of a code block out, markup back. Nothing executable is exchanged. Keystone reads that markup as though you had written it, raw LaTeX included, so a hook is trusted exactly as far as your own manuscript is. Markup that did reach for something outside the document would find shell-escape off, no network, and your manuscript mounted read-only.

Wiring one is a decision of the same kind as taking on a dependency, whether the hook is your own or a third party's — and like the engine, a hook is usually there to be read before you take it on.

A template may ship a hook already wired, in which case the decision was made for you — its page says so, and removing it is a compose edit.

Shell-escape is off

While typesetting a PDF, XeLaTeX can normally reach outside the document — running a command with \write18, or reading a command's output through a piped \openin. Both are shell-escape, which Keystone disables entirely — not even LaTeX's restricted default whitelist runs. Nothing in your manuscript can make the typesetter execute a command.

A LaTeX package that shells out to a helper — minted (Pygments), TikZ's external-figure library — won't run. That rarely touches a book author: Keystone's idiom is static assets, not inline execution. Code highlighting is built in; a precompiled diagram goes in as a figure. Wanting the package to run inline is a reason to fork a template that ships the engine source (core) and relax the sandbox yourself — a deliberate trade against the guarantees above, and yours to own.

Auditing it yourself

The container hardening is your project's .docker/docker-compose.yaml — the network, filesystem, and capability settings are a few readable lines. Shell-escape sits a layer down, in the engine's publish.sh — readable in your project if your template ships the engine source, and inherited from the signed image if it does not. See Verifying your download for image signatures and the checksum manifest.