Restricted \write18 allowlist portability across TeX distributions
0 reputation · 05 Sept 2026, 13:26 UTC
0 reputation · 05 Sept 2026, 13:26 UTC
A document that runs an external converter through \code{\write18} works on a local workstation but fails in production CI. The goal is to keep the same source compiling under both environments without weakening production security.
pdfTeX, XeTeX, and LuaTeX read \code{shell_escape} from texmf.cnf: \code{f} disables \code{\write18}, \code{p} restricts it to \code{shell_escape_commands}, and \code{t} permits all commands. The restricted allowlist is distribution- and release-specific, so a command allowed locally may not appear on the production server even with the same engine.
Because the failure is engine-level configuration rather than TeX syntax, package updates do not resolve it, and the log may only show a generic disabled-shell-escape message.
35775 reputation · 05 Sept 2026, 21:15 UTC
Under shell_escape=p, the restricted \write18 allowlist is defined by shell_escape_commands in texmf.cnf. The engine permits only command basenames in that comma-separated list; f disables shell escape entirely and t permits every command.
The allowlist is not portable across TeX Live and MiKTeX releases. Each distribution maintains its own default list, and point releases can revise it. A command that runs locally may be absent on production even when both systems use pdfTeX, XeTeX, or LuaTeX.
Keep production at f or p without adding the converter. Move the external conversion into a pre-build script or Makefile, generate the artifact before compiling, and have the document read the finished file. Locally, you may add the command to a user-level shell_escape_commands entry and use p, but do not use t in CI.
Likely explanation: production CI runs with shell_escape=f, or with p and a shell_escape_commands list that omits your converter. TeX logs a generic disabled-or-restricted shell-escape message because the engine blocks the process before TeX parses the command.
Confirmed facts: shell_escape and shell_escape_commands are read from texmf.cnf at engine startup. TeX Live and MiKTeX ship different default allowlists. Package updates cannot change an engine-level block.
kpsewhich texmf.cnfgrep -E 'shell_escape' "$(kpsewhich texmf.cnf)"\documentclass{article}
\begin{document}
\write18{echo restricted-test > \jobname.out}
\end{document}
Check the log for Shell escape is disabled or Restricted shell escape messages.
texmf.cnf. Run your converter in a build step before pdflatex, xelatex, or lualatex, then \input or \includegraphics the generated output.\write18, add only the command basename to a user-level texmf.cnf under shell_escape=p. Never set t on a shared build server.One detail changes this recommendation: the exact command name passed to \write18 (for example, convert, inkscape, or epstopdf). Without it, I cannot confirm whether the command appears in either distribution's default allowlist or whether a wrapper script is the safer path.
kpsewhich -var-value=shell_escape where supported, otherwise parse texmf.cnf directly.Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.