deepset-ai/haystack · error

{subject} '{path_like}' resolves outside the store root '{ro

Error message

{subject} '{path_like}' resolves outside the store root '{root}'.

What it means

The offloading store resolves every requested path against its root directory and rejects anything that escapes it via .., symlinks, or absolute paths pointing elsewhere. This prevents path traversal when reading or writing offloaded tool results. _resolve_in_root raises ValueError naming the offending path and the store root.

Source

Thrown at haystack/hooks/tool_result_offloading/stores.py:49

        self.root = Path(root)

    def _resolve_in_root(self, path_like: str | Path, *, subject: str) -> Path:
        """
        Resolve a path-like value and ensure it stays within the configured store root.

        Relative values are interpreted relative to `self.root`; absolute values are used as-is.

        :param path_like: Relative or absolute path-like value to resolve.
        :param subject: Human-readable label used in the error message.
        :returns: The resolved absolute path within the store root.
        :raises ValueError: If the resolved path escapes the store root.
        """
        root = self.root.resolve()
        path = Path(path_like)
        candidate = path if path.is_absolute() else root / path
        resolved = candidate.resolve()
        if not resolved.is_relative_to(root):
            raise ValueError(f"{subject} '{path_like}' resolves outside the store root '{root}'.")
        return resolved

    def write(self, *, key: str, content: str) -> str:
        """
        Write `content` to `<root>/<key>`, creating parent directories, and return the file path.

        The resolved target must stay within the root directory: a `key` that escapes it (e.g. containing `../` or an
        absolute path) is rejected, so a tool-provided key cannot write outside the store.

        :param key: Relative file name for the result within the store root.
        :param content: The tool result to persist.
        :returns: The absolute path the content was written to, as a string, for use with `read`.
        :raises ValueError: If `key` resolves to a location outside the store root.
        """
        path = self._resolve_in_root(key, subject="Result key")
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(content, encoding="utf-8")
        return str(path)

View on GitHub (pinned to e318778c9b)

Solutions

  1. Sanitize keys: use relative, flat keys without `..` or leading `/`
  2. Pass an absolute path located inside the store root if an absolute path is required
  3. Point the store root at the intended directory, or relocate the target file inside it

Example fix

// before
store.read(key="../../../secrets.txt")  # ValueError
// after
store.read(key="results/answer.txt")  # stays inside root
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
root = Path(store.root).resolve()
candidate = (root / key) if not Path(key).is_absolute() else Path(key)
if not candidate.resolve().is_relative_to(root):
    raise ValueError(f"key {key!r} escapes store root")

Type guard

from pathlib import Path
def is_safe_key(key: str, root: Path) -> bool:
    p = Path(key)
    candidate = p if p.is_absolute() else root / p
    return candidate.resolve().is_relative_to(root.resolve())

Try / catch

try:
    path = store.read(key=key)
except ValueError as e:
    if "resolves outside the store root" in str(e):
        key = sanitize_key(key)  # strip .., absolute prefixes
        path = store.read(key=key)
    else:
        raise

Prevention

When it happens

Trigger: Calling write/read with key/content paths containing `..` segments, an absolute path outside the store root, or a path whose resolved symlink target lies outside root.

Common situations: LLM-generated or user-supplied file keys like `../../etc/passwd`; misconfigured root that differs from where legacy data lives; symlinked directories inside the store pointing to external locations.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/67989a1f52cec98e. Report an issue: GitHub.