Skip to main content
The sandbox is a multi-layer defense system for custom skill execution. It combines content scanning, input sanitization, tool policy enforcement, and execution limits to prevent malicious or runaway skill behavior. For the separate OS-level kernel namespace that wraps the system.exec shell tool, see the dedicated Exec Sandbox page; this page summarizes its config keys and runtime detection at the bottom.
Sandbox vocabulary. Comis uses the word “sandbox” for two distinct mechanisms:
  • Skill sandbox — the application-level layers documented on this page (content scanning, sanitization, tool policy, execution limits). Always active for every skill.
  • Exec sandbox — the OS-level kernel namespace (bubblewrap / sandbox-exec) that wraps the system.exec tool only. Auto-detected at daemon startup; falls back to unsandboxed with a warning if the binary is missing.
The skill sandbox protects every skill regardless of how it executes. The exec sandbox protects only system.exec. Make sure to enable both.

Overview

Every custom skill passes through four protection layers before and during execution:
1

Content Scanning

Skill body inspected for dangerous patterns across 6 categories at load time.
2

Sanitization Pipeline

Skill body cleaned through 4-step pipeline: HTML comment stripping, Unicode normalization, invisible character removal, and size enforcement.
3

Tool Policy Enforcement

Available tools filtered by profile, allow/deny lists, and per-skill restrictions.
4

Execution Limits

Runtime constraints via budget protection, circuit breaker, step limits, and optional Node.js permissions.

Content Scanning

The content scanner inspects skill body content at load time for dangerous patterns. It is a pure function — callers handle audit emission and blocking decisions.
Source: packages/skills/src/prompt/content-scanner.ts — patterns across 6 categories. Patterns imported from @comis/core injection-patterns module.

Scan Categories

Six categories of malicious content are detected:
Targets actual injection syntax operators combined with dangerous binaries. These patterns detect subshell injection, backtick injection, eval() usage, and pipe-to-shell patterns.
Pattern IDSeverityDescription
EXEC_SUBSHELLCRITICALSubshell command injection: $(command) with dangerous binary
EXEC_BACKTICKCRITICALBacktick command injection with dangerous binary
EXEC_EVALCRITICALeval() with string argument
EXEC_PIPE_BASHCRITICALPipe to shell interpreter
Targets mass-dump patterns that extract all environment variables. Individual $VAR references are NOT flagged because they are common in configuration documentation.
Pattern IDSeverityDescription
ENV_PRINTENVWARNprintenv command dumps all environment variables
ENV_PROC_ENVIRONWARNDirect read of process environment via /proc
ENV_MASS_DUMPWARNEnvironment dump piped to exfiltration or encoding
Very low false-positive risk. These terms almost never appear in legitimate AI skill instructions.
Pattern IDSeverityDescription
CRYPTO_STRATUMCRITICALMining pool protocol (stratum://)
CRYPTO_MINER_BINARYCRITICALKnown cryptocurrency miner binary
CRYPTO_POOL_DOMAINWARNMining pool domain pattern
Focuses on piped execution patterns (curl/wget output piped to interpreter) rather than standalone URL references. Reverse shell patterns are elevated to CRITICAL.
Pattern IDSeverityDescription
NET_CURL_PIPEWARNcurl output piped to interpreter
NET_WGET_EXECWARNwget output to stdout piped elsewhere
NET_REVERSE_SHELLCRITICALReverse shell pattern
Only flags long encoded blocks (likely obfuscated payloads) or decode-and-execute chains. Short base64 examples in documentation are not flagged.
Pattern IDSeverityDescription
OBF_BASE64_LONGWARNLong base64-encoded string (80+ chars)
OBF_HEX_LONGWARNLong hex-escaped string (20+ sequences)
OBF_BASE64_DECODE_PIPECRITICALbase64 decode piped to another command
Detects attempts to escape the skill XML structure and inject system-level instructions at a higher privilege level.
Pattern IDSeverityDescription
XML_SKILL_CLOSECRITICALClosing tag for skill XML structure (breakout attempt)
XML_SYSTEM_TAGCRITICALSystem-level message tag (breakout attempt)

Severity Levels

SeverityMeaningCategories
CRITICALActive exploitation attempt. Skill should be blocked.exec_injection, crypto_mining (stratum, miner binary), network_exfiltration (reverse shell), obfuscated_encoding (decode pipe), xml_breakout
WARNSuspicious pattern with possible legitimate use. Flag for review.env_harvesting, crypto_mining (pool domain), network_exfiltration (curl/wget pipe), obfuscated_encoding (long encoded strings)

Scan Result Interface

interface ContentScanResult {
  clean: boolean;                     // true if no patterns matched
  findings: ContentScanFinding[];     // array of all matched patterns
}

interface ContentScanFinding {
  ruleId: string;          // Pattern ID (e.g., "EXEC_SUBSHELL")
  category: ScanCategory;  // Category name
  severity: ScanSeverity;  // "CRITICAL" or "WARN"
  description: string;     // Human-readable description
  matchedText: string;     // Matched content (truncated to 100 chars)
  position: number;        // Character offset in content
  lineNumber: number;      // 1-based line number of the match in the original content
}
For user-facing guide, see Security Scanning.

Sanitization Pipeline

The 4-step sanitization pipeline processes skill body content before it reaches the system prompt. All functions are pure with no side effects.
Source: packages/skills/src/prompt/sanitizer.ts — strict pipeline order: strip HTML comments, NFKC normalize, strip invisible, enforce size.

Step 1: Strip HTML Comments

Removes all <!-- ... --> sequences using non-greedy regex (/<!--[\s\S]*?-->/g). Non-greedy matching handles multiple separate comments correctly, stopping at the first --> rather than the last. Returns the count of comments removed for audit logging.

Step 2: Unicode NFKC Normalization

Applies NFKC normalization (compatibility decomposition + canonical composition) via String.prototype.normalize("NFKC"). This:
  • Decomposes fullwidth characters to their ASCII equivalents (e.g., fullwidth A to A)
  • Decomposes ligatures into component characters
  • Normalizes compatibility characters to their canonical forms
This prevents homoglyph-based obfuscation where visually similar characters bypass pattern matching.

Step 3: Strip Invisible Characters

Removes zero-width and invisible Unicode characters that could hide malicious content:
CharacterCode PointName
ZWJU+200DZero-width joiner
ZWNJU+200CZero-width non-joiner
ZWSPU+200BZero-width space
SHYU+00ADSoft hyphen
LRMU+200ELeft-to-right mark
RLMU+200FRight-to-left mark
Tag blockU+E0000-U+E007FUnicode tag block characters
Also detects and reports Unicode tag block bypass attempts (characters in the U+E0000-U+E007F range used to encode hidden instructions).

Step 4: Size Limit Enforcement

Truncates the sanitized output at maxBodyLength characters. Default: 20,000 characters.
  • Size enforcement applies to the output AFTER all other steps
  • This prevents unnecessary truncation when HTML comments inflate the raw input size
  • When truncation occurs, [TRUNCATED] marker is appended

Pipeline Result

interface SanitizeResult {
  body: string;                  // Final sanitized content
  htmlCommentsStripped: number;  // Count of HTML comments removed
  truncated: boolean;            // Whether size limit was hit
  tagBlockDetected: boolean;     // Unicode tag block bypass detected
}

Tool Policy Enforcement

Tool policies control which tools are available to each agent during skill execution.
Source: packages/skills/src/policy/tool-policy.ts — config-driven filtering with 5 profiles and group expansion.

Built-in Profiles

ProfileToolsUse Case
minimalread, writeBasic file operations only
codingread, edit, write, grep, find, ls, apply_patch, exec, processSoftware development tasks
messagingmessage, session_statusCommunication-only agents
supervisoragents_manage, obs_query, sessions_manage, memory_manage, channels_manage, tokens_manage, models_manage, skills_manage, mcp_manage, heartbeat_manageAdministrative agents
fullAll tools (empty array = unrestricted)Fully trusted agents
cron-minimalweb_search, message, read_file, write_file, list_dir, memory_store, memory_search, cron, discoverConservative preset for cron jobs (opt-in only)
heartbeat-minimalmessage, memory_store, memory_search, discoverConservative preset for heartbeat agents (opt-in only)

Resolution Order

  1. Profile baseline — populate allowed set from the profile’s tool list
  2. Allow list additions — add explicitly allowed tools (with group expansion)
  3. Deny list removals — remove denied tools (deny always wins)

Per-Skill Restrictions

In addition to agent-level tool policy, individual skills can declare tool restrictions in their SKILL.md manifest. These restrictions further filter the already-policy-filtered tool set. A skill cannot grant tools that the agent’s policy denies. For user-facing guide, see Tool Policy.

Execution Limits

Runtime constraints prevent runaway or expensive skill executions.

Budget Protection

Each agent has a configurable token budget that limits total LLM spend. When the budget is exhausted, further tool calls are rejected. See Agent Safety for configuration details.

Circuit Breaker

The circuit breaker tracks consecutive LLM failures per agent. After a configurable number of failures, the circuit opens and rejects further requests until a cooldown period expires. This prevents cascading failures from propagating through the system. See Agent Safety for configuration details.

Step Limit

Each execution has a maximum number of tool-use steps. When the limit is reached, the execution completes with the current state. This prevents infinite loops where the LLM keeps calling tools without converging on a response.

Source Profiles

Built-in tools that ingest external content have per-tool source profiles controlling byte/char limits and extraction strategies. These are clamped to hard ceilings to prevent runaway context injection:
ToolMax Response BytesMax CharsExtraction Strategy
web_fetch2 MB50,000readability
web_search500 KB40,000structured
bash500 KB50,000tail
file_read1 MB100,000raw
mcp_default2 MB50,000raw
Hard ceilings: 5 MB max response bytes, 500K max chars. Operator overrides cannot exceed these values.
Source: packages/skills/src/builtin/tool-source-profiles.ts — per-tool defaults with hard ceiling clamping. Operator overrides via per-agent config.

Defense Layer Summary

LayerWhenWhatAction on Violation
Content ScanningSkill load timeInspects skill body for malicious patternsBlocks skill loading (CRITICAL) or flags for review (WARN)
SanitizationSkill load timeStrips hidden content and normalizes encodingSilently removes dangerous content
Tool PolicyExecution timeFilters available tools by profile/allow/denyTool call rejected
BudgetExecution timeTracks cumulative token spendExecution halted
Circuit BreakerExecution timeTracks consecutive LLM failuresRequests rejected until cooldown
Step LimitExecution timeCounts tool-use iterationsExecution completed with current state
Source ProfilesTool result timeCaps external content sizeContent truncated to limit
Exec SandboxExecution timeWraps exec child process in OS namespace (bwrap/sandbox-exec)Logs WARN, runs unsandboxed (graceful fallback)
Node PermissionsOS levelRestricts file system and network accessERR_ACCESS_DENIED thrown

Node.js Permissions

When enabled, child processes spawned during skill execution run with restricted OS-level permissions. This provides a defense layer beneath application-level controls. See Node Permissions for full configuration reference.

Exec Sandbox (OS-Level)

The system.exec tool wraps every shell command in a kernel-enforced namespace. The provider is selected automatically at daemon startup based on process.platform:
PlatformProviderModule
LinuxBwrapProvider (bubblewrap)packages/skills/src/builtin/sandbox/bwrap-provider.ts
macOS (darwin)SandboxExecProvider (sandbox-exec)packages/skills/src/builtin/sandbox/sandbox-exec-provider.ts
Anything elsenone — exec tool runs unsandboxed with a WARN log
detectSandboxProvider() first probes the candidate binary’s availability (bwrap --version or sandbox-exec -h); if the binary is missing it logs a structured warning with the install hint (apt install bubblewrap) and returns undefined, leaving the exec tool to run unsandboxed unless the operator has set execSandbox.enabled: "never" to disable it explicitly.
Source: detectSandboxProvider() in packages/skills/src/builtin/sandbox/detect-provider.ts
For the full mount table, attack scope, and configuration walkthrough, see Exec Sandbox.

Network Modes

The exec sandbox supports two network modes, selected via SandboxOptions.network:
ModeConfig valuebwrap argsDescription
Open (default){ mode: "open" } or undefined--unshare-all --share-netStandard exec sandbox; full network access
Broker-only{ mode: "broker-only", brokerSocketPath: "..." }--unshare-all --unshare-net --bind <path> <path>Driven-CLI sandbox; only the broker unix socket is reachable via bind-mount (Linux only)
broker-only mode is set automatically for driven-CLI spawns — it is not configured directly by operators. On Linux it uses --unshare-net to remove all network access except the broker unix socket bind-mount. On macOS, broker-only mode is not available (bubblewrap requirement); the broker still provides TLS termination and injection, but without network namespace enforcement.

Secure Credential Home

When secureCredentialHome: true is set on the sandbox options (automatically applied for driven-CLI spawns), the following bind mounts are omitted so credential files are unreachable inside the sandbox:
  • ~/.claude (read-write bind removed)
  • ~/.claude.json (read-only bind removed)
  • ~/.local/share/claude (read-write bind removed)
This means cat ~/.claude/.credentials.json inside the sandbox returns “no such file or directory”. The Claude Code CLI cannot read its own credential cache from inside the namespace. The broker-only network mode and secureCredentialHome work together as the credential isolation layer for driven-CLI spawns. Credential Broker →
Source: packages/skills/src/tools/builtin/sandbox/types.tsSandboxOptions.network union; packages/skills/src/tools/builtin/sandbox/bwrap-provider.tsbroker-only branch at line 234, secureCredentialHome bind removal at lines 196–218.

Configuration Reference

All sandbox-related configuration fields consolidated:
Config PathTypeDefaultDescription
agents.{id}.toolPolicy.profilestring"coding"Tool policy profile name
agents.{id}.toolPolicy.allowstring[][]Additional tools to allow
agents.{id}.toolPolicy.denystring[][]Tools to deny (overrides allow)
agents.{id}.budget.maxTokensnumbervariesMaximum tokens per execution
agents.{id}.budget.maxCostUsdnumbervariesMaximum cost per execution
agents.{id}.skills.execSandbox.enabledenum"always"OS-level sandbox mode: "always" or "never"
agents.{id}.skills.execSandbox.readOnlyAllowPathsstring[][]Additional read-only paths inside sandbox
security.permission.enableNodePermissionsbooleanfalseEnable OS-level sandboxing
security.permission.allowedFsPathsstring[][]Allowed file system paths
security.permission.allowedNetHostsstring[][]Allowed network hosts
Source: Config schemas in packages/core/src/config/schema-security.ts and packages/core/src/config/schema-agent.ts.

Security Model

Defense-in-depth security architecture

Tool Security

SSRF guard, tool policies, content scanner

Action Classifier

Complete action registry

Node Permissions

Node.js permission model

Sandbox Guide

User-facing sandbox setup guide