>From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Pavel Sanda <sanda () lyx org>
Date: Thu, 11 Jun 2026 02:37:55 +0200
Subject: [PATCH] Hardening case 00g - arbitrary command execution via mangled filename extension

mangledFileName() sanitized the base of a copied file's name but re-attached
its extension unsanitized; the mangled name reaches shell command lines in
shipped conversion helpers on export, so a hostile graphics extension injects
arbitrary commands.

Fires on export.

Tier 00 fix: sanitize the extension too in mangledFileName(), drop ;=

Assisted-by: Claude Opus 4.7
---
 src/support/FileName.cpp  | 12 +++++++-----
 src/support/filetools.cpp |  3 ++-
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/support/FileName.cpp b/src/support/FileName.cpp
index e49f76e90c..2b8e0243f3 100644
--- a/src/support/FileName.cpp
+++ b/src/support/FileName.cpp
@@ -990,11 +990,11 @@ string DocFileName::mangledFileName(string const & dir, bool encrypt_path) const
 	// xHTML route
 	// we use hash instead of counter to get stable filenames in export directory
 	if (encrypt_path) {
-		// sanitization probably not neccessary for xhtml, but won't harm
 		string sanfn = support::changeExtension(onlyFileName(), string());
 		sanfn = sanitizeFileName(sanfn);
-		// Add the extension back on
-		sanfn = support::changeExtension(sanfn, getExtension(onlyFileName()));
+		// extension is user-controlled string, suppress metacharacters
+		sanfn = support::changeExtension(sanfn,
+				sanitizeFileName(getExtension(onlyFileName())));
 
 		//various filesystems have filename limit around 2^8
 		if (sanfn.length() > 230)
@@ -1014,8 +1014,10 @@ string DocFileName::mangledFileName(string const & dir, bool encrypt_path) const
 		mname = support::changeExtension(name, string());
 		// The mangled name must be a valid LaTeX name.
 		mname = sanitizeFileName(mname);
-		// Add the extension back on
-		mname = support::changeExtension(mname, getExtension(name));
+		// Add the extension back on, but sanitize from metachars,
+		// it's user-controlled string
+		mname = support::changeExtension(mname,
+				sanitizeFileName(getExtension(name)));
 
 		// Prepend a counter to the filename. This is necessary to make
 		// the mangled name unique, see truncation below.
diff --git a/src/support/filetools.cpp b/src/support/filetools.cpp
index e0dc4e7654..abb4d05840 100644
--- a/src/support/filetools.cpp
+++ b/src/support/filetools.cpp
@@ -1363,9 +1363,10 @@ std::string sanitizeFileName(const std::string & str)
 	// are forbidden: '/', '.', ' ', and ':'.
 	// On windows it is not possible to create files with '<', '>' or '?'
 	// in the name.
+	// We forbid ';', '=' as they could become active in shell.
 	static std::string const keep = "abcdefghijklmnopqrstuvwxyz"
 	                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-	                           "+-0123456789;=";
+	                           "+-0123456789";
 
 	std::string name = str;
 	string::size_type pos = 0;
