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.