Install in Claude Code
Copygit clone --depth 1 https://github.com/TerminalSkills/skills /tmp/agent-sandbox && cp -r /tmp/agent-sandbox/skills/agent-sandbox ~/.claude/skills/agent-sandboxThen start a new Claude Code session; the skill loads automatically.
Definition
SKILL.md
# Agent Sandbox
## Overview
AI agents execute code, modify files, and run shell commands. Without guardrails, a bad prompt or hallucination can delete your database, overwrite production configs, or exfiltrate secrets. This skill builds safety layers — sandboxed execution, filesystem restrictions, network policies, audit trails, and kill switches.
## When to Use
- Running untrusted or AI-generated code in production
- Adding safety controls to coding agents that modify your codebase
- Restricting which files, directories, or commands an agent can access
- Logging every agent action for compliance or debugging
- Building multi-tenant agent platforms where agents need isolation
## Instructions
### Strategy 1: Filesystem + Process Sandbox (Zero Dependencies)
The simplest safety layer — restrict which paths the agent can read/write and which commands it can execute. No Docker required.
```typescript
// sandbox.ts — Filesystem and process sandbox for AI agents
/**
* Wraps agent operations with safety checks:
* - Allowlist/denylist for file paths
* - Command blocklist (rm -rf, DROP TABLE, etc.)
* - Audit log of every action
* - Kill switch to halt agent immediately
*/
import { execSync } from "child_process";
import { readFileSync, writeFileSync, existsSync, appendFileSync } from "fs";
import { resolve, relative } from "path";
interface SandboxConfig {
workDir: string; // Root directory agent can access
allowedPaths: string[]; // Glob patterns of allowed paths
deniedPaths: string[]; // Glob patterns of denied paths
blockedCommands: string[]; // Commands that are never allowed
maxFileSize: number; // Max bytes per file write
auditLog: string; // Path to audit log file
readOnly: boolean; // If true, block all writes
}
const DEFAULT_BLOCKED = [
"rm -rf /", "rm -rf ~", "rm -rf .",
"mkfs", "dd if=", "> /dev/sd",
"DROP DATABASE", "DROP TABLE", "TRUNCATE",
"curl.*|.*sh", "wget.*|.*bash", // Pipe to shell
"chmod 777", "chmod -R 777",
"env | curl", "printenv | curl", // Secret exfiltration
"ssh-keygen", "ssh-copy-id",
];
export class AgentSandbox {
private config: SandboxConfig;
private killed = false;
constructor(config: Partial<SandboxConfig> & { workDir: string }) {
this.config = {
allowedPaths: ["**"],
deniedPaths: ["**/.env", "**/.ssh/**", "**/node_modules/**"],
blockedCommands: DEFAULT_BLOCKED,
maxFileSize: 1024 * 1024, // 1MB default
auditLog: "./agent-audit.jsonl",
readOnly: false,
...config,
};
}
/**
* Read a file through the sandbox — checks path is allowed.
*/
readFile(filePath: string): string {
this.checkKilled();
const absPath = resolve(this.config.workDir, filePath);
this.checkPathAllowed(absPath, "read");
this.audit("read", filePath);
return readFileSync(absPath, "utf-8");
}
/**
* Write a file through the sandbox — checks path, size, and read-only mode.
*/
writeFile(filePath: string, content: string): void {
this.checkKilled();
if (this.config.readOnly) {
throw new SandboxError("Write blocked: sandbox is read-only");
}
const absPath = resolve(this.config.workDir, filePath);
this.checkPathAllowed(absPath, "write");
if (Buffer.byteLength(content) > this.config.maxFileSize) {
throw new SandboxError(
`Write blocked: file exceeds max size (${this.config.maxFileSize} bytes)`
);
}
this.audit("write", filePath, { size: Buffer.byteLength(content) });
writeFileSync(absPath, content);
}
/**
* Execute a command through the sandbox — checks against blocklist.
*/
exec(command: string, timeoutMs: number = 30000): string {
this.checkKilled();
this.checkCommandAllowed(command);
this.audit("exec", command);
try {
return execSync(command, {
cwd: this.config.workDir,
encoding: "utf-8",
timeout: timeoutMs,
maxBuffer: 10 * 1024 * 1024, // 10MB output limit
});
} catch (error: any) {
this.audit("exec_error", command, { error: error.message });
throw error;
}
}
/**
* Kill switch — immediately halt all agent operations.
*/
kill(reason: string): void {
this.killed = true;
this.audit("killed", reason);
console.error(`🛑 Agent sandbox killed: ${reason}`);
}
private checkKilled(): void {
if (this.killed) throw new SandboxError("Agent has been killed");
}
private checkPathAllowed(absPath: string, operation: string): void {
const relPath = relative(this.config.workDir, absPath);
// Must be within workDir (no ../ escapes)
if (relPath.startsWith("..")) {
throw new SandboxError(`${operation} blocked: path escapes sandbox (${relPath})`);
}
// Check denylist
for (const pattern of this.config.deniedPaths) {
if (matchGlob(relPath, pattern)) {
throw new SandboxError(`${operation} blocked: path matches denylist (${pattern})`);
}
}
}
private checkCommandAllowed(command: string): void {
const lower = command.toLowerCase();
for (const blocked of this.config.blockedCommands) {
if (lower.includes(blocked.toLowerCase())) {
throw new SandboxError(`Command blocked: matches "${blocked}"`);
}
}
}
private audit(action: string, target: string, extra?: Record<string, unknown>): void {
const entry = {
timestamp: new Date().toISOString(),
action,
target,
...extra,
};
appendFileSync(this.config.auditLog, JSON.stringify(entry) + "\n");
}
}
class SandboxError extends Error {
constructor(message: string) {
super(message);
this.name = "SandboxError";
}
}
function matchGlob(path: string, pattern: string): boolean {
const regex = pattern
.replace(/\*\*/g, ".*")
.replace(/\*/g, "[^/]*")
.replace(/\?/g, ".");
return new RegMore from this repository
PULL_REQUEST_TEMPLATESkill
3dsmax-renderingSkill
>-
3dsmax-scriptingSkill
>-
3proxySkill
>-
a2a-protocolSkill
>-
ab-test-setupSkill
When the user wants to plan, design, or implement an A/B test or experiment. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," or "hypothesis." For tracking implementation, see analytics-tracking.
ablySkill
>-
accessibility-auditorSkill
>-