T09 · Insecure Skill Coding Practices
- Location
examples/file-manager-skill/index.ts:34- Finding
Path Containment Bypass Permits File Operations Outside the Configured Base Directory
- Content
View full analysis
Vulnerability Details
File Location:
examples/file-manager-skill/index.ts:34-42
Vulnerability Type: Improper path containment validation
Risk Level: HighVulnerable Code
ts private resolvePath(filePath: string): string { const base = this.config.basePath || process.cwd(); // Prevent directory traversal const resolved = path.resolve(base, filePath); if (!resolved.startsWith(path.resolve(base))) { throw new Error('Access denied: Path outside base directory'); } return resolved; }The resulting path is subsequently passed to privileged filesystem operations, including
fs.readFile,fs.writeFile,fs.readdir, andfs.unlink.Technical Analysis
The code attempts to prevent directory traversal by checking whether the resolved path starts with the configured base-directory string. A string-prefix comparison does not establish that the target is a descendant of the base directory.
For example, if the configured base directory is
/safe, the resolved path/safe-evil/secret.txtstarts with the string/safeand therefore passes the check, even though it is outside/safe.The check also does not canonicalize the target with
realpath, so symbolic links located inside the permitted directory may potentially redirect operations outside that directory.Attack Path
- The file-manager example is enabled with
basePathset to/safe. - An attacker or untrusted caller identifies or creates a sibling directory whose name shares the base prefix, such as
/safe-evil. - The caller supplies a path such as
../safe-evil/secret.txt. path.resolve('/safe', '../safe-evil/secret.txt')produces/safe-evil/secret.txt./safe-evil/secret.txt.startsWith(/safe) evaluates totrue.- The containment check accepts the path.
- Depending on the invoked tool, the code reads, writes, enumerates, or deletes the out-of-scope target.
A symbolic-link va ...[truncated 1390 chars]
- The file-manager example is enabled with
- Remediation
View remediation
Remediation Suggestions
Replace string-prefix validation with a path-relative containment check that enforces directory boundaries:
ts private resolvePath(filePath: string): string { const base = path.resolve(this.config.basePath || process.cwd()); const target = path.resolve(base, filePath); const relative = path.relative(base, target); if ( relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) ) { throw new Error('Access denied: Path outside base directory'); } return target; }Additional hardening should include:
- Canonicalize the base directory with
fs.realpath. - For existing targets, canonicalize the target and repeat the containment check using canonical paths.
- For new files, canonicalize the nearest existing parent directory before writing.
- Reject or safely handle symbolic links when strict sandboxing is required.
- Open files with protections such as
O_NOFOLLOWwhere supported and appropriate. - Add regression tests covering prefix-sharing siblings,
..traversal, absolute paths, mixed separators, and symlink escapes. - Run the Skill under a minimally privileged operating-system account so a containment failure cannot expose unrelated sensitive files.
- Canonicalize the base directory with
