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
- Sanitize keys: use relative, flat keys without `..` or leading `/`
- Pass an absolute path located inside the store root if an absolute path is required
- 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
- Treat LLM/user-supplied keys as untrusted; normalize before use
- Use flat, generated keys (uuid/slug) with no separators or dots
- Keep symlinks out of the store root
- Log rejected traversal attempts
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
- Document with ID '{doc.id}' has a file path '{file_path}' th
- tool_concurrency_limit must be greater than or equal to 1.
- Tool execution requires at least one tool.
- Refusing to deserialize an OutputAdapter with unsafe=True wh
- Refusing to deserialize an OutputAdapter with custom filters
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/67989a1f52cec98e.
Report an issue: GitHub.