9.9 KiB
Sandbox Implementation Plan for AnchorCli
Overview
By default, all file and directory operations are restricted to the current working directory (CWD).
Users can bypass this restriction with the --no-sandbox flag.
Usage
# Default: sandbox enabled (operations limited to CWD)
anchor
# Disable sandbox (allow operations anywhere)
anchor --no-sandbox
Architecture
The implementation leverages the existing ResolvePath() methods in FileTools and DirTools.
Since tools are static classes without dependency injection, we use a static SandboxContext class.
Implementation Steps
Step 1: Create SandboxContext.cs
Create a new file Core/SandboxContext.cs:
using System;
namespace AnchorCli;
/// <summary>
/// Static context holding sandbox configuration.
/// Checked by ResolvePath() to validate paths are within working directory.
/// </summary>
internal static class SandboxContext
{
private static string? _workingDirectory;
private static bool _enabled = true;
public static bool Enabled
{
get => _enabled;
set => _enabled = value;
}
public static string WorkingDirectory
{
get => _workingDirectory ?? Environment.CurrentDirectory;
set => _workingDirectory = value;
}
/// <summary>
/// Validates that a resolved path is within the working directory (if sandbox is enabled).
/// Returns the resolved path if valid, or null if outside sandbox (no exception thrown).
/// When null is returned, the calling tool should return an error message to the agent.
/// </summary>
public static string? ValidatePath(string resolvedPath)
{
if (!_enabled)
return resolvedPath;
var workDir = WorkingDirectory;
// Normalize paths for comparison
var normalizedPath = Path.GetFullPath(resolvedPath).TrimEnd(Path.DirectorySeparatorChar);
var normalizedWorkDir = Path.GetFullPath(workDir).TrimEnd(Path.DirectorySeparatorChar);
// Check if path starts with working directory
if (!normalizedPath.StartsWith(normalizedWorkDir, StringComparison.OrdinalIgnoreCase))
{
// Return null to signal violation - caller handles error messaging
return null;
}
return resolvedPath;
}
public static void Initialize(bool sandboxEnabled)
{
_enabled = sandboxEnabled;
_workingDirectory = Environment.CurrentDirectory;
}
}
Step 2: Modify Program.cs
Add argument parsing and initialize the sandbox context:
After line 15 (after the setup subcommand check), add:
// ── Parse sandbox flag ──────────────────────────────────────────────────
bool sandboxEnabled = !args.Contains("--no-sandbox");
SandboxContext.Initialize(sandboxEnabled);
if (!sandboxEnabled)
{
AnsiConsole.MarkupLine("[dim grey]Sandbox disabled (--no-sandbox)[/]");
}
Step 3: Update FileTools.ResolvePath()
Replace lines 322-323 with:
internal static string? ResolvePath(string path, out string? errorMessage)
{
errorMessage = null;
var resolved = Path.IsPathRooted(path)
? path
: Path.GetFullPath(path, Environment.CurrentDirectory);
var validated = SandboxContext.ValidatePath(resolved);
if (validated == null)
{
errorMessage = $"Sandbox violation: Path '{path}' is outside working directory '{SandboxContext.WorkingDirectory}'. Use --no-sandbox to disable restrictions.";
return null;
}
return validated;
}
Step 4: Update DirTools.ResolvePath()
Replace lines 84-85 with:
internal static string? ResolvePath(string path, out string? errorMessage)
{
errorMessage = null;
var resolved = Path.IsPathRooted(path)
? path
: Path.GetFullPath(path, Environment.CurrentDirectory);
var validated = SandboxContext.ValidatePath(resolved);
if (validated == null)
{
errorMessage = $"Sandbox violation: Path '{path}' is outside working directory '{SandboxContext.WorkingDirectory}'. Use --no-sandbox to disable restrictions.";
return null;
}
return validated;
}
---
### Step 5: Update Tool Descriptions (Optional but Recommended)
Update the `[Description]` attributes to mention sandbox behavior:
**FileTools.cs - ReadFile** (line 23):
```csharp
[Description("Read a file. Max 200 lines per call. Returns lines with line:hash| anchors. Sandbox: restricted to working directory unless --no-sandbox is used. IMPORTANT: Call GrepFile first...")]
DirTools.cs - CreateDir (line 63):
[Description("Create a new directory. Creates parent directories if they don't exist. Sandbox: restricted to working directory unless --no-sandbox is used. Returns OK on success...")]
Repeat for other tools as needed.
How Tools Handle Sandbox Violations
Each tool that uses ResolvePath() must check for null return and handle it gracefully:
FileTools Pattern
// Before (old code):
var resolvedPath = ResolvePath(path);
var content = File.ReadAllText(resolvedPath);
// After (new code):
var resolvedPath = ResolvePath(path, out var errorMessage);
if (resolvedPath == null)
return $"ERROR: {errorMessage}";
var content = File.ReadAllText(resolvedPath);
DirTools Pattern
// Before (old code):
var resolvedPath = ResolvePath(path);
Directory.CreateDirectory(resolvedPath);
// After (new code):
var resolvedPath = ResolvePath(path, out var errorMessage);
if (resolvedPath == null)
return $"ERROR: {errorMessage}";
Directory.CreateDirectory(resolvedPath);
return "OK";
EditTools
No changes needed - it already calls FileTools.ResolvePath(), so the sandbox check happens there.
Tools That Don't Use ResolvePath
ListDirwith no path argument (uses current directory)GetFileInfo- needs to be updated to useResolvePath()FindFiles- needs to be updated to validate the search path
Error Handling - No Crashes
When a sandbox violation occurs, the program does not crash. Instead:
ResolvePath()returnsnulland setserrorMessage- The tool returns the error message to the agent
- The agent sees the error and can continue the conversation
- The user sees a clear error message in the chat
Example tool implementation pattern:
public static async Task<string> ReadFile(string path, int startLine, int endLine)
{
var resolvedPath = ResolvePath(path, out var errorMessage);
if (resolvedPath == null)
return $"ERROR: {errorMessage}"; // Return error, don't throw
// ... rest of the tool logic
}
What the agent sees:
Tool result: ERROR: Sandbox violation: Path '/home/tomi/.ssh' is outside working directory '/home/tomi/dev/anchor'. Use --no-sandbox to disable restrictions.
What the user sees in chat:
The agent tried to read
/home/tomi/.sshbut was blocked by the sandbox. The agent can now adjust its approach or ask you to run with--no-sandbox.
Edge Cases Handled
| Case | Behavior |
|---|---|
| Symlinks inside CWD pointing outside | Follows symlink (user-created link = intentional) |
Path traversal (../..) |
Blocked if result is outside CWD |
| Absolute paths | Validated against CWD |
| Network paths | Blocked (not under CWD) |
| Case sensitivity | Uses OrdinalIgnoreCase for cross-platform compatibility |
Security Notes
⚠️ The sandbox is a safety feature, not a security boundary.
- It prevents accidental modifications to system files
- It does not protect against malicious intent
CommandTool.ExecuteCommand()can still run arbitrary shell commands- A determined user can always use
--no-sandbox
For true isolation, run anchor in a container or VM.
Testing Checklist
ReadFileon file inside CWD → SuccessReadFileon file outside CWD → Sandbox violation errorReadFilewith../traversal outside CWD → Sandbox violation errorCreateDiroutside CWD → Sandbox violation erroranchor --no-sandboxthen read/etc/passwd→ Success- Symlink inside CWD pointing to
/etc/passwd→ Success (user-created link) - Case variations on Windows (
C:\Usersvsc:\users) → Success
Migration Guide
Existing Workflows
If you have scripts or workflows that rely on accessing files outside the project:
# Update your scripts to use --no-sandbox
anchor --no-sandbox
CI/CD Integration
For CI environments where sandbox may not be needed:
# GitHub Actions example
- name: Run anchor
run: anchor --no-sandbox
Files Modified
| File | Changes |
|---|---|
Core/SandboxContext.cs |
New file - Static sandbox state and validation |
Program.cs |
Add --no-sandbox parsing, call SandboxContext.Initialize() |
Tools/FileTools.cs |
Update ResolvePath() signature to return string? with out errorMessage; update all tool methods to check for null |
Tools/DirTools.cs |
Update ResolvePath() signature to return string? with out errorMessage; update all tool methods to check for null |
Tools/EditTools.cs |
No changes (uses FileTools.ResolvePath(), sandbox check happens there) |
Tools/CommandTool.cs |
Not sandboxed - shell commands can access any path (documented limitation) |
Future Enhancements
- Allowlist: Let users specify additional safe directories via config
- Per-tool sandbox: Some tools (e.g.,
GrepRecursive) could have different rules - Audit mode: Log all file operations for review
- Interactive prompt: Ask for confirmation before violating sandbox instead of hard fail