Skip to content

fix: sanitize LoRA paths and enable dynamic loading#1156

Open
MateusGPe wants to merge 6 commits intoleejet:masterfrom
MateusGPe:master
Open

fix: sanitize LoRA paths and enable dynamic loading#1156
MateusGPe wants to merge 6 commits intoleejet:masterfrom
MateusGPe:master

Conversation

@MateusGPe
Copy link

  • 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 and avoiding weight accumulation.

- 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.
@MateusGPe
Copy link
Author

Is this block optional or required? It has a clear disadvantage: users cannot organize their LoRA files into subfolders.

        // 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;
        }

Proposals:

  • Remove it;
  • Use a flag or macro to enable/disable;
  • Maintain as is.

@wbruna
Copy link
Contributor

wbruna commented Jan 1, 2026

* Force `LORA_APPLY_AT_RUNTIME` in the server to allow applying LoRAs dynamically per request without reloading the model and avoiding weight accumulation.

Since we have a parameter controlling that, the server should follow it. But I think it'd be OK to have its value default to at_runtime for the server.

@MateusGPe MateusGPe changed the title fix(server): sanitize LoRA paths and enable dynamic loading fix: sanitize LoRA paths and enable dynamic loading Jan 1, 2026
- 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.
Copy link
Author

@MateusGPe MateusGPe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problems that were identified have been resolved, I believe.

@MateusGPe MateusGPe requested a review from wbruna January 2, 2026 18:05
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned it as a simple comment, so it may have been overlooked: I believe this should follow the command-line flag (maybe keeping LORA_APPLY_AT_RUNTIME as a default).

Copy link
Contributor

@wbruna wbruna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, sanitize_lora_path seems to me more complex than needed.

If we don't want to support any absolute LoRA path at all, it'd be enough (after excluding an empty path) to canonicalize first, then forbid paths either absolute or starting with .. (the canonicalization will ensure all relevant .. elements will be effectively moved to the beginning).

(disclaimer: I have a Unix background. Weird Windows stuff like c:../directory could very well fall under the cracks; I don't know. But I'd appreciate some comments, if that's the case 🙂)

OTOH, the canonicalization+comparison with lora_model_dir is needed if we intend to support absolute paths for LoRAs as long as the final path falls under lora_model_dir - which could be better from a backward-compatibility POV. But in this case, neither .. elements nor absolute paths would demand special handling.

@MrDrMcCoy
Copy link

@MateusGPe, Do you have time to revisit this and get it across the finish line? It would be very useful to many of us.

roj234 added a commit to roj234/stable-diffusion.cpp that referenced this pull request Feb 3, 2026
roj234 added a commit to roj234/stable-diffusion.cpp that referenced this pull request Feb 3, 2026
@MateusGPe
Copy link
Author

MateusGPe commented Feb 4, 2026

First of all, @wbruna and @MrDrMcCoy I apologize for the delay; I was experiencing a high volume of work.

@wbruna , I tried to fix the problems pointed out, but this new version only offers a basic check:

  1. Windows root_name() Necessity
  • On Windows, D:model.bin has a root_name() of "D:"
  • Without this check, fs::path("C:\\lora") / fs::path("D:model.bin") would indeed resolve to D:model.bin, completely bypassing the base directory
  • This is a critical Windows-specific vulnerability
  1. UNC Path Protection
  • \\AttackerServer\Share\file has root_name() of "\\AttackerServer" on Windows
  • The root_name() check blocks this, preventing network path injection
  1. Linux Root Protection
  • is_absolute() returns true for /etc/shadow on POSIX systems
  • This prevents absolute path injection on Linux/macOS
  1. Traversal Guard
  • The find("..") check does provide basic protection against ../../../etc/passwd style attacks
  • However, it's not foolproof
Input Path root_name() root_directory() is_absolute() Security Risk
C:\Windows "C:" "" true Low (Standard absolute path)
C:Windows "C:" "" (Empty) false Critical: Drive-relative injection
\Windows "" (Empty) "" false High: Rooted relative to current drive
\Server\Share "\Server\Share" "" (Empty) true Medium: Network injection / NTLM Leak
\?\C:\Windows "\?\C:" "" true High: Bypasses Win32 normalization
/etc/passwd "" (Empty) "" (Empty) false Low: Relative path on Windows (invalid char / in some contexts, or interpreted as relative)

@MateusGPe MateusGPe requested a review from wbruna February 5, 2026 21:42
Copy link
Contributor

@wbruna wbruna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sanitizing seems pretty straightforward now.

@wbruna
Copy link
Contributor

wbruna commented Feb 7, 2026

Related: #1256

roj234 added a commit to roj234/stable-diffusion.cpp that referenced this pull request Feb 21, 2026
roj234 added a commit to roj234/stable-diffusion.cpp that referenced this pull request Feb 21, 2026
roj234 added a commit to roj234/stable-diffusion.cpp that referenced this pull request Feb 21, 2026
@candrews
Copy link

Can this effort please be resumed? :)

@leejet
Copy link
Owner

leejet commented Mar 15, 2026

Thanks for the contribution. However, for the server implementation, I’m not inclined to extract LoRA from the prompt. Instead, I suggest using a separate LoRA field, which is already implemented in sdapi.

@mostlygeek
Copy link

hi @leejet,

Just to check if I understand the recommendation correctly. This would mean that /v1/images/generations does not support using a lora.

Instead, I should use POST /sdapi/v1/txt2img with a body like:


{
  "prompt": "a cat in a forest",
  "lora": [
    {
      "path": "my_style.safetensors",
      "multiplier": 0.8,
      "is_high_noise": false
    }
  ]
}

@wbruna
Copy link
Contributor

wbruna commented Mar 15, 2026

One option could be a field inside <sd_cpp_extra_args>, although functionally it wouldn't be that different from the <lora:name:value> syntax.

And if we'll be extending the OpenAI API anyway, I'd suggest a 'lora' field with the same schema as the sdapi; but kept inside a top-level sd_cpp_extra_args field, so we won't risk conflicts if we ever need any other field.

@MrDrMcCoy
Copy link

Whichever solution we land on, I would humbly request clear usage documentation on the following:

  • Server CLI arguments and how they relate to API functionality.
  • Usage of the API for single and multiple LoRAs.
  • If LoRAs are to be defined separately from the prompt, clearly explain how this impacts load order and trigger words. Most guidance I've seen suggests that this closely follows how, when, and where LoRA tags appear within a prompt. Clarity on how things work with them being separate, even if there is functionality no difference, will likely alleviate confusion for users.

@mostlygeek
Copy link

Having a top level sdd_cpp_extra_args be great.

Would it make sense to support a similar json schema to txt2img and img2img?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants