safePath() function prevents path traversal attacks by validating that resolved paths remain within a trusted base directory. It is always active with no configuration toggle — every file system operation that accepts user-controlled path segments must use this function.
Path traversal is consistently ranked in the OWASP Top 10. The safePath() function is the single chokepoint for all file system path validation in the system. By centralizing this check, every file operation benefits from the same 5-layer defense without needing per-callsite validation logic.
Source:
packages/core/src/security/safe-path.ts — defensive path resolution with 5 attack vector mitigations.API
safePath
base parameter must be an absolute path to the trusted directory. The segments are path components to join under base.
Returns: The resolved, validated absolute path.
Throws: PathTraversalError if the resolved path escapes the base directory.
PathTraversalError
Attack Vectors and Defenses
The function defends against five categories of path traversal attacks:1. Basic Traversal (../ sequences)
Attack: Using ../ sequences to navigate above the base directory.
path.resolve() canonicalizes the path, then a prefix check verifies the resolved path starts with base + path.sep (or equals base exactly).
2. URL-Encoded Traversal (%2e%2e%2f)
Attack: Using URL-encoded characters to bypass naive string checks.
decodeURIComponent() before resolution. If decoding fails (malformed percent encoding), the raw segment is used instead.
3. Prefix Attacks (/uploads vs /uploads-evil)
Attack: Exploiting simple startsWith() checks where /data/uploads-evil starts with /data/uploads.
base exactly or start with base + path.sep. This prevents /data/uploads-evil from matching /data/uploads.
4. Null Byte Injection
Attack: Inserting null bytes to truncate the path at the OS level while passing application-level checks.\0) before any path resolution. If a null byte is found, PathTraversalError is thrown immediately.
5. Symlink Escapes
Attack: Creating a symlink inside the base directory that points to a location outside it.lstatSync(). If a component is a symbolic link, realpathSync() resolves its target and verifies the target remains within the base directory. If the symlink target escapes the base, PathTraversalError is thrown.
If an intermediate path component does not exist yet, the symlink check is skipped for that component. This is safe because non-existent paths cannot be symlinks.
Usage Patterns
Safe Usage
Error Handling
Blocked Attempts
The following path segments are all blocked when the base is/data/uploads:
Integration Points
safePath() is used throughout the codebase wherever file system operations accept user-controlled path segments:
| Component | Usage |
|---|---|
File tools (file.read, file.write) | Validate agent-provided file paths within workspace |
| Memory file store | Validate attachment paths within data directory |
| Skill discovery | Validate skill file paths within discovery directories |
| Media processing | Validate temp file paths within media temp directory |
| Config file loading | Validate config file paths against allowed directories |
| Exec tool CWD | Validate explicit cwd parameter within workspace |
safePath() validates file tool parameters (paths passed to read, write, etc.) but cannot protect arbitrary commands passed to the exec tool. For example, exec cat /etc/passwd bypasses safePath() entirely because the path is inside a shell command string, not a tool parameter. The Exec Sandbox provides OS-level filesystem isolation to close this gap.Defense Summary
| Attack Vector | Defense Mechanism | Throws |
|---|---|---|
../ traversal | path.resolve() + prefix check | PathTraversalError |
%2e%2e%2f encoded | decodeURIComponent() before resolve | PathTraversalError |
/uploads-evil prefix | Trailing separator guard (base + path.sep) | PathTraversalError |
Null byte \0 | Segment scan before any resolution | PathTraversalError |
| Symlink escape | lstatSync + realpathSync walk | PathTraversalError |
Design Notes
- No configuration:
safePath()is always active and cannot be disabled. There is no config toggle. - Synchronous: The function is synchronous (
lstatSync,realpathSync) because path validation must complete before any file I/O operation. - Pure validation: The function only validates paths — it does not create files or directories.
- Base must be absolute: The
baseparameter should always be an absolute path. Relative base paths would make the security guarantee dependent on the current working directory. - Base equals resolved: When no segments are provided (or segments resolve to base itself), the base path is returned unchanged. This is safe because the base is a trusted directory.
- Non-existent intermediate paths: If an intermediate component does not exist on disk, the symlink check is skipped for that component. This is safe because you cannot create a symlink at a path that does not exist.
- Re-thrown errors: If a
PathTraversalErroris thrown during symlink checking (e.g., the symlink target escapes base), it is re-thrown. All other file system errors (e.g., permission denied) are silently ignored since they indicate the path does not need symlink validation. - Thread safety: The function uses synchronous file system calls (
lstatSync,realpathSync) which are safe in Node.js single-threaded event loop. No race conditions between validation and usage when called immediately before file operations.
Security Model
Defense-in-depth security architecture
Tool Security
SSRF guard, tool policies, content scanner
Sandbox
Skill sandboxing implementation
