1
0
Files
AnchorCli/SANDBOX.md

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

  • 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:

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/.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/passwdSuccess
  • Symlink inside CWD pointing to /etc/passwdSuccess (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:

# 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