mastra-ai/mastra · error
[FilesystemStorage] path must be a non-empty relative path.
Error message
[FilesystemStorage] path must be a non-empty relative path.
What it means
assertRelativePath enforces that file paths stored in FilesystemStorage are non-empty, relative, and composed only of safe segments — no leading '/', no empty, '.', or '..' segments. This prevents path traversal and absolute-path writes. Violating any of these throws this error via validateFiles.
Source
Thrown at mastracode/factory/src/storage/domains/filesystem/base.ts:53
interface FilesystemSnapshotDbRow extends Record<string, unknown> {
id: string;
resource_id: string;
thread_id: string;
files: FilesystemFile[];
captured_at: Date;
}
function assertIdentifier(value: string, label: string): void {
if (!value.trim()) throw new Error(`[FilesystemStorage] ${label} must not be empty.`);
}
function assertRelativePath(value: string): void {
if (
!value ||
value.startsWith('/') ||
value.split('/').some(segment => !segment || segment === '.' || segment === '..')
) {
throw new Error('[FilesystemStorage] path must be a non-empty relative path.');
}
}
function assertScope(args: { resourceId: string; threadId: string }): void {
assertIdentifier(args.resourceId, 'resourceId');
assertIdentifier(args.threadId, 'threadId');
}
function validateFiles(files: FilesystemFile[]): void {
const paths = new Set<string>();
for (const file of files) {
assertRelativePath(file.path);
if (paths.has(file.path)) throw new Error(`[FilesystemStorage] duplicate file path: ${file.path}`);
paths.add(file.path);
}
}
View on GitHub (pinned to 75dd419e61)
Solutions
- Strip the storage root prefix so paths are relative
- Sanitize/reject segments equal to '.', '..' or empty before calling the API
- Normalize user-provided filenames (remove traversal, collapse slashes)
- Use a path-join helper that always yields relative, clean segments
Example fix
// before
await storage.replaceFiles({ resourceId, threadId, files: [{ path: `/uploads/${userFile}`, ... }] });
// after
const safe = userFile.replaceAll('..', '').replace(/^\/+/, '');
await storage.replaceFiles({ resourceId, threadId, files: [{ path: `uploads/${safe}`, ... }] }); Defensive patterns
Strategy: validation
Validate before calling
function isSafeRelativePath(p: string): boolean {
return !!p && !p.startsWith('/') && p.split('/').every(s => !!s && s !== '.' && s !== '..');
}
if (!isSafeRelativePath(file.path)) throw new Error(`Unsafe path: ${file.path}`); Type guard
function isFilesystemFile(f: unknown): f is FilesystemFile {
return typeof f === 'object' && f !== null &&
typeof (f as FilesystemFile).path === 'string' && isSafeRelativePath((f as FilesystemFile).path);
} Try / catch
try {
await storage.replaceFiles({ resourceId, threadId, files });
} catch (e) {
if (e instanceof Error && e.message.includes('path must be a non-empty relative path')) {
throw new BadRequest(`Invalid file path in batch: ${e.message}`);
}
throw e;
} Prevention
- Always store paths relative to the storage root
- Sanitize user-supplied filenames (strip '..' and leading slashes)
- Write a shared sanitizePath helper and use it for every write
- Add unit tests for traversal attempts like '../../etc/passwd'
When it happens
Trigger: Calling a file-writing method (e.g. via replaceFiles → validateFiles) with a FilesystemFile whose path is '', starts with '/', or contains segments like '..', './', or double slashes.
Common situations: Joining an absolute filesystem path directly instead of a storage-relative path; user-supplied filenames containing '..'; path.normalize output retaining a leading slash; string concatenation producing '//'.
Related errors
- ${label} must be relative
- ${label} escapes workspace
- Path escapes workspace
- Invalid resourceId: ${resourceId}
- Invalid mount path: ${mountPath}. Must be an absolute path w
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/4fb2d907b78b6874.
Report an issue: GitHub.