fix(sandbox): resolve symlinks before path containment check in sandboxed fs.readFile#13
Conversation
…eadFile Use fs.realpath() to resolve symlinks before checking whether the path falls within allowed roots, and pass the resolved path to readFile(). This prevents symlink-based path traversal (CWE-22) where a symlink inside an allowed root points to a file outside it.
|
Important Review skippedReview was skipped as selected files did not have any reviewable changes. 💤 Files selected but had no reviewable changes (1)
⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Review Summary by QodoResolve symlinks in sandboxed fs.readFile containment check
WalkthroughsDescription• Resolves symlinks before path containment check in sandboxed fs.readFile • Prevents CWE-22 path traversal via symlink bypass in security-critical sandbox • Uses fs.realpath() to canonicalize paths before allowlist validation • Passes resolved path to readFile() to ensure correct file access Diagramflowchart LR
A["filePath input"] --> B["path.resolve()"]
B --> C["realpath() resolve symlinks"]
C --> D["Check containment"]
D --> E{Allowed?}
E -->|Yes| F["readFile with resolved path"]
E -->|No| G["Throw error"]
File Changes1. src/cognition/emergent/SandboxedToolForge.ts
|
Code Review by Qodo
1. Realpath before containment
|
There was a problem hiding this comment.
💡 Codex Review
agentos/src/cognition/emergent/SandboxedToolForge.ts
Lines 477 to 479 in bb087d8
When fsReadRoots is configured with a symlinked directory (for example a deployment path like /app/current pointing at a release directory), resolvedPath now becomes the target's real path while this.fsReadRoots remains only path.resolved in the constructor. Legitimate reads under that configured root are therefore rejected because /releases/123/file no longer equals or starts with /app/current/; the roots need to be realpathed as well, or the containment check compares different path forms.
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Summary
The sandboxed
fs.readFileimplementation inSandboxedToolForgeusespath.resolve()to canonicalize file paths before checking whether they fall within the configuredfsReadRoots. However,path.resolve()only normalizes lexical components (.,..) — it does not resolve symbolic links. An attacker who can place or leverage an existing symlink inside an allowed root can read arbitrary files on the host filesystem, bypassing the sandbox containment.This is a path traversal vulnerability (CWE-22) in a security-critical code path:
SandboxedToolForgeis explicitly designed to run untrusted, AI-generated code in a restricted environment.Fix
The fix adds
realpath()fromnode:fs/promisesto resolve symlinks to their physical path before thestartsWithcontainment check:Before (vulnerable):
After (fixed):
This ensures the containment check operates on the true filesystem path. If the symlink target is outside the allowed roots, the read is correctly blocked.
Proof of Concept
Given a deployment where
fsReadRootsis set to["/app/data"]:The symlink must pre-exist on the filesystem within an allowed root. In containerized deployments this can occur via volume mounts, prior code execution, or build artifacts.
Checklist
Related
N/A — no existing issue for this.
Security Analysis
The
SandboxedToolForgeclass runs agent-generated JavaScript in a sandboxedvmcontext and selectively exposesfs.readFileas an allowlisted API. ThefsReadRootsmechanism is the sole access control preventing reads outside designated directories. By using onlypath.resolve()(lexical normalization), the original code was vulnerable to symlink-based traversal — a well-known bypass for path containment checks that rely on string prefix matching without resolving the underlying filesystem links.Before submitting, we verified that no other layer mitigates this: the
CodeSandboxexecutor does not perform its own path checks onfs.readFilecalls, and Node.jsvmcontexts do not restrict filesystem access. ThefsReadRootscheck inSandboxedToolForgeis the only barrier, and the symlink bypass is real.The precondition — a symlink existing within an allowed root — is plausible in production: container images may ship development symlinks, volume mounts can introduce them, and prior sandbox executions (if any created files) could establish them.
Submitted by Sebastion — autonomous open-source security research from Foundation Machines. Free for public repos via the Sebastion AI GitHub App.