Comfy-Org/ComfyUI · error · ValueError

destination escapes base directory

Error message

destination escapes base directory

What it means

Raised by validate_path_within_base when the absolute candidate path is not located under the absolute base directory (Path.is_relative_to check). It is the final containment guard on upload destinations: after joining a hashed basename onto the resolved base dir, the result must still live inside that base, blocking path-traversal writes (e.g. via '..' segments or crafted filenames).

Source

Thrown at app/assets/services/path_utils.py:72

            bases = model_folder_paths[folder_name]
        except KeyError:
            raise ValueError(f"unknown model category '{folder_name}'")
        if not bases:
            raise ValueError(f"no base path configured for category '{folder_name}'")
        base_dir = os.path.abspath(bases[0])
    elif root == "input":
        base_dir = os.path.abspath(folder_paths.get_input_directory())
    else:
        base_dir = os.path.abspath(folder_paths.get_output_directory())

    return base_dir, []


def validate_path_within_base(candidate: str, base: str) -> None:
    cand_abs = Path(os.path.abspath(candidate))
    base_abs = Path(os.path.abspath(base))
    if not cand_abs.is_relative_to(base_abs):
        raise ValueError("destination escapes base directory")


def _compute_relative_path(child: str, parent: str) -> str:
    rel = os.path.relpath(os.path.abspath(child), os.path.abspath(parent))
    if rel == ".":
        return ""
    return rel.replace(os.sep, "/")


def _is_relative_to(child: str, parent: str) -> bool:
    return Path(os.path.abspath(child)).is_relative_to(os.path.abspath(parent))


def compute_asset_response_paths(file_path: str) -> tuple[str, str | None] | None:
    """Return (logical_path, display_name) for a file path.

    ``logical_path`` is the internal namespaced storage locator (e.g.
    ``models/checkpoints/foo/bar.safetensors``); ``display_name`` is the

View on GitHub (pinned to 1c6d8d45b3)

Solutions

  1. If you're a legitimate caller: don't craft extensions/subdirs — send a plain filename and let the server derive the extension; the hashed basename makes traversal impossible in normal flow.
  2. If you operate the server: ensure the input/output/model base directories are real directories, not symlinks whose targets confuse abspath containment checks.
  3. For security reviewers: this guard is the last line — also check the earlier extension length cap (<=16 chars) that constrains the join input.
Defensive patterns

Strategy: validation

Validate before calling

# Server-side: constrain extensions to a safe charset before the join
import re
SAFE_EXT = re.compile(r'^\.[A-Za-z0-9_-]{1,15}$')

def safe_ext(filename: str) -> str:
    ext = os.path.splitext(os.path.basename(filename))[1]
    return ext if SAFE_EXT.match(ext) else ''

Type guard

def destination_is_within(dest: str, base: str) -> bool:
    return Path(os.path.abspath(dest)).is_relative_to(Path(os.path.abspath(base)))

Try / catch

try:
    validate_path_within_base(dest_abs, base_dir)
except ValueError:
    raise UploadRejected('invalid destination')  # do not write bytes

Prevention

When it happens

Trigger: An upload whose client_filename/extension or tag-derived subdir produces a dest_abs outside base_dir — e.g. an extension containing path separators or '..' so os.path.join escapes the base; or a symlinked base where abspath resolution diverges. Called from ingest before any bytes are moved.

Common situations: Malicious or malformed filenames (ext like '/../../evil', sneaky unicode separators); mismatched path normalization between the tag resolution and the join; symlinked model/input directories making absolute-path comparison fail; security testing of the upload endpoint.

Related errors


AI-assisted analysis of Comfy-Org/ComfyUI@1c6d8d45b3 (2026-08-14). Data as JSON: /api/errors/2d82f3aae3222ebd. Report an issue: GitHub.