Phase 4: System Tools
This phase provides the agent with controlled access to the host system: executing commands, reading/writing files, making HTTP requests, and browsing web pages
Phase 4: System Tools
Overview
This phase provides the agent with controlled access to the host system: executing commands, reading/writing files, making HTTP requests, and browsing web pages. All tools are gated by approval policies to prevent unauthorized operations. Optional Docker sandboxing provides an additional security layer.
What this enables:
Dependencies
Phase 1: Gateway & Channel Foundation
ToolRegistry for tool registrationPolicyEngine for permission enforcementExisting runtime infrastructure:
Tool interface from runtime/src/tools/types.tsToolRegistry from runtime/src/tools/registry.tsPolicyEngine from runtime/src/policy/engine.tsruntime/src/utils/lazy-import.tsIssue Dependency Graph
Implementation Order
- 1067 β Bash tool (M)
- Command execution with allow/deny lists
- 1068 β Filesystem tool (M)
- Read, write, list, stat, delete operations
- 1069 β HTTP tool (M)
- GET, POST, fetch with domain control
- 1073 β Approval policies (M)
- Per-tool, per-amount approval rules
- 1077 β Policy integration (S)
- Extend PolicyEngine with ToolPolicy
- 1072 β Browser tool (L)
- HTML extraction + optional Playwright
- 1076 β Docker sandbox (L)
- Optional sandboxed execution environment
Rationale: Core tools β approval system β policy integration β advanced tools β sandboxing. Build security layer before adding powerful tools.
Issue Details
4.1: Bash tool (command execution with allow/deny lists) (#1067)
Goal: Execute bash commands with security controls.
Files to create:
gateway/src/tools/bash/tool.ts β BashTool classgateway/src/tools/bash/config.ts β BashToolConfiggateway/src/tools/bash/security.ts β Command validationgateway/src/tools/bash/index.tsgateway/src/tools/bash/tool.test.tsFiles to modify:
gateway/src/gateway.ts β register BashTool by defaultgateway/src/tools/index.ts β export bash toolIntegration points:
child_process.execFile() (NOT exec() for shell injection safety)Tool interface from runtime/src/tools/types.ts- Allow list (only whitelisted commands)
- Deny list (blocked commands)
- Timeout enforcement (default 30s)
- Output truncation (max 10KB)
- Working directory restriction
Patterns to follow:
runtime/src/tools/agenc/tools.tsruntime/src/types/errors.tsKey interfaces:
interface BashToolConfig {
allowedCommands?: string[];
deniedCommands?: string[];
allowedDirectories?: string[];
timeoutMs?: number;
maxOutputBytes?: number;
}
class BashTool implements Tool {
readonly name = 'bash';
readonly description = 'Execute bash commands';
readonly inputSchema: JSONSchema;
// implementation
}
interface BashToolInput {
command: string;
args?: string[];
cwd?: string;
}
interface BashToolResult extends ToolResult {
exitCode?: number;
stdout?: string;
stderr?: string;
}Testing strategy:
command; rm -rf /)execFile for most testsEstimated scope: M (500-700 lines)
4.2: Filesystem tool (read, write, list, stat, delete) (#1068)
Goal: File system operations with path traversal prevention.
Files to create:
gateway/src/tools/filesystem/tool.ts β FileSystemTool classgateway/src/tools/filesystem/config.ts β FileSystemToolConfiggateway/src/tools/filesystem/security.ts β Path validationgateway/src/tools/filesystem/operations.ts β File operationsgateway/src/tools/filesystem/index.tsgateway/src/tools/filesystem/tool.test.tsFiles to modify:
gateway/src/gateway.ts β register FileSystemTool by defaultgateway/src/tools/index.ts β export filesystem toolIntegration points:
fs/promises APITool interface - Path traversal prevention (../ attacks)
- Allowed directories whitelist
- File size limits
- Read-only mode option
- Symlink resolution
Patterns to follow:
runtime/src/tools/types.tssdk/src/validation.tsKey interfaces:
interface FileSystemToolConfig {
allowedDirectories?: string[];
readOnly?: boolean;
maxFileSize?: number;
followSymlinks?: boolean;
}
class FileSystemTool implements Tool {
readonly name = 'filesystem';
readonly description = 'File system operations';
readonly inputSchema: JSONSchema;
// implementation
}
interface FileSystemOperation {
operation: 'read' | 'write' | 'list' | 'stat' | 'delete';
path: string;
content?: string;
encoding?: string;
}Testing strategy:
../../etc/passwd)fs/promises for testsEstimated scope: M (600-800 lines)
4.3: HTTP tool (GET, POST, fetch with domain control) (#1069)
Goal: HTTP requests with domain whitelisting.
Files to create:
gateway/src/tools/http/tool.ts β HttpTool classgateway/src/tools/http/config.ts β HttpToolConfiggateway/src/tools/http/security.ts β Domain validationgateway/src/tools/http/index.tsgateway/src/tools/http/tool.test.tsFiles to modify:
gateway/src/gateway.ts β register HttpTool by defaultgateway/src/tools/index.ts β export http toolIntegration points:
node-fetch or native fetch (Node 18+)Tool interface- Allowed domains list
- Blocked domains list (e.g., localhost, private IPs)
- Timeout enforcement
- Response size limits
- Header control
Patterns to follow:
runtime/src/tools/types.tsKey interfaces:
interface HttpToolConfig {
allowedDomains?: string[];
blockedDomains?: string[];
timeoutMs?: number;
maxResponseSize?: number;
allowedMethods?: string[];
}
class HttpTool implements Tool {
readonly name = 'http';
readonly description = 'HTTP requests';
readonly inputSchema: JSONSchema;
// implementation
}
interface HttpRequest {
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
url: string;
headers?: Recordstring, string>;
body?: string;
}
interface HttpResponse {
status: number;
headers: Recordstring, string>;
body: string;
}Testing strategy:
Estimated scope: M (400-600 lines)
4.4: Browser tool (HTML extraction + optional Playwright) (#1072)
Goal: Web page rendering and content extraction.
Files to create:
gateway/src/tools/browser/tool.ts β BrowserTool classgateway/src/tools/browser/config.ts β BrowserToolConfiggateway/src/tools/browser/extractor.ts β HTML content extractiongateway/src/tools/browser/playwright.ts β Playwright integration (optional)gateway/src/tools/browser/index.tsgateway/src/tools/browser/tool.test.tsFiles to modify:
gateway/src/gateway.ts β register BrowserTool if enabledgateway/src/tools/index.ts β export browser toolgateway/package.json β add playwright as optional dependencyIntegration points:
1. Simple: Fetch HTML + extract text (no JS execution)
2. Playwright: Full browser rendering (lazy-loaded)
ensureLazyModule() for PlaywrightTool interfacePatterns to follow:
runtime/src/utils/lazy-import.tsruntime/src/tools/types.tsKey interfaces:
interface BrowserToolConfig {
mode: 'simple' | 'playwright';
timeoutMs?: number;
maxContentLength?: number;
allowedDomains?: string[];
}
class BrowserTool implements Tool {
readonly name = 'browser';
readonly description = 'Browse web pages';
readonly inputSchema: JSONSchema;
// implementation
}
interface BrowseRequest {
url: string;
waitForSelector?: string;
screenshot?: boolean;
}
interface BrowseResult {
title: string;
content: string;
screenshot?: string;
}Testing strategy:
Estimated scope: L (700-1000 lines)
4.5: Execution sandboxing via Docker (#1076)
Goal: Optional Docker container for isolated tool execution.
Files to create:
gateway/src/tools/sandbox/docker.ts β DockerSandbox classgateway/src/tools/sandbox/config.ts β SandboxConfiggateway/src/tools/sandbox/image.ts β Docker image managementgateway/src/tools/sandbox/index.tsgateway/src/tools/sandbox/docker.test.tsdocker/sandbox/Dockerfile β Sandbox container imagedocker/sandbox/entrypoint.sh β Container entrypoint scriptFiles to modify:
gateway/src/tools/bash/tool.ts β use sandbox if configuredgateway/src/gateway.ts β initialize sandbox if enabledgateway/package.json β add dockerode as optional dependencyIntegration points:
dockerode library (lazy-loaded)Patterns to follow:
runtime/src/utils/lazy-import.tsKey interfaces:
interface SandboxConfig {
enabled: boolean;
image?: string;
networkMode?: 'none' | 'bridge';
memoryLimit?: string;
cpuLimit?: number;
volumeMounts?: Recordstring, string>;
}
class DockerSandbox {
initialize(): Promisevoid>;
execute(command: string, args: string[]): PromiseSandboxResult>;
cleanup(): Promisevoid>;
}
interface SandboxResult {
exitCode: number;
stdout: string;
stderr: string;
duration: number;
}Testing strategy:
Estimated scope: L (600-900 lines)
4.6: Approval policies (per-tool, per-amount rules) (#1073)
Goal: Define approval rules for tool execution.
Files to create:
gateway/src/approval/policy.ts β ApprovalPolicy classgateway/src/approval/rules.ts β ApprovalRule typesgateway/src/approval/gate.ts β Approval gate (hook handler)gateway/src/approval/index.tsgateway/src/approval/policy.test.tsFiles to modify:
gateway/src/hooks/builtin.ts β add approval gate hookgateway/src/gateway.ts β register approval policygateway/src/index.ts β export approval typesIntegration points:
tool:before lifecycle event- Auto-approve: Always allow (for safe tools)
- Auto-deny: Always block (for dangerous tools)
- Prompt: Ask user via chat channel
- Budget: Approve up to daily/monthly limit
/approve <request-id> or /deny <request-id>Patterns to follow:
runtime/src/policy/engine.tsgateway/src/hooks/types.tsKey interfaces:
interface ApprovalPolicy {
rules: ApprovalRule[];
evaluate(toolName: string, input: unknown): PromiseApprovalDecision>;
}
interface ApprovalRule {
toolName: string;
action: 'approve' | 'deny' | 'prompt' | 'budget';
condition?: (input: unknown) => boolean;
budgetLimit?: number;
budgetPeriod?: 'daily' | 'weekly' | 'monthly';
}
interface ApprovalDecision {
approved: boolean;
requiresPrompt: boolean;
requestId?: string;
message?: string;
}
interface ApprovalGate {
handle(event: HookEvent): Promisevoid>;
waitForApproval(requestId: string): Promiseboolean>;
}Testing strategy:
Estimated scope: M (500-700 lines)
4.7: Tool permission policy integration (extend PolicyEngine) (#1077)
Goal: Extend PolicyEngine to support tool permissions.
Files to create:
gateway/src/policy/tool-policy.ts β ToolPolicy classgateway/src/policy/tool-policy.test.tsFiles to modify:
runtime/src/policy/engine.ts β add ToolPolicy support (or keep in gateway)gateway/src/gateway.ts β integrate ToolPolicy into PolicyEnginegateway/src/policy/index.ts β export tool policyIntegration points:
runtime/src/policy/engine.ts- Tool enabled/disabled
- Per-tool rate limits
- Per-tool resource limits (CPU, memory, duration)
Patterns to follow:
runtime/src/policy/engine.tsKey interfaces:
interface ToolPolicy {
enabled: Mapstring, boolean>;
rateLimits: Mapstring, RateLimit>;
resourceLimits: Mapstring, ResourceLimit>;
check(toolName: string, action: string): PromisePolicyDecision>;
}
interface ResourceLimit {
maxDuration?: number;
maxMemory?: number;
maxCpu?: number;
}
interface PolicyDecision {
allowed: boolean;
reason?: string;
retryAfter?: number;
}Testing strategy:
Estimated scope: S (300-400 lines)
Integration Checklist
After completing all issues:
/approve and /deny commands workConfiguration Example
{
"tools": {
"bash": {
"enabled": true,
"allowedCommands": ["ls", "cat", "grep", "git"],
"deniedCommands": ["rm", "sudo"],
"allowedDirectories": ["/home/user/projects"],
"timeoutMs": 30000
},
"filesystem": {
"enabled": true,
"allowedDirectories": ["/home/user/projects", "/tmp"],
"readOnly": false,
"maxFileSize": 10485760
},
"http": {
"enabled": true,
"allowedDomains": ["api.github.com", "docs.solana.com"],
"blockedDomains": ["localhost", "127.0.0.1"],
"timeoutMs": 30000
},
"browser": {
"enabled": true,
"mode": "simple",
"allowedDomains": ["*"]
},
"sandbox": {
"enabled": false,
"image": "agenc/sandbox:latest",
"networkMode": "none",
"memoryLimit": "512m"
}
},
"approval": {
"rules": [
{
"toolName": "bash",
"action": "prompt"
},
{
"toolName": "filesystem",
"action": "budget",
"budgetLimit": 100,
"budgetPeriod": "daily"
},
{
"toolName": "http",
"action": "approve"
}
]
},
"policy": {
"rateLimits": {
"bash": { "maxCalls": 10, "windowMs": 60000 },
"filesystem": { "maxCalls": 50, "windowMs": 60000 }
}
}
}Security Considerations
Path Traversal Prevention
function validatePath(path: string, allowedDirs: string[]): boolean {
const resolved = resolve(path);
return allowedDirs.some(dir => resolved.startsWith(resolve(dir)));
}Domain Validation
function validateDomain(url: string, allowed: string[]): boolean {
const hostname = new URL(url).hostname;
return allowed.some(pattern =>
pattern === '*' || hostname === pattern || hostname.endsWith('.' + pattern)
);
}Command Injection Prevention
// NEVER use exec or spawn with shell: true
// ALWAYS use execFile with explicit args
execFile(command, args, { timeout: 30000 });