\write18 shell-escape permission boundary across restricted and unrestricted engine modes
0 reputation · 10 Apr 2021, 05:51 UTC
0 reputation · 10 Apr 2021, 05:51 UTC
The \write18 primitive lets a LaTeX document request an external program, but the engine—not the LaTeX kernel—decides whether that request is ignored, rejected, or executed. In a project that relies on an external tool such as repstopdf or makeindex, the same source may compile on a developer workstation yet fail in continuous integration because the engine is invoked with a different shell-escape mode. Modern engines expose three settings: no shell escape, restricted shell escape with a small allow-list, and unrestricted shell escape. The restricted list is compiled into the engine build and texmf.cnf, so a reviewer cannot infer permitted commands from the document alone.
The uncertainty deepens with LuaTeX, where \directlua combined with os.execute or io.popen can reach the operating system even when \write18 is blocked. Editors and build tools such as latexmk or arara pass their own flags, so interactive and automated builds may observe different permission boundaries. Because the allow-list and diagnostic wording change between TeX Live releases and engine builds, a configuration that works on one version cannot be assumed portable.
Which specific engine flags and texmf.cnf entries must be inspected to determine the exact set of commands allowed under restricted mode for a given TeX Live release, and how can a build configuration verify that the LuaTeX \directlua path is gated by the same policy rather than bypassing it?
34775 reputation · 10 Apr 2021, 06:54 UTC
Inspect the engine command-line flags and the shell-escape allow-list in the active texmf.cnf. Restricted mode is selected by flags such as --enable-write18 (or --shell-restricted in newer builds); unrestricted mode uses --shell-escape; and --no-shell-escape disables \write18 entirely. The permit list lives in a texmf.cnf variable—usually shell_escape_commands, though some releases expose it as shell_escape—and is compiled from the engine build plus local configuration. Do not assume that restricting \write18 also confines LuaTeX’s \directlua path; that boundary is governed by separate engine internals and must be verified independently.
First, capture the exact engine invocation used in CI. Build tools such as latexmk and arara can inject or override flags, so the developer workstation command may differ from the automated one. Run the build in verbose mode or inspect the CI log to see the final arguments passed to pdflatex, lualatex, or xelatex.
Next, locate the active configuration:
kpsewhich texmf.cnf
Open that file and search for the shell-escape policy entries. You are looking for a comma-separated allow-list of executables. Because variable names and defaults shift between TeX Live releases and OS packages, treat the exact key as version-dependent rather than portable.
\directlua boundaryLuaTeX exposes the operating system through the Lua os and io libraries. A document can call os.execute or io.popen inside \directlua. Whether these calls are blocked, restricted, or allowed depends on the engine’s sandboxing configuration, which is not necessarily the same switch that controls \write18. Some builds harden the Lua VM independently; others leave it open even when \write18 is in restricted mode.
latexmk -verbose (or the equivalent for your build tool).kpsewhich texmf.cnf and inspect the allow-list entry (for example, grep -i shell_escape $(kpsewhich texmf.cnf)).\write18{echo write18_probe}
\directlua{os.execute("echo lua_probe")}
Observe whether each probe produces a side effect, a log message, or an error. If the \directlua probe executes while \write18 is blocked, the Lua path is bypassing the \write18 policy.
The exact TeX Live release and LuaTeX version used in CI. The allow-list variable name, default command set, and Lua sandboxing behavior have changed across releases, so the specific texmf.cnf key to audit and the expected \directlua behavior depend on that version.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.