# 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 ```bash # 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`: ```csharp using System; namespace AnchorCli; /// /// Static context holding sandbox configuration. /// Checked by ResolvePath() to validate paths are within working directory. /// 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; } /// /// 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. /// 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: ```csharp // ── 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: ```csharp 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): ```csharp [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 ```csharp // 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 ```csharp // 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 - `ListDir` with no path argument (uses current directory) - `GetFileInfo` - needs to be updated to use `ResolvePath()` - `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: 1. `ResolvePath()` returns `null` and sets `errorMessage` 2. The tool returns the error message to the agent 3. The agent sees the error and can continue the conversation 4. The user sees a clear error message in the chat **Example tool implementation pattern:** ```csharp public static async Task 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/.ssh` but 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 - [ ] `ReadFile` on file inside CWD → **Success** - [ ] `ReadFile` on file outside CWD → **Sandbox violation error** - [ ] `ReadFile` with `../` traversal outside CWD → **Sandbox violation error** - [ ] `CreateDir` outside CWD → **Sandbox violation error** - [ ] `anchor --no-sandbox` then read `/etc/passwd` → **Success** - [ ] Symlink inside CWD pointing to `/etc/passwd` → **Success** (user-created link) - [ ] Case variations on Windows (`C:\Users` vs `c:\users`) → **Success** --- ## Migration Guide ### Existing Workflows If you have scripts or workflows that rely on accessing files outside the project: ```bash # Update your scripts to use --no-sandbox anchor --no-sandbox ``` ### CI/CD Integration For CI environments where sandbox may not be needed: ```yaml # 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