>From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Pavel Sanda <sanda () lyx org>
Date: Wed, 8 Jul 2026 12:26:10 +0200
Subject: [PATCH] Hardening case 00de (xindex) - version check + --restricted jail

Stacks on the processing gate 00de: adds xindex to the processor table.
For >= 1.07 ungated + appends --restricted to the spawned command so 
a hostile document cannot shadow xindex's own config/modules in CWD.

Assisted-by: Claude Opus 4.8
---
 src/LaTeX.cpp | 35 +++++++++++++++++++++++++++++---
 1 file changed, 33 insertions(+), 2 deletions(-)

diff --git a/src/LaTeX.cpp b/src/LaTeX.cpp
--- a/src/LaTeX.cpp
+++ b/src/LaTeX.cpp
@@ -613,6 +613,19 @@
 	    && !checkProcessorAuth(doc_fname, tmp))
 		return Systemcall::KILLED;
 
+	// A --restricted-capable xindex (not gated, i.e. version >= the table
+	// threshold) is run with --restricted so a document cannot shadow
+	// xindex's own modules/configs from the current directory and get code
+	// executed. This also covers a document-supplied \index_command, which
+	// replaces the whole command string. Older xindex would reject the
+	// unknown flag, hence the !isProcessorGated guard (cached above).
+	{
+		string iprog;
+		split(tmp, iprog, ' ');
+		if (onlyFileName(iprog) == "xindex" && !isProcessorGated(tmp))
+			tmp += " --restricted";
+	}
+
 	Language const * doc_lang = languages.getLanguage(rp.document_language);
 	
 	if (contains(tmp, "$$x")) {
@@ -844,6 +857,11 @@
 	{ "upmendex",  true,  nullptr, nullptr, 0, 0 },
 	// biber: code-capable; fixed upstream at 2.22
 	{ "biber", false, "--version", "version:\\s*([0-9]+)\\.([0-9]+)", 2, 22 },
+	// xindex: code-capable (texlua); the CWD-shadowing RCE is closed by
+	// running it with --restricted (see runMakeIndex). Versions that support
+	// that flag are not gated. PIN this threshold to the xindex release that
+	// actually ships --restricted before enabling (placeholder: 1.07).
+	{ "xindex", false, "--version", "xindex version ([0-9]+)\\.([0-9]+)", 1, 7 },
 };
 
 // False only when >= required version. 
@@ -871,8 +889,21 @@
 			break;
 		}
 		//safe because prog was matched against the table
-		cmd_ret const r =
-			runCommand(quoteName(prog) + ' ' + p.version_arg);
+		// Probe in a fresh empty directory, not the export dir: a
+		// texlua-based processor (xindex) executes CWD-first module loads
+		// before it prints its version, so probing where the document has
+		// written files would run planted code. createLyXTmpDir(FileName())
+		// makes a unique dir under the system temp; on failure we leave the
+		// fail-safe gated=true.
+		cmd_ret r{};
+		FileName const probe_dir = createLyXTmpDir(FileName());
+		if (!probe_dir.empty()) {
+			FileName const saved = FileName::getcwd();
+			probe_dir.chdir();
+			r = runCommand(quoteName(prog) + ' ' + p.version_arg);
+			saved.chdir();
+			probe_dir.destroyDirectory();
+		}
 		smatch m;
 		regex const re(p.version_re);
 		if (r.valid && regex_search(r.result, m, re)) {
