Skip to content

Add sandboxDirectory support to FileSystemTools#46

Closed
kezhenxu94 wants to merge 2 commits into
spring-ai-community:mainfrom
kezhenxu94:feature/filesystem-tools-sandbox-directory
Closed

Add sandboxDirectory support to FileSystemTools#46
kezhenxu94 wants to merge 2 commits into
spring-ai-community:mainfrom
kezhenxu94:feature/filesystem-tools-sandbox-directory

Conversation

@kezhenxu94

Copy link
Copy Markdown
Contributor

Summary

  • Adds a sandboxDirectory option to FileSystemTools.Builder that restricts all read, write, and edit operations to paths within a configured directory
  • Path traversal via .. is blocked by normalizing the absolute path before checking
  • Symlink escapes are blocked by resolving real paths on existing path components; dangling symlinks (pointing to a non-existent target outside the sandbox) are also denied
  • No sandbox configured (default) = existing behavior unchanged, fully backward compatible

Usage

FileSystemTools tools = FileSystemTools.builder()
    .sandboxDirectory(Path.of("/workspace/project"))
    .build();
// or: .sandboxDirectory("/workspace/project")

Test plan

  • Allow read/write/edit of files inside the sandbox
  • Deny read/write/edit of files outside the sandbox
  • Deny path traversal via .. for all three operations
  • Deny symlink inside sandbox pointing to file outside
  • Deny symlink directory inside sandbox pointing to directory outside
  • Deny dangling symlink pointing outside sandbox on write
  • No restriction when sandbox is not configured (backward compat)
  • Builder accepts both Path and String forms
  • null String sandbox = no restriction
  • Access to nested subdirectories inside sandbox works

Closes #45

…ations

When a sandboxDirectory is configured via the builder, all read, write, and
edit operations are restricted to paths within that directory. Path traversal
via '..' and symlink escapes (including dangling symlinks) are both blocked.
Closes spring-ai-community#45
Copilot AI review requested due to automatic review settings May 12, 2026 02:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional sandbox directory constraint to FileSystemTools so read, write, and edit are restricted to a configured directory (default remains unrestricted for backward compatibility), with accompanying tests.

Changes:

  • Introduces sandboxDirectory configuration on FileSystemTools.Builder (String and Path overloads).
  • Adds validateSandboxAccess(...) checks to read, write, and edit.
  • Adds a new nested test suite covering sandbox allow/deny behavior including traversal and symlink cases.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
spring-ai-agent-utils/src/main/java/org/springaicommunity/agent/tools/FileSystemTools.java Adds sandbox directory support and enforces it in read/write/edit.
spring-ai-agent-utils/src/test/java/org/springaicommunity/agent/tools/FileSystemToolsTest.java Adds unit tests validating sandbox restrictions, traversal blocking, and symlink escape blocking.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +55 to +62
private String validateSandboxAccess(String filePath) {
if (this.sandboxDirectory == null) {
return null;
}
try {
Path sandbox = this.sandboxDirectory.toAbsolutePath().normalize();
Path target = Paths.get(filePath).toAbsolutePath().normalize();

Comment on lines +68 to +74
// Check 2: resolve symlinks in all existing path components.
// Walk up from target using NOFOLLOW_LINKS so dangling symlinks are detected.
Path realSandbox = Files.exists(sandbox) ? sandbox.toRealPath() : sandbox;
Path existing = target;
while (existing != null && !Files.exists(existing, LinkOption.NOFOLLOW_LINKS)) {
existing = existing.getParent();
}
Comment on lines +60 to +66
Path sandbox = this.sandboxDirectory.toAbsolutePath().normalize();
Path target = Paths.get(filePath).toAbsolutePath().normalize();

// Check 1: normalized path must be within sandbox (blocks .. traversal)
if (!target.startsWith(sandbox)) {
return "Error: Access denied. Path is outside the allowed sandbox directory: " + filePath;
}
Comment on lines +681 to +685
@Test
@DisplayName("Should deny symlink pointing outside sandbox")
@DisabledOnOs(OS.WINDOWS)
void shouldDenySymlinkPointingOutsideSandbox() throws IOException {
FileSystemTools sandboxedTools = FileSystemTools.builder().sandboxDirectory(sandboxDir).build();
- Reject '..' components in the raw absolute path to prevent symlink+'..' escapes
  (e.g. /sandbox/link/../outside normalizes to /sandbox/outside but the OS resolves
  'link' as a symlink first, landing outside the sandbox)
- Skip symlink real-path check when sandbox directory does not yet exist, so writes
  into a not-yet-created sandbox directory work correctly (checks 1+2 are sufficient)
- Catch RuntimeException (e.g. InvalidPathException) in validateSandboxAccess so
  invalid paths return a consistent error string instead of escaping as exceptions
- Add private Builder() constructor consistent with GlobTool/ListDirectoryTool convention
- Add regression tests for symlink+'..' bypass and non-existent sandbox directory
@tzolov

tzolov commented May 24, 2026

Copy link
Copy Markdown
Contributor

Thank you @kezhenxu94 ,
I've renamed the method from sandbox to allowed, added support for multiple allowed directories and updated the docs.
Rebased, extended, squashed and merged at ffdd432

@tzolov tzolov closed this May 24, 2026
@tzolov tzolov added this to the 0.8.0 milestone May 24, 2026
@kezhenxu94
kezhenxu94 deleted the feature/filesystem-tools-sandbox-directory branch May 25, 2026 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FileSystemTools: Add sandboxed directory support to restrict file operations

3 participants