diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 157d3ed8..169bd040 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -21,3 +21,7 @@ **Vulnerability:** `CustomEmployeeManager.Register` accepted arbitrary employee IDs without validation, which were later used directly in `Path.Combine` to construct image loading paths, enabling path traversal (CWE-22). **Learning:** Identifiers provided by mods or external sources must be treated as untrusted input and validated before being used in file system operations. **Prevention:** Validate input strings that form part of a file path before concatenating them. Reject them if they contain directory traversal characters like `..`, `Path.DirectorySeparatorChar`, `Path.AltDirectorySeparatorChar`, or any invalid filename characters (using `Path.GetInvalidFileNameChars()`). +## 2024-05-24 - Path Traversal in CustomEmployeeManager +**Vulnerability:** Found a path traversal vulnerability in `CustomEmployeeManager.SetPortrait` where `employeeId` was directly concatenated to a path using `Path.Combine`. This allowed an attacker to potentially read arbitrary image files outside the intended ModAssets directory by passing payloads like `../../../sensitive_file`. +**Learning:** String identifiers (like `employeeId`, `modId`) passed to file system operations must always be validated against directory traversal attacks, even if they might be validated at registration, to protect against direct calls or deserialization bypasses. +**Prevention:** Always validate input using `IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || Contains("..")` to ensure the input cannot contain directory separators or traversal sequences before using it in a file path. Avoid using `id.Contains(Path.DirectorySeparatorChar)` as it is not supported in older .NET frameworks. diff --git a/src/API/CustomEmployeeManager.cs b/src/API/CustomEmployeeManager.cs index 9b4512b6..8b5703f8 100644 --- a/src/API/CustomEmployeeManager.cs +++ b/src/API/CustomEmployeeManager.cs @@ -891,10 +891,16 @@ private static void SetPortrait(Transform card, string employeeId) string assetsDir = Path.Combine(MelonEnvironment.UserDataDirectory, "ModAssets"); string? imagePath = null; - foreach (var ext in new[] { ".jpg", ".png" }) + + // SECURITY: Validate employeeId to prevent path traversal attacks. + // Using IndexOfAny and Contains("..") to ensure no directory escaping. + if (!string.IsNullOrEmpty(employeeId) && employeeId.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 && !employeeId.Contains("..")) { - string candidate = Path.Combine(assetsDir, employeeId + ext); - if (File.Exists(candidate)) { imagePath = candidate; break; } + foreach (var ext in new[] { ".jpg", ".png" }) + { + string candidate = Path.Combine(assetsDir, employeeId + ext); + if (File.Exists(candidate)) { imagePath = candidate; break; } + } } if (imagePath != null)