chore: Remove obsolete planning and documentation files.
This commit is contained in:
81
IDEAS.md
81
IDEAS.md
@@ -1,81 +0,0 @@
|
||||
# Command Ideas for AnchorCli
|
||||
|
||||
## Session & Help
|
||||
|
||||
### `/help`
|
||||
Show available commands, version info, and tool capabilities. Combines `/help`, `/version`, `/about`, and `/tools`.
|
||||
|
||||
### `/clear`
|
||||
Clear the terminal screen and optionally reset conversation with `/clear --reset`.
|
||||
|
||||
### `/history`
|
||||
Show the current chat history. Use `/history <n>` to show last N messages.
|
||||
|
||||
## Navigation
|
||||
|
||||
### `/cd [path]`
|
||||
Change directory. With no argument, shows current working directory (combines `/cwd`, `/pwd`, `/cd`).
|
||||
|
||||
### `/ls`
|
||||
List files in current directory (alias for ListDir tool).
|
||||
|
||||
## Configuration
|
||||
|
||||
### `/config`
|
||||
Show or modify settings. Subcommands:
|
||||
- `/config model <name>` - Change AI model
|
||||
- `/config endpoint <url>` - Change API endpoint
|
||||
- `/config debug <on|off>` - Toggle debug mode
|
||||
|
||||
## Conversation Management
|
||||
|
||||
### `/save [filename]`
|
||||
Save current conversation to a file (JSON or markdown format).
|
||||
|
||||
### `/load <filename>`
|
||||
Load a previous conversation from a file.
|
||||
|
||||
### `/export <filename>`
|
||||
Export chat history in a specific format (JSON, markdown, plain text).
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### `/undo`
|
||||
Undo the last file edit (requires edit history tracking).
|
||||
|
||||
### `/diff [file]`
|
||||
Show differences between current and original file state. With no argument, shows all pending changes.
|
||||
|
||||
### `/search <pattern>`
|
||||
Quick file/content search across the project.
|
||||
|
||||
### `/stats`
|
||||
Show session statistics (files edited, tokens used, commands run, estimated costs).
|
||||
|
||||
### `/macro <name> [commands...]`
|
||||
Create and execute multi-step command sequences.
|
||||
|
||||
### `/alias <name> <command>`
|
||||
Create custom command shortcuts.
|
||||
|
||||
## Safety & Integration
|
||||
|
||||
### `--dry-run` / Read-only Mode
|
||||
Run Anchor without mutating any files. Shows what *would* happen (edits, deletes, renames) without applying changes. Perfect for reviewing AI suggestions before committing.
|
||||
|
||||
### Git Integration
|
||||
Seamless version control integration:
|
||||
- Auto-create a branch per session (`anchor session --git-branch`)
|
||||
- Auto-commit after successful edits with descriptive messages
|
||||
- Show git diff before/after operations
|
||||
- Revert to pre-session state if something goes wrong
|
||||
|
||||
### Mutation Rate Limits
|
||||
Prevent runaway AI from trashing a project:
|
||||
- Configurable max file edits per conversation turn
|
||||
- Hard cap on delete/rename operations without confirmation
|
||||
- Cooldown period after N rapid mutations
|
||||
- Warning when approaching limits
|
||||
|
||||
### File Type Restrictions
|
||||
Config to block edits on sensitive patterns (`*.config`, `*.sql`, `*.production.*`, etc.). Requires explicit override flag.
|
||||
90
IMPROVEME.md
90
IMPROVEME.md
@@ -1,90 +0,0 @@
|
||||
# Improvements for AnchorCli
|
||||
|
||||
This document contains criticisms and suggestions for improving the AnchorCli project.
|
||||
|
||||
## Architecture
|
||||
|
||||
1. **Program.cs is too large (433 lines)** - Split into smaller classes: ChatSession, ReplLoop, ResponseStreamer
|
||||
2. **No dependency injection** - Use Microsoft.Extensions.DependencyInjection for testability
|
||||
3. **Static tool classes with global Log delegates** - Convert to instance classes with injected ILogger
|
||||
|
||||
## Testing
|
||||
|
||||
4. **No unit tests** - Add xUnit project, test HashlineEncoder/Validator, tools, and ContextCompactor
|
||||
5. **No integration tests** - Use Spectre.Console.Testing for TUI workflows
|
||||
6. **No CI/CD** - Add GitHub Actions for test runs on push/PR
|
||||
|
||||
## Documentation
|
||||
|
||||
7. **Missing XML docs** - Add summary docs to public APIs
|
||||
8. **Incomplete README** - Add contributing, development, troubleshooting sections
|
||||
9. **No CHANGELOG.md** - Track releases and changes
|
||||
|
||||
## Security & Safety
|
||||
|
||||
10. **Command execution unsandboxed** - Add allowlist/denylist, time limits, output size limits
|
||||
11. **No mutation rate limiting** - Track edits per turn, add configurable limits
|
||||
12. **API key in plain text** - Use OS keychain or env var, set restrictive file permissions
|
||||
|
||||
## Performance
|
||||
|
||||
13. **No file read caching** - Cache file content per-turn with invalidation on write
|
||||
14. **Regex not static** - Make compiled regexes static readonly
|
||||
|
||||
## User Experience
|
||||
|
||||
15. **No undo** - Store edit history, add /undo command
|
||||
16. **No session persistence** - Add /save and /load commands
|
||||
17. **Limited error recovery** - Better error messages, /debug mode
|
||||
|
||||
## Developer Experience
|
||||
|
||||
18. **No .editorconfig** - Add code style enforcement
|
||||
19. **No solution file** - Create AnchorCli.sln
|
||||
20. **Hardcoded model list** - Fetch from OpenRouter API dynamically
|
||||
21. **No version info** - Add <Version> to .csproj, display in /help
|
||||
|
||||
## Code Quality
|
||||
|
||||
22. **Inconsistent error handling** - Standardize on error strings, avoid empty catch blocks
|
||||
23. **Magic numbers** - Extract to named constants (150_000, 300, KeepRecentTurns=2)
|
||||
24. **Commented-out debug code** - Remove or use #if DEBUG
|
||||
25. **Weak hash algorithm** - Adler-8 XOR only has 256 values; consider 4-char hex
|
||||
|
||||
## Build & Dependencies
|
||||
|
||||
26. **No LICENSE file** - Add MIT LICENSE file
|
||||
|
||||
## Priority
|
||||
|
||||
### High
|
||||
- [ ] Add unit tests
|
||||
- [ ] Implement undo functionality
|
||||
- [ ] Add mutation rate limiting
|
||||
- [x] Refactor Program.cs
|
||||
- [x] Add LICENSE file
|
||||
|
||||
### Medium
|
||||
- [ ] Session persistence
|
||||
- [ ] XML documentation
|
||||
- [ ] Error handling consistency
|
||||
- [x] .editorconfig
|
||||
- [ ] Dynamic model list
|
||||
|
||||
### Low
|
||||
- [ ] CHANGELOG.md
|
||||
- [ ] CI/CD pipeline
|
||||
- [ ] Stronger hash algorithm
|
||||
- [ ] Code coverage reporting
|
||||
|
||||
## Quick Wins (<1 hour each)
|
||||
|
||||
- [x] Add <Version> to .csproj
|
||||
- [x] Create LICENSE file
|
||||
- [x] Add .editorconfig
|
||||
- [x] Remove commented code
|
||||
- [x] Extract magic numbers to constants
|
||||
- [x] Add XML docs to Hashline classes
|
||||
- [x] Make regexes static readonly
|
||||
|
||||
*Prioritize based on goals: safety, testability, or user experience.*
|
||||
293
PROVIDERS.md
293
PROVIDERS.md
@@ -1,293 +0,0 @@
|
||||
# Provider Support Plan
|
||||
|
||||
## Current Problems
|
||||
|
||||
1. **OpenRouter Hardcoded**: Endpoint, headers, and pricing API calls are hardcoded to OpenRouter
|
||||
2. **Config Ineffective**: SetupTui allows "custom endpoint" but Program.cs ignores it
|
||||
3. **Token Count**: Token usage tracking only works with OpenRouter response headers
|
||||
4. **Pricing Only for One Provider**: Models list shows pricing, but only when using OpenRouter
|
||||
|
||||
---
|
||||
|
||||
## Goals
|
||||
|
||||
1. Make the system **endpoint-agnostic**
|
||||
2. Support pricing/token tracking for **multiple providers**
|
||||
3. Keep **OpenRouter as the default** (familiar)
|
||||
4. Allow users to configure any OpenAI-compatible endpoint
|
||||
5. Show pricing/token info **only when available** for each provider
|
||||
|
||||
---
|
||||
|
||||
## Provider Categories
|
||||
|
||||
### Tier 1: Native Support (Built-in)
|
||||
- OpenRouter (default)
|
||||
- Ollama (local, no auth)
|
||||
- Groq (high-speed inference)
|
||||
- Anthropic (native or via API)
|
||||
- OpenAI (official api)
|
||||
|
||||
### Tier 2: Config-Based Support
|
||||
- Cerebras
|
||||
- DeepSeek
|
||||
- Any OpenAI-compatible endpoint that supports custom headers
|
||||
|
||||
### Tier 3: Manual Configuration Required
|
||||
- Self-hosted endpoints
|
||||
- Corporate proxies
|
||||
- Custom middleware layers
|
||||
|
||||
---
|
||||
|
||||
```csharp
|
||||
// Example: Provider interface
|
||||
class PricingProvider
|
||||
{
|
||||
// Get pricing info from provider's API
|
||||
async Task<List<ModelPricing>> GetModelsAsync(string apiKey);
|
||||
|
||||
// Get tokens from response
|
||||
async Task<TokenUsage> GetTokensFromResponseAsync(HttpResponseMessage response);
|
||||
|
||||
// Add provider-specific headers if needed
|
||||
void AddHeaders(HttpRequestMessage request, string apiKey);
|
||||
}
|
||||
```
|
||||
|
||||
**Supported Implementations:**
|
||||
- `OpenRouterProvider` (uses `/api/v1/models` + `x-total-tokens`)
|
||||
- `GroqProvider` (uses Groq's pricing API + response headers)
|
||||
- `OllamaProvider` (free tier, no pricing lookup, basic token counting)
|
||||
- `OpenAIProvider` (uses OpenAI's model list + token counting)
|
||||
- `GenericProvider` (fallback for any OpenAI-compatible endpoint)
|
||||
|
||||
**Configuration:**
|
||||
Store provider selection in `anchor.config.json`:
|
||||
```json
|
||||
{
|
||||
"apiKey": "your-key",
|
||||
"model": "qwen3.5-27b",
|
||||
"endpoint": "https://openrouter.ai/api/v1",
|
||||
"provider": "openrouter"
|
||||
}
|
||||
```
|
||||
|
||||
Auto-detect provider from endpoint URL if not specified.
|
||||
---
|
||||
|
||||
## Pricing System
|
||||
|
||||
### Current State
|
||||
- Uses OpenRouter's `/api/v1/models` endpoint
|
||||
- Displays pricing in a table during startup
|
||||
- Only works when using OpenRouter
|
||||
|
||||
### Improved Behavior
|
||||
|
||||
**When endpoint matches known provider:**
|
||||
1. Fetch pricing from that provider's API
|
||||
2. Display pricing in the startup table
|
||||
3. Show per-prompt costs in chat output
|
||||
|
||||
**When endpoint is generic/unsupported:**
|
||||
1. Skip API call (no pricing lookup)
|
||||
2. Display `---` or `$` placeholders
|
||||
3. Optional: Show "Pricing not available" note
|
||||
|
||||
**User Feedback:**
|
||||
- Show clear messaging: "Pricing data loaded from OpenRouter"
|
||||
- Show: "Pricing not available for this endpoint" (for unsupported)
|
||||
- Don't break chat functionality if pricing fails
|
||||
|
||||
### Pricing Data Format
|
||||
|
||||
Store in `ModelPricing` class:
|
||||
```csharp
|
||||
class ModelPricing
|
||||
{
|
||||
string ModelId;
|
||||
decimal InputPricePerMTokens;
|
||||
decimal OutputPricePerMTokens;
|
||||
double? CacheCreationPricePerMTokens; // if supported
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Token Tracking System
|
||||
|
||||
### Current State
|
||||
- Uses `x-total-tokens` from OpenRouter headers
|
||||
- Only works with OpenRouter responses
|
||||
|
||||
### Multi-Provider Strategy
|
||||
|
||||
**OpenRouter:**
|
||||
- Use `x-total-tokens` header
|
||||
- Use `x-response-timing` for latency tracking
|
||||
|
||||
**Groq:**
|
||||
- Use `x-groq-tokens` header
|
||||
- Use `x-groq-response-time` for latency
|
||||
|
||||
**OpenAI:**
|
||||
- Use `x-ai-response-tokens` header (if available)
|
||||
- Fall back to response body if needed
|
||||
|
||||
**Ollama:**
|
||||
- No official token counting
|
||||
- Use output length as proxy estimate
|
||||
- Optional: Show message token estimates
|
||||
|
||||
**Generic/Fallback:**
|
||||
- Parse `total_tokens` from response JSON
|
||||
- Fall back to character count estimates
|
||||
- Show placeholder when unavailable
|
||||
|
||||
### Integration Points
|
||||
|
||||
**During Chat Session:**
|
||||
1. After each response, extract tokens from response headers
|
||||
2. Store in `ChatSession.TokensUsed` object
|
||||
3. Display in status bar: `Tokens: 128/2048 • Cost: $0.002`
|
||||
|
||||
**At Session End:**
|
||||
1. Show summary: `Total tokens: 1,024 | Total cost: $0.015`
|
||||
2. Write to session log or history file
|
||||
|
||||
---
|
||||
|
||||
## Implementation Roadmap
|
||||
|
||||
### Phase 1: Conditional Pricing (Current Issues First)
|
||||
- [ ] Check if endpoint is OpenRouter before fetching pricing
|
||||
- [ ] Skip pricing API call for non-OpenRouter endpoints
|
||||
- [ ] Show placeholder message if pricing not available
|
||||
- [ ] **Time estimate:** 2 hours
|
||||
|
||||
### Phase 2: Provider Configuration
|
||||
- [ ] Add `provider` field to `AnchorConfig` model
|
||||
- [ ] Update `SetupTui` to ask "Which provider?" (openrouter, ollama, groq, etc.)
|
||||
- [ ] Auto-detect provider from endpoint URL (smart default)
|
||||
- [ ] Write provider to config file on setup
|
||||
- [ ] **Time estimate:** 3 hours
|
||||
|
||||
### Phase 3: Provider Abstraction
|
||||
- [ ] Create `IPricingProvider` interface
|
||||
- [ ] Move existing `PricingProvider` to `OpenRouterProvider`
|
||||
- [ ] Create `GenericPricingProvider` for fallback
|
||||
- [ ] Add provider factory: `ProviderFactory.Create(providerName)`
|
||||
- [ ] **Time estimate:** 5 hours
|
||||
|
||||
### Phase 4: Token Tracking Enhancement
|
||||
- [ ] Create `ITokenTracker` interface
|
||||
- [ ] Implement token extraction for multiple providers
|
||||
- [ ] Display token usage in status bar
|
||||
- [ ] Add per-prompt cost calculation
|
||||
- [ ] **Time estimate:** 6 hours
|
||||
|
||||
### Phase 5: Second Provider Implementation
|
||||
- [ ] Implement `GroqProvider` (similar to OpenRouter)
|
||||
- [ ] Test with Groq API
|
||||
- [ ] Update documentation
|
||||
- [ ] **Time estimate:** 4 hours
|
||||
|
||||
### Phase 6: Future-Proofing (Optional)
|
||||
- [ ] Add plugin system for custom providers
|
||||
- [ ] Allow users to define custom pricing rules
|
||||
- [ ] Support OpenRouter-compatible custom endpoints
|
||||
- [ ] **Time estimate:** 8+ hours
|
||||
|
||||
---
|
||||
|
||||
## User Configuration Guide
|
||||
|
||||
### Automatic Setup
|
||||
Run `/setup` in the chat or `anchor setup` in CLI:
|
||||
```
|
||||
Which provider are you using?
|
||||
1) OpenRouter (qwen models)
|
||||
2) Groq (qwen/gemma models)
|
||||
3) Ollama (local models)
|
||||
4) OpenAI (gpt models)
|
||||
5) Custom endpoint
|
||||
```
|
||||
|
||||
### Manual Configuration
|
||||
Edit `anchor.config.json` directly:
|
||||
```json
|
||||
{
|
||||
"apiKey": "your-api-key",
|
||||
"model": "qwen3.5-27b",
|
||||
"endpoint": "https://api.groq.com/openai/v1",
|
||||
"provider": "groq" // optional, auto-detected if missing
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
For custom setup:
|
||||
```
|
||||
ANCHOR_ENDPOINT=https://api.groq.com/openai/v1
|
||||
ANCHOR_PROVIDER=groq
|
||||
ANCHOR_API_KEY=...
|
||||
ANCHOR_MODEL=qwen3.5-27b
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### Tier 1 Providers (Full Support)
|
||||
**✓ OpenRouter**
|
||||
- Pricing: ✓ (native API)
|
||||
- Tokens: ✓ (response headers)
|
||||
- Cost tracking: ✓
|
||||
|
||||
**✓ Groq** (after Phase 4)
|
||||
- Pricing: ✓ (will add)
|
||||
- Tokens: ✓ (response headers)
|
||||
- Cost tracking: ✓
|
||||
|
||||
### Tier 2 Providers (Partial Support)
|
||||
**○ Ollama**
|
||||
- Pricing: ○ (free, no lookup needed)
|
||||
- Tokens: ○ (estimated from output)
|
||||
- Cost tracking: ○ (placeholder)
|
||||
|
||||
**○ OpenAI**
|
||||
- Pricing: ○ (manual pricing display)
|
||||
- Tokens: ○ (header extraction)
|
||||
- Cost tracking: ○ (config-based)
|
||||
|
||||
### Tier 3 Providers (Basic Support)
|
||||
**□ Custom Endpoints**
|
||||
- Pricing: □ (manual only)
|
||||
- Tokens: □ (fallback parsing)
|
||||
- Cost tracking: □ (user-defined)
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Pricing Database**: Maintain own pricing database (like OpenRouter's)
|
||||
2. **Cost Estimator**: Predict costs before sending message
|
||||
3. **Usage Alerts**: Warn user when approaching budget limits
|
||||
4. **Multi-Model Support**: Compare costs between different providers
|
||||
5. **Plugin System**: Allow community to add new providers
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- ✅ Users can choose from 3+ providers in setup
|
||||
- ✅ Pricing displays only for supported endpoints
|
||||
- ✅ Token tracking works for all Tier 1 providers
|
||||
- ✅ No breaking changes to existing OpenRouter users
|
||||
- ✅ Clear documentation on what each provider supports
|
||||
- ✅ Graceful degradation for unsupported features
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2025-12-23*
|
||||
|
||||
334
SANDBOX.md
334
SANDBOX.md
@@ -1,334 +0,0 @@
|
||||
# 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;
|
||||
|
||||
/// <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:
|
||||
|
||||
```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<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/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
|
||||
Reference in New Issue
Block a user