refactor: remove sandbox system and simplify agent architecture

Remove the entire sandbox security system including:
- All sandbox-related Rust code and dependencies (gaol crate)
- Sandbox command handlers and platform-specific implementations
- Comprehensive test suite for sandbox functionality
- Agent sandbox settings UI components

Simplify agent configuration by removing sandbox and permission fields:
- Remove sandbox_enabled, enable_file_read, enable_file_write, enable_network from agent configs
- Update all CC agents to use simplified configuration format
- Remove sandbox references from documentation and UI
This commit is contained in:
Vivek R
2025-07-02 19:17:38 +05:30
parent 124fe1544f
commit 2dfdf31b83
47 changed files with 115 additions and 7774 deletions

View File

@@ -1,122 +0,0 @@
import React from "react";
import { Shield, FileText, Upload, Network, AlertTriangle } from "lucide-react";
import { Card } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Badge } from "@/components/ui/badge";
import { type Agent } from "@/lib/api";
import { cn } from "@/lib/utils";
interface AgentSandboxSettingsProps {
agent: Agent;
onUpdate: (updates: Partial<Agent>) => void;
className?: string;
}
/**
* Component for managing per-agent sandbox permissions
* Provides simple toggles for sandbox enable/disable and file/network permissions
*/
export const AgentSandboxSettings: React.FC<AgentSandboxSettingsProps> = ({
agent,
onUpdate,
className
}) => {
const handleToggle = (field: keyof Agent, value: boolean) => {
onUpdate({ [field]: value });
};
return (
<Card className={cn("p-4 space-y-4", className)}>
<div className="flex items-center gap-2">
<Shield className="h-5 w-5 text-amber-500" />
<h4 className="font-semibold">Sandbox Permissions</h4>
{!agent.sandbox_enabled && (
<Badge variant="secondary" className="text-xs">
Disabled
</Badge>
)}
</div>
<div className="space-y-3">
{/* Master sandbox toggle */}
<div className="flex items-center justify-between p-3 rounded-lg border bg-muted/30">
<div className="space-y-1">
<Label className="text-sm font-medium">Enable Sandbox</Label>
<p className="text-xs text-muted-foreground">
Run this agent in a secure sandbox environment
</p>
</div>
<Switch
checked={agent.sandbox_enabled}
onCheckedChange={(checked) => handleToggle('sandbox_enabled', checked)}
/>
</div>
{/* Permission toggles - only visible when sandbox is enabled */}
{agent.sandbox_enabled && (
<div className="space-y-3 pl-4 border-l-2 border-amber-200">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<FileText className="h-4 w-4 text-blue-500" />
<div>
<Label className="text-sm font-medium">File Read Access</Label>
<p className="text-xs text-muted-foreground">
Allow reading files and directories
</p>
</div>
</div>
<Switch
checked={agent.enable_file_read}
onCheckedChange={(checked) => handleToggle('enable_file_read', checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Upload className="h-4 w-4 text-green-500" />
<div>
<Label className="text-sm font-medium">File Write Access</Label>
<p className="text-xs text-muted-foreground">
Allow creating and modifying files
</p>
</div>
</div>
<Switch
checked={agent.enable_file_write}
onCheckedChange={(checked) => handleToggle('enable_file_write', checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Network className="h-4 w-4 text-purple-500" />
<div>
<Label className="text-sm font-medium">Network Access</Label>
<p className="text-xs text-muted-foreground">
Allow outbound network connections
</p>
</div>
</div>
<Switch
checked={agent.enable_network}
onCheckedChange={(checked) => handleToggle('enable_network', checked)}
/>
</div>
</div>
)}
{/* Warning when sandbox is disabled */}
{!agent.sandbox_enabled && (
<div className="flex items-start gap-2 p-3 rounded-lg bg-amber-50 border border-amber-200 text-amber-800 dark:bg-amber-950/50 dark:border-amber-800 dark:text-amber-200">
<AlertTriangle className="h-4 w-4 mt-0.5 flex-shrink-0" />
<div className="text-xs">
<p className="font-medium">Sandbox Disabled</p>
<p>This agent will run with full system access. Use with caution.</p>
</div>
</div>
)}
</div>
</Card>
);
};

View File

@@ -9,7 +9,6 @@ import { api, type Agent } from "@/lib/api";
import { cn } from "@/lib/utils";
import MDEditor from "@uiw/react-md-editor";
import { type AgentIconName } from "./CCAgents";
import { AgentSandboxSettings } from "./AgentSandboxSettings";
import { IconPicker, ICON_MAP } from "./IconPicker";
interface CreateAgentProps {
@@ -48,10 +47,6 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
const [systemPrompt, setSystemPrompt] = useState(agent?.system_prompt || "");
const [defaultTask, setDefaultTask] = useState(agent?.default_task || "");
const [model, setModel] = useState(agent?.model || "sonnet");
const [sandboxEnabled, setSandboxEnabled] = useState(agent?.sandbox_enabled ?? true);
const [enableFileRead, setEnableFileRead] = useState(agent?.enable_file_read ?? true);
const [enableFileWrite, setEnableFileWrite] = useState(agent?.enable_file_write ?? true);
const [enableNetwork, setEnableNetwork] = useState(agent?.enable_network ?? false);
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const [toast, setToast] = useState<{ message: string; type: "success" | "error" } | null>(null);
@@ -81,11 +76,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
selectedIcon,
systemPrompt,
defaultTask || undefined,
model,
sandboxEnabled,
enableFileRead,
enableFileWrite,
enableNetwork
model
);
} else {
await api.createAgent(
@@ -93,11 +84,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
selectedIcon,
systemPrompt,
defaultTask || undefined,
model,
sandboxEnabled,
enableFileRead,
enableFileWrite,
enableNetwork
model
);
}
@@ -119,11 +106,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
selectedIcon !== (agent?.icon || "bot") ||
systemPrompt !== (agent?.system_prompt || "") ||
defaultTask !== (agent?.default_task || "") ||
model !== (agent?.model || "sonnet") ||
sandboxEnabled !== (agent?.sandbox_enabled ?? true) ||
enableFileRead !== (agent?.enable_file_read ?? true) ||
enableFileWrite !== (agent?.enable_file_write ?? true) ||
enableNetwork !== (agent?.enable_network ?? false)) &&
model !== (agent?.model || "sonnet")) &&
!confirm("You have unsaved changes. Are you sure you want to leave?")) {
return;
}
@@ -309,29 +292,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
</p>
</div>
{/* Sandbox Settings */}
<AgentSandboxSettings
agent={{
id: agent?.id,
name,
icon: selectedIcon,
system_prompt: systemPrompt,
default_task: defaultTask || undefined,
model,
sandbox_enabled: sandboxEnabled,
enable_file_read: enableFileRead,
enable_file_write: enableFileWrite,
enable_network: enableNetwork,
created_at: agent?.created_at || "",
updated_at: agent?.updated_at || ""
}}
onUpdate={(updates) => {
if ('sandbox_enabled' in updates) setSandboxEnabled(updates.sandbox_enabled!);
if ('enable_file_read' in updates) setEnableFileRead(updates.enable_file_read!);
if ('enable_file_write' in updates) setEnableFileWrite(updates.enable_file_write!);
if ('enable_network' in updates) setEnableNetwork(updates.enable_network!);
}}
/>
{/* System Prompt Editor */}
<div className="space-y-2">
@@ -377,4 +338,4 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
/>
</div>
);
};
};

View File

@@ -314,9 +314,6 @@ export const GitHubAgentBrowser: React.FC<GitHubAgentBrowserProps> = ({
</h3>
<div className="flex items-center gap-2 mt-1">
<Badge variant="outline">{selectedAgent.data.agent.model}</Badge>
{selectedAgent.data.agent.sandbox_enabled && (
<Badge variant="secondary">Sandbox</Badge>
)}
</div>
</div>
</div>
@@ -341,21 +338,7 @@ export const GitHubAgentBrowser: React.FC<GitHubAgentBrowserProps> = ({
</div>
)}
{/* Permissions */}
<div>
<h4 className="text-sm font-medium mb-2">Permissions</h4>
<div className="flex flex-wrap gap-2">
<Badge variant={selectedAgent.data.agent.enable_file_read ? "default" : "secondary"}>
File Read: {selectedAgent.data.agent.enable_file_read ? "Yes" : "No"}
</Badge>
<Badge variant={selectedAgent.data.agent.enable_file_write ? "default" : "secondary"}>
File Write: {selectedAgent.data.agent.enable_file_write ? "Yes" : "No"}
</Badge>
<Badge variant={selectedAgent.data.agent.enable_network ? "default" : "secondary"}>
Network: {selectedAgent.data.agent.enable_network ? "Yes" : "No"}
</Badge>
</div>
</div>
{/* Metadata */}
<div className="text-xs text-muted-foreground">

View File

@@ -580,4 +580,4 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
</Dialog>
</div>
);
};
};

View File

@@ -106,83 +106,6 @@ export interface ClaudeInstallation {
source: string;
}
// Sandbox API types
export interface SandboxProfile {
id?: number;
name: string;
description?: string;
is_active: boolean;
is_default: boolean;
created_at: string;
updated_at: string;
}
export interface SandboxRule {
id?: number;
profile_id: number;
operation_type: string;
pattern_type: string;
pattern_value: string;
enabled: boolean;
platform_support?: string;
created_at: string;
}
export interface PlatformCapabilities {
os: string;
sandboxing_supported: boolean;
operations: OperationSupport[];
notes: string[];
}
export interface OperationSupport {
operation: string;
support_level: string;
description: string;
}
// Sandbox violation types
export interface SandboxViolation {
id?: number;
profile_id?: number;
agent_id?: number;
agent_run_id?: number;
operation_type: string;
pattern_value?: string;
process_name?: string;
pid?: number;
denied_at: string;
}
export interface SandboxViolationStats {
total: number;
recent_24h: number;
by_operation: Array<{
operation: string;
count: number;
}>;
}
// Import/Export types
export interface SandboxProfileExport {
version: number;
exported_at: string;
platform: string;
profiles: SandboxProfileWithRules[];
}
export interface SandboxProfileWithRules {
profile: SandboxProfile;
rules: SandboxRule[];
}
export interface ImportResult {
profile_name: string;
imported: boolean;
reason?: string;
new_name?: string;
}
// Agent API types
export interface Agent {
id?: number;
@@ -191,10 +114,6 @@ export interface Agent {
system_prompt: string;
default_task?: string;
model: string;
sandbox_enabled: boolean;
enable_file_read: boolean;
enable_file_write: boolean;
enable_network: boolean;
created_at: string;
updated_at: string;
}
@@ -208,10 +127,6 @@ export interface AgentExport {
system_prompt: string;
default_task?: string;
model: string;
sandbox_enabled: boolean;
enable_file_read: boolean;
enable_file_write: boolean;
enable_network: boolean;
};
}
@@ -718,10 +633,6 @@ export const api = {
* @param system_prompt - The system prompt for the agent
* @param default_task - Optional default task
* @param model - Optional model (defaults to 'sonnet')
* @param sandbox_enabled - Optional sandbox enable flag
* @param enable_file_read - Optional file read permission
* @param enable_file_write - Optional file write permission
* @param enable_network - Optional network permission
* @returns Promise resolving to the created agent
*/
async createAgent(
@@ -729,11 +640,7 @@ export const api = {
icon: string,
system_prompt: string,
default_task?: string,
model?: string,
sandbox_enabled?: boolean,
enable_file_read?: boolean,
enable_file_write?: boolean,
enable_network?: boolean
model?: string
): Promise<Agent> {
try {
return await invoke<Agent>('create_agent', {
@@ -741,11 +648,7 @@ export const api = {
icon,
systemPrompt: system_prompt,
defaultTask: default_task,
model,
sandboxEnabled: sandbox_enabled,
enableFileRead: enable_file_read,
enableFileWrite: enable_file_write,
enableNetwork: enable_network
model
});
} catch (error) {
console.error("Failed to create agent:", error);
@@ -761,10 +664,6 @@ export const api = {
* @param system_prompt - The updated system prompt
* @param default_task - Optional default task
* @param model - Optional model
* @param sandbox_enabled - Optional sandbox enable flag
* @param enable_file_read - Optional file read permission
* @param enable_file_write - Optional file write permission
* @param enable_network - Optional network permission
* @returns Promise resolving to the updated agent
*/
async updateAgent(
@@ -773,11 +672,7 @@ export const api = {
icon: string,
system_prompt: string,
default_task?: string,
model?: string,
sandbox_enabled?: boolean,
enable_file_read?: boolean,
enable_file_write?: boolean,
enable_network?: boolean
model?: string
): Promise<Agent> {
try {
return await invoke<Agent>('update_agent', {
@@ -786,11 +681,7 @@ export const api = {
icon,
systemPrompt: system_prompt,
defaultTask: default_task,
model,
sandboxEnabled: sandbox_enabled,
enableFileRead: enable_file_read,
enableFileWrite: enable_file_write,
enableNetwork: enable_network
model
});
} catch (error) {
console.error("Failed to update agent:", error);
@@ -1092,337 +983,6 @@ export const api = {
return invoke("search_files", { basePath, query });
},
// Sandbox API methods
/**
* Lists all sandbox profiles
* @returns Promise resolving to an array of sandbox profiles
*/
async listSandboxProfiles(): Promise<SandboxProfile[]> {
try {
return await invoke<SandboxProfile[]>('list_sandbox_profiles');
} catch (error) {
console.error("Failed to list sandbox profiles:", error);
throw error;
}
},
/**
* Creates a new sandbox profile
* @param name - The profile name
* @param description - Optional description
* @returns Promise resolving to the created profile
*/
async createSandboxProfile(name: string, description?: string): Promise<SandboxProfile> {
try {
return await invoke<SandboxProfile>('create_sandbox_profile', { name, description });
} catch (error) {
console.error("Failed to create sandbox profile:", error);
throw error;
}
},
/**
* Updates a sandbox profile
* @param id - The profile ID
* @param name - The updated name
* @param description - Optional description
* @param is_active - Whether the profile is active
* @param is_default - Whether the profile is the default
* @returns Promise resolving to the updated profile
*/
async updateSandboxProfile(
id: number,
name: string,
description: string | undefined,
is_active: boolean,
is_default: boolean
): Promise<SandboxProfile> {
try {
return await invoke<SandboxProfile>('update_sandbox_profile', {
id,
name,
description,
is_active,
is_default
});
} catch (error) {
console.error("Failed to update sandbox profile:", error);
throw error;
}
},
/**
* Deletes a sandbox profile
* @param id - The profile ID to delete
* @returns Promise resolving when the profile is deleted
*/
async deleteSandboxProfile(id: number): Promise<void> {
try {
return await invoke('delete_sandbox_profile', { id });
} catch (error) {
console.error("Failed to delete sandbox profile:", error);
throw error;
}
},
/**
* Gets a single sandbox profile by ID
* @param id - The profile ID
* @returns Promise resolving to the profile
*/
async getSandboxProfile(id: number): Promise<SandboxProfile> {
try {
return await invoke<SandboxProfile>('get_sandbox_profile', { id });
} catch (error) {
console.error("Failed to get sandbox profile:", error);
throw error;
}
},
/**
* Lists rules for a sandbox profile
* @param profileId - The profile ID
* @returns Promise resolving to an array of rules
*/
async listSandboxRules(profileId: number): Promise<SandboxRule[]> {
try {
return await invoke<SandboxRule[]>('list_sandbox_rules', { profile_id: profileId });
} catch (error) {
console.error("Failed to list sandbox rules:", error);
throw error;
}
},
/**
* Creates a new sandbox rule
* @param profileId - The profile ID
* @param operation_type - The operation type
* @param pattern_type - The pattern type
* @param pattern_value - The pattern value
* @param enabled - Whether the rule is enabled
* @param platform_support - Optional platform support JSON
* @returns Promise resolving to the created rule
*/
async createSandboxRule(
profileId: number,
operation_type: string,
pattern_type: string,
pattern_value: string,
enabled: boolean,
platform_support?: string
): Promise<SandboxRule> {
try {
return await invoke<SandboxRule>('create_sandbox_rule', {
profile_id: profileId,
operation_type,
pattern_type,
pattern_value,
enabled,
platform_support
});
} catch (error) {
console.error("Failed to create sandbox rule:", error);
throw error;
}
},
/**
* Updates a sandbox rule
* @param id - The rule ID
* @param operation_type - The operation type
* @param pattern_type - The pattern type
* @param pattern_value - The pattern value
* @param enabled - Whether the rule is enabled
* @param platform_support - Optional platform support JSON
* @returns Promise resolving to the updated rule
*/
async updateSandboxRule(
id: number,
operation_type: string,
pattern_type: string,
pattern_value: string,
enabled: boolean,
platform_support?: string
): Promise<SandboxRule> {
try {
return await invoke<SandboxRule>('update_sandbox_rule', {
id,
operation_type,
pattern_type,
pattern_value,
enabled,
platform_support
});
} catch (error) {
console.error("Failed to update sandbox rule:", error);
throw error;
}
},
/**
* Deletes a sandbox rule
* @param id - The rule ID to delete
* @returns Promise resolving when the rule is deleted
*/
async deleteSandboxRule(id: number): Promise<void> {
try {
return await invoke('delete_sandbox_rule', { id });
} catch (error) {
console.error("Failed to delete sandbox rule:", error);
throw error;
}
},
/**
* Gets platform capabilities for sandbox configuration
* @returns Promise resolving to platform capabilities
*/
async getPlatformCapabilities(): Promise<PlatformCapabilities> {
try {
return await invoke<PlatformCapabilities>('get_platform_capabilities');
} catch (error) {
console.error("Failed to get platform capabilities:", error);
throw error;
}
},
/**
* Tests a sandbox profile
* @param profileId - The profile ID to test
* @returns Promise resolving to test result message
*/
async testSandboxProfile(profileId: number): Promise<string> {
try {
return await invoke<string>('test_sandbox_profile', { profile_id: profileId });
} catch (error) {
console.error("Failed to test sandbox profile:", error);
throw error;
}
},
// Sandbox violation methods
/**
* Lists sandbox violations with optional filtering
* @param profileId - Optional profile ID to filter by
* @param agentId - Optional agent ID to filter by
* @param limit - Optional limit on number of results
* @returns Promise resolving to array of violations
*/
async listSandboxViolations(profileId?: number, agentId?: number, limit?: number): Promise<SandboxViolation[]> {
try {
return await invoke<SandboxViolation[]>('list_sandbox_violations', {
profile_id: profileId,
agent_id: agentId,
limit
});
} catch (error) {
console.error("Failed to list sandbox violations:", error);
throw error;
}
},
/**
* Logs a sandbox violation
* @param violation - The violation details
* @returns Promise resolving when logged
*/
async logSandboxViolation(violation: {
profileId?: number;
agentId?: number;
agentRunId?: number;
operationType: string;
patternValue?: string;
processName?: string;
pid?: number;
}): Promise<void> {
try {
return await invoke('log_sandbox_violation', {
profile_id: violation.profileId,
agent_id: violation.agentId,
agent_run_id: violation.agentRunId,
operation_type: violation.operationType,
pattern_value: violation.patternValue,
process_name: violation.processName,
pid: violation.pid
});
} catch (error) {
console.error("Failed to log sandbox violation:", error);
throw error;
}
},
/**
* Clears old sandbox violations
* @param olderThanDays - Optional days to keep (clears all if not specified)
* @returns Promise resolving to number of deleted violations
*/
async clearSandboxViolations(olderThanDays?: number): Promise<number> {
try {
return await invoke<number>('clear_sandbox_violations', { older_than_days: olderThanDays });
} catch (error) {
console.error("Failed to clear sandbox violations:", error);
throw error;
}
},
/**
* Gets sandbox violation statistics
* @returns Promise resolving to violation stats
*/
async getSandboxViolationStats(): Promise<SandboxViolationStats> {
try {
return await invoke<SandboxViolationStats>('get_sandbox_violation_stats');
} catch (error) {
console.error("Failed to get sandbox violation stats:", error);
throw error;
}
},
// Import/Export methods
/**
* Exports a single sandbox profile with its rules
* @param profileId - The profile ID to export
* @returns Promise resolving to export data
*/
async exportSandboxProfile(profileId: number): Promise<SandboxProfileExport> {
try {
return await invoke<SandboxProfileExport>('export_sandbox_profile', { profile_id: profileId });
} catch (error) {
console.error("Failed to export sandbox profile:", error);
throw error;
}
},
/**
* Exports all sandbox profiles
* @returns Promise resolving to export data
*/
async exportAllSandboxProfiles(): Promise<SandboxProfileExport> {
try {
return await invoke<SandboxProfileExport>('export_all_sandbox_profiles');
} catch (error) {
console.error("Failed to export all sandbox profiles:", error);
throw error;
}
},
/**
* Imports sandbox profiles from export data
* @param exportData - The export data to import
* @returns Promise resolving to import results
*/
async importSandboxProfiles(exportData: SandboxProfileExport): Promise<ImportResult[]> {
try {
return await invoke<ImportResult[]>('import_sandbox_profiles', { export_data: exportData });
} catch (error) {
console.error("Failed to import sandbox profiles:", error);
throw error;
}
},
/**
* Gets overall usage statistics
* @returns Promise resolving to usage statistics