agentscope-ai/agentscope · error · ValueError
Invalid blob key: {key!r}
Error message
Invalid blob key: {key!r} What it means
LocalBlobStore._path_for validates every blob key before mapping it to a filesystem path under the store's root. It raises ValueError('Invalid blob key: ...') when the key is empty, starts with '/' (absolute path), or contains a '..' path component. This is a path-traversal guard shared by write_stream, open, size, delete, and exists.
Source
Thrown at src/agentscope/app/rag/blob_store/_local.py:69
def _path_for(self, key: str) -> Path:
"""Resolve a backend-relative key to an absolute filesystem path.
Rejects keys that try to escape :attr:`_root` via ``..`` or
absolute paths. Callers pick keys server-side so this is
defensive rather than a primary trust boundary, but we still
refuse to write outside the root.
Args:
key (`str`):
Backend-relative key.
Returns:
`Path`:
The absolute filesystem path within :attr:`_root`.
"""
if not key or key.startswith("/") or ".." in Path(key).parts:
raise ValueError(f"Invalid blob key: {key!r}")
path = (self._root / key).resolve()
if self._root not in path.parents and path != self._root:
raise ValueError(f"Blob key {key!r} escapes the root directory.")
return path
def _key_from_uri(self, uri: str) -> str:
"""Extract the backend-relative key from a ``local://`` URI."""
if not uri.startswith(_SCHEME):
raise ValueError(f"Not a local blob URI: {uri!r}")
return uri[len(_SCHEME) :]
async def write_stream(self, key: str, stream: IO[bytes]) -> str:
"""Copy ``stream`` into the blob at ``key`` in 1 MiB chunks.
Creates intermediate directories as needed. Existing blobs at
the same key are overwritten — keys are generated server-side
from document ids, so collisions only happen on intentional
re-uploads.View on GitHub (pinned to e90f1c7592)
Solutions
- Pass a relative key like 'kb123/chunk.bin', never a URI or absolute path
- Strip the scheme first: use the store's _key_from_uri helper or uri.removeprefix('local://') if you hold a URI
- Validate/sanitize user-supplied keys: reject empty, absolute, and '..'-containing segments before calling the store
- Normalize with PurePosixPath(key).parts to inspect segments
Example fix
# before
await store.write_stream('/data/abc.bin', stream) # ValueError: Invalid blob key
# after
await store.write_stream('data/abc.bin', stream) Defensive patterns
Strategy: validation
Validate before calling
from pathlib import PurePosixPath
def valid_blob_key(key: str) -> bool:
if not key or key.startswith('/'):
return False
return '..' not in PurePosixPath(key).parts
assert valid_blob_key(key), f'bad blob key {key!r}' Type guard
from pathlib import PurePosixPath
def is_valid_blob_key(key: str) -> bool:
return bool(key) and not key.startswith('/') and '..' not in PurePosixPath(key).parts Try / catch
try:
await store.write_stream(key, stream)
except ValueError as e:
if 'Invalid blob key' in str(e):
... # sanitize key and retry
raise Prevention
- Always pass relative POSIX keys ('kb/blob.bin'), never URIs or absolute paths
- Map user-supplied filenames to opaque ids (uuid/hash) before using as keys
- Strip 'local://' before passing a URI-derived string as a key
When it happens
Trigger: Calling any LocalBlobStore blob method with key='' , key='/abs/path', or key='a/../../etc/passwd'. Windows-style components or URLs (e.g. 'local://foo') passed as keys also fail the checks.
Common situations: Passing a full local:// URI where a bare key is expected; constructing keys from unvalidated user input; joining paths with a leading slash; sending filenames containing '..' from an upload form.
Related errors
- Unsafe upload path: {entry.path!r}
- Blob key {key!r} escapes the root directory.
- Not a local blob URI: {uri!r}
- Malformed S3 blob URI: {uri!r}
- Bucket {bucket!r} in URI {uri!r} does not match configured b
AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28).
Data as JSON: /api/errors/ec20cdeb16f321a1.
Report an issue: GitHub.