>From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Pavel Sanda <sanda () lyx org>
Date: Thu, 16 Jul 2026 19:06:25 +0200
Subject: [PATCH] Hardening case 00e - arbitrary command execution via index_command

A document-set \index_command was copied verbatim into the index-processor
call on export, giving program substitution and <> redirection (file overwrite,
same primitive as 00k), and/or code injection via metachars on windows.

Fires on export.

Tier 00 hotfix: accept only known index cmd, reject <> (+metas on win)
Tier 01 DiD will land in master (canonical split, shared with 00d).

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

diff --git a/src/Converter.cpp b/src/Converter.cpp
index 03172ac2ae..98f029376b 100644
--- a/src/Converter.cpp
+++ b/src/Converter.cpp
@@ -498,8 +498,39 @@ Converters::RetVal Converters::convert(Buffer const * buffer,
 			&& bp.encoding().package() == Encoding::japanese;
 		runparams.use_indices = bp.use_indices;
 		runparams.bibtex_command = bp.bibtexCommand(true);
-		runparams.index_command = (bp.index_command == "default") ?
-			string() : bp.index_command;
+
+		// Accept only programs from fixed known list
+		string accepted_index_cmd;
+		if (bp.index_command != "default" && !bp.index_command.empty()) {
+
+			// Block redirection (+ metas on Windows where it sinks to shell)
+#if defined(_WIN32)
+			static char const * const SUSPECT_CHARS = "<>" "&|()^%;";
+#else
+			static char const * const SUSPECT_CHARS = "<>";
+#endif
+			bool const has_suspect =
+				bp.index_command.find_first_of(SUSPECT_CHARS) != string::npos;
+			if (!has_suspect) {
+				string supplied_prog;
+				split(bp.index_command, supplied_prog, ' ');
+				for (auto const & alt : lyxrc.index_alternatives) {
+					string alt_prog;
+					split(alt, alt_prog, ' ');
+					if (!supplied_prog.empty()
+					    && supplied_prog == alt_prog) {
+						accepted_index_cmd = bp.index_command;
+						break;
+					}
+				}
+			}
+			if (accepted_index_cmd.empty())
+				LYXERR0("Document-supplied index command '"
+					<< bp.index_command << "' is not a recognised "
+					"index processor; falling back to default.");
+		}
+
+		runparams.index_command = accepted_index_cmd;
 		runparams.document_language = bp.language->lang();
 		// Some macros rely on font encoding
 		runparams.main_fontenc = bp.main_font_encoding();
