From 4aaca67479469faab232dc276afe12acdcd7f801 Mon Sep 17 00:00:00 2001 From: mateusgpe Date: Wed, 31 Dec 2025 18:42:23 -0300 Subject: [PATCH 1/4] fix(server): sanitize LoRA paths and enable dynamic loading - Implement `sanitize_lora_path` in `SDGenerationParams` to prevent directory traversal attacks via LoRA tags in prompts. - Restrict LoRA paths to be relative and strictly within the configured LoRA directory (no subdirectories allowed, optional? drawback: users cannot organize their LoRAs into subfolders.). - Update server example to pass `lora_model_dir` to `process_and_check`, enabling LoRA extraction from prompts. - Force `LORA_APPLY_AT_RUNTIME` in the server to allow applying LoRAs dynamically per request without reloading the model. --- examples/common/common.hpp | 67 +++++++++++++++++++++++++++++++++++--- examples/server/main.cpp | 5 +-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/examples/common/common.hpp b/examples/common/common.hpp index 7ea95ed14..7f869868c 100644 --- a/examples/common/common.hpp +++ b/examples/common/common.hpp @@ -1601,6 +1601,63 @@ struct SDGenerationParams { return true; } + static bool sanitize_lora_path(const std::string& lora_model_dir, + const std::string& raw_path_str, + fs::path& full_path) { + if (lora_model_dir.empty()) { + return false; + } + + fs::path raw_path(raw_path_str); + + // Disallow absolute paths. + if (raw_path.is_absolute()) { + LOG_WARN("lora path must be relative: %s", raw_path_str.c_str()); + return false; + } + + // Disallow '..' in the raw path to prevent basic traversal attempts. + for (const auto& part : raw_path) { + if (part == "..") { + LOG_WARN("lora path cannot contain '..': %s", raw_path_str.c_str()); + return false; + } + } + + fs::path lora_dir(lora_model_dir); + full_path = lora_dir / raw_path; + + // --- Security Checks on Canonical Path --- + // Canonicalize paths to resolve symlinks and normalize separators for robust checks. + // weakly_canonical is used because the target file might not exist yet. + auto canonical_lora_dir = fs::weakly_canonical(lora_dir); + auto canonical_full_path = fs::weakly_canonical(full_path); + + // 1. The resolved path must not be a directory. + if (fs::is_directory(canonical_full_path)) { + LOG_WARN("lora path resolved to a directory, not a file: %s", raw_path_str.c_str()); + return false; + } + + // 2. The file must be inside the designated lora directory. + // We check this by ensuring the relative path does not climb up with '..'. + fs::path relative_path = canonical_full_path.lexically_relative(canonical_lora_dir); + for (const auto& part : relative_path) { + if (part == "..") { + LOG_WARN("lora path is outside of the lora model directory: %s", raw_path_str.c_str()); + return false; + } + } + + // 3. The file must be directly in the lora directory, not in a subdirectory. + if (relative_path.has_parent_path() && !relative_path.parent_path().empty()) { + LOG_WARN("lora path in subdirectories is not allowed: %s", raw_path_str.c_str()); + return false; + } + + return true; + } + void extract_and_remove_lora(const std::string& lora_model_dir) { if (lora_model_dir.empty()) { return; @@ -1632,10 +1689,10 @@ struct SDGenerationParams { } fs::path final_path; - if (is_absolute_path(raw_path)) { - final_path = raw_path; - } else { - final_path = fs::path(lora_model_dir) / raw_path; + if (!sanitize_lora_path(lora_model_dir, raw_path, final_path)) { + tmp = m.suffix().str(); + prompt = std::regex_replace(prompt, re, "", std::regex_constants::format_first_only); + continue; } if (!fs::exists(final_path)) { bool found = false; @@ -1643,7 +1700,7 @@ struct SDGenerationParams { fs::path try_path = final_path; try_path += ext; if (fs::exists(try_path)) { - final_path = try_path; + final_path = try_path.lexically_normal(); found = true; break; } diff --git a/examples/server/main.cpp b/examples/server/main.cpp index c540958f8..69c75d322 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -293,6 +293,7 @@ int main(int argc, const char** argv) { LOG_DEBUG("%s", default_gen_params.to_string().c_str()); sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(false, false, false); + ctx_params.lora_apply_mode = LORA_APPLY_AT_RUNTIME; sd_ctx_t* sd_ctx = new_sd_ctx(&sd_ctx_params); if (sd_ctx == nullptr) { @@ -414,7 +415,7 @@ int main(int argc, const char** argv) { return; } - if (!gen_params.process_and_check(IMG_GEN, "")) { + if (!gen_params.process_and_check(IMG_GEN, ctx_params.lora_model_dir)) { res.status = 400; res.set_content(R"({"error":"invalid params"})", "application/json"); return; @@ -592,7 +593,7 @@ int main(int argc, const char** argv) { return; } - if (!gen_params.process_and_check(IMG_GEN, "")) { + if (!gen_params.process_and_check(IMG_GEN, ctx_params.lora_model_dir)) { res.status = 400; res.set_content(R"({"error":"invalid params"})", "application/json"); return; From 4b80b61003aa06f41c6bdec47ff926e37007b87d Mon Sep 17 00:00:00 2001 From: mateusgpe Date: Thu, 1 Jan 2026 15:24:01 -0300 Subject: [PATCH 2/4] fix: sanitize LoRA paths and enable dynamic loading - Remove the restriction that LoRA models must be in the root of the LoRA directory, allowing them to be organized in subfolders. - Refactor the directory containment check to use `std::mismatch` instead of `lexically_relative` to verify the path is inside the allowed root. - Remove redundant `lexically_normal()` call when resolving file extensions. --- examples/common/common.hpp | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/examples/common/common.hpp b/examples/common/common.hpp index 7f869868c..a2e919409 100644 --- a/examples/common/common.hpp +++ b/examples/common/common.hpp @@ -1610,13 +1610,12 @@ struct SDGenerationParams { fs::path raw_path(raw_path_str); - // Disallow absolute paths. + // Disallow absolute paths and '..' components if (raw_path.is_absolute()) { LOG_WARN("lora path must be relative: %s", raw_path_str.c_str()); return false; } - // Disallow '..' in the raw path to prevent basic traversal attempts. for (const auto& part : raw_path) { if (part == "..") { LOG_WARN("lora path cannot contain '..': %s", raw_path_str.c_str()); @@ -1624,34 +1623,26 @@ struct SDGenerationParams { } } + // Construct and canonicalize paths fs::path lora_dir(lora_model_dir); full_path = lora_dir / raw_path; - // --- Security Checks on Canonical Path --- - // Canonicalize paths to resolve symlinks and normalize separators for robust checks. - // weakly_canonical is used because the target file might not exist yet. auto canonical_lora_dir = fs::weakly_canonical(lora_dir); auto canonical_full_path = fs::weakly_canonical(full_path); - // 1. The resolved path must not be a directory. + // Check if path is a directory if (fs::is_directory(canonical_full_path)) { LOG_WARN("lora path resolved to a directory, not a file: %s", raw_path_str.c_str()); return false; } - // 2. The file must be inside the designated lora directory. - // We check this by ensuring the relative path does not climb up with '..'. - fs::path relative_path = canonical_full_path.lexically_relative(canonical_lora_dir); - for (const auto& part : relative_path) { - if (part == "..") { - LOG_WARN("lora path is outside of the lora model directory: %s", raw_path_str.c_str()); - return false; - } - } + // Verify path stays within lora directory + auto [root_end, nothing] = std::mismatch( + canonical_lora_dir.begin(), canonical_lora_dir.end(), + canonical_full_path.begin(), canonical_full_path.end()); - // 3. The file must be directly in the lora directory, not in a subdirectory. - if (relative_path.has_parent_path() && !relative_path.parent_path().empty()) { - LOG_WARN("lora path in subdirectories is not allowed: %s", raw_path_str.c_str()); + if (root_end != canonical_lora_dir.end()) { + LOG_WARN("lora path is outside of the lora model directory: %s", raw_path_str.c_str()); return false; } @@ -1700,7 +1691,7 @@ struct SDGenerationParams { fs::path try_path = final_path; try_path += ext; if (fs::exists(try_path)) { - final_path = try_path.lexically_normal(); + final_path = try_path; found = true; break; } From 117281d8f119062b9fe2bdeea11e831ca901d4b1 Mon Sep 17 00:00:00 2001 From: mateusgpe Date: Wed, 4 Feb 2026 18:11:11 -0300 Subject: [PATCH 3/4] fix: sanitize LoRA paths and enable dynamic loading --- examples/common/common.hpp | 50 +++++++++----------------------------- examples/server/main.cpp | 3 +-- 2 files changed, 13 insertions(+), 40 deletions(-) diff --git a/examples/common/common.hpp b/examples/common/common.hpp index 1133944bc..da23af597 100644 --- a/examples/common/common.hpp +++ b/examples/common/common.hpp @@ -934,7 +934,7 @@ struct SDContextParams { return oss.str(); } - sd_ctx_params_t to_sd_ctx_params_t(bool vae_decode_only, bool free_params_immediately, bool taesd_preview) { + sd_ctx_params_t to_sd_ctx_params_t(bool vae_decode_only, bool free_params_immediately, bool taesd_preview, bool is_server = false) { embedding_vec.clear(); embedding_vec.reserve(embedding_map.size()); for (const auto& kv : embedding_map) { @@ -944,6 +944,11 @@ struct SDContextParams { embedding_vec.emplace_back(item); } + if(is_server && lora_apply_mode == LORA_APPLY_AUTO) + { + lora_apply_mode = LORA_APPLY_AT_RUNTIME; + } + sd_ctx_params_t sd_ctx_params = { model_path.c_str(), clip_l_path.c_str(), @@ -1631,48 +1636,17 @@ struct SDGenerationParams { static bool sanitize_lora_path(const std::string& lora_model_dir, const std::string& raw_path_str, fs::path& full_path) { - if (lora_model_dir.empty()) { - return false; - } - - fs::path raw_path(raw_path_str); - - // Disallow absolute paths and '..' components - if (raw_path.is_absolute()) { - LOG_WARN("lora path must be relative: %s", raw_path_str.c_str()); - return false; - } - - for (const auto& part : raw_path) { - if (part == "..") { - LOG_WARN("lora path cannot contain '..': %s", raw_path_str.c_str()); - return false; - } - } - - // Construct and canonicalize paths - fs::path lora_dir(lora_model_dir); - full_path = lora_dir / raw_path; - - auto canonical_lora_dir = fs::weakly_canonical(lora_dir); - auto canonical_full_path = fs::weakly_canonical(full_path); - - // Check if path is a directory - if (fs::is_directory(canonical_full_path)) { - LOG_WARN("lora path resolved to a directory, not a file: %s", raw_path_str.c_str()); + if (lora_model_dir.empty()) return false; - } - // Verify path stays within lora directory - auto [root_end, nothing] = std::mismatch( - canonical_lora_dir.begin(), canonical_lora_dir.end(), - canonical_full_path.begin(), canonical_full_path.end()); + fs::path raw_p(raw_path_str); - if (root_end != canonical_lora_dir.end()) { - LOG_WARN("lora path is outside of the lora model directory: %s", raw_path_str.c_str()); + if (raw_p.is_absolute() || + !raw_p.root_name().empty() || + raw_path_str.find("..") != std::string::npos) { return false; } - + full_path = fs::path(lora_model_dir) / raw_p; return true; } diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 483a78eb9..0daebfd00 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -283,8 +283,7 @@ int main(int argc, const char** argv) { LOG_DEBUG("%s", ctx_params.to_string().c_str()); LOG_DEBUG("%s", default_gen_params.to_string().c_str()); - sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(false, false, false); - ctx_params.lora_apply_mode = LORA_APPLY_AT_RUNTIME; + sd_ctx_params_t sd_ctx_params = ctx_params.to_sd_ctx_params_t(false, false, false, true); sd_ctx_t* sd_ctx = new_sd_ctx(&sd_ctx_params); if (sd_ctx == nullptr) { From a492cda847088d1af9ec7633c7ff444940e92ac4 Mon Sep 17 00:00:00 2001 From: mateusgpe Date: Wed, 4 Feb 2026 18:24:14 -0300 Subject: [PATCH 4/4] fix: sanitize LoRA paths and enable dynamic loading --- examples/server/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 0daebfd00..0969505a6 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -408,7 +408,7 @@ int main(int argc, const char** argv) { if (gen_params.sample_params.sample_steps > 100) gen_params.sample_params.sample_steps = 100; - if (!gen_params.process_and_check(IMG_GEN, "")) { + if (!gen_params.process_and_check(IMG_GEN, ctx_params.lora_model_dir)) { res.status = 400; res.set_content(R"({"error":"invalid params"})", "application/json"); return; @@ -589,7 +589,7 @@ int main(int argc, const char** argv) { if (gen_params.sample_params.sample_steps > 100) gen_params.sample_params.sample_steps = 100; - if (!gen_params.process_and_check(IMG_GEN, "")) { + if (!gen_params.process_and_check(IMG_GEN, ctx_params.lora_model_dir)) { res.status = 400; res.set_content(R"({"error":"invalid params"})", "application/json"); return;