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.exectool only. Auto-detected at daemon startup; falls back to unsandboxed with a warning if the binary is missing.
system.exec. Make sure to enable both.Overview
Every custom skill passes through four protection layers before and during execution:Sanitization Pipeline
Skill body cleaned through 4-step pipeline: HTML comment stripping, Unicode normalization, invisible character removal, and size enforcement.
Tool Policy Enforcement
Available tools filtered by profile, allow/deny lists, and per-skill restrictions.
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:exec_injection (4 patterns) -- CRITICAL
exec_injection (4 patterns) -- CRITICAL
Targets actual injection syntax operators combined with dangerous binaries. These patterns detect subshell injection, backtick injection,
eval() usage, and pipe-to-shell patterns.| Pattern ID | Severity | Description |
|---|---|---|
EXEC_SUBSHELL | CRITICAL | Subshell command injection: $(command) with dangerous binary |
EXEC_BACKTICK | CRITICAL | Backtick command injection with dangerous binary |
EXEC_EVAL | CRITICAL | eval() with string argument |
EXEC_PIPE_BASH | CRITICAL | Pipe to shell interpreter |
env_harvesting (3 patterns) -- WARN
env_harvesting (3 patterns) -- WARN
Targets mass-dump patterns that extract all environment variables. Individual
$VAR references are NOT flagged because they are common in configuration documentation.| Pattern ID | Severity | Description |
|---|---|---|
ENV_PRINTENV | WARN | printenv command dumps all environment variables |
ENV_PROC_ENVIRON | WARN | Direct read of process environment via /proc |
ENV_MASS_DUMP | WARN | Environment dump piped to exfiltration or encoding |
crypto_mining (3 patterns) -- CRITICAL/WARN
crypto_mining (3 patterns) -- CRITICAL/WARN
Very low false-positive risk. These terms almost never appear in legitimate AI skill instructions.
| Pattern ID | Severity | Description |
|---|---|---|
CRYPTO_STRATUM | CRITICAL | Mining pool protocol (stratum://) |
CRYPTO_MINER_BINARY | CRITICAL | Known cryptocurrency miner binary |
CRYPTO_POOL_DOMAIN | WARN | Mining pool domain pattern |
network_exfiltration (3 patterns) -- WARN/CRITICAL
network_exfiltration (3 patterns) -- WARN/CRITICAL
Focuses on piped execution patterns (curl/wget output piped to interpreter) rather than standalone URL references. Reverse shell patterns are elevated to CRITICAL.
| Pattern ID | Severity | Description |
|---|---|---|
NET_CURL_PIPE | WARN | curl output piped to interpreter |
NET_WGET_EXEC | WARN | wget output to stdout piped elsewhere |
NET_REVERSE_SHELL | CRITICAL | Reverse shell pattern |
obfuscated_encoding (3 patterns) -- WARN/CRITICAL
obfuscated_encoding (3 patterns) -- WARN/CRITICAL
Only flags long encoded blocks (likely obfuscated payloads) or decode-and-execute chains. Short base64 examples in documentation are not flagged.
| Pattern ID | Severity | Description |
|---|---|---|
OBF_BASE64_LONG | WARN | Long base64-encoded string (80+ chars) |
OBF_HEX_LONG | WARN | Long hex-escaped string (20+ sequences) |
OBF_BASE64_DECODE_PIPE | CRITICAL | base64 decode piped to another command |
xml_breakout (2 patterns) -- CRITICAL
xml_breakout (2 patterns) -- CRITICAL
Detects attempts to escape the skill XML structure and inject system-level instructions at a higher privilege level.
| Pattern ID | Severity | Description |
|---|---|---|
XML_SKILL_CLOSE | CRITICAL | Closing tag for skill XML structure (breakout attempt) |
XML_SYSTEM_TAG | CRITICAL | System-level message tag (breakout attempt) |
Severity Levels
| Severity | Meaning | Categories |
|---|---|---|
| CRITICAL | Active exploitation attempt. Skill should be blocked. | exec_injection, crypto_mining (stratum, miner binary), network_exfiltration (reverse shell), obfuscated_encoding (decode pipe), xml_breakout |
| WARN | Suspicious 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
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) viaString.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
Step 3: Strip Invisible Characters
Removes zero-width and invisible Unicode characters that could hide malicious content:| Character | Code Point | Name |
|---|---|---|
| ZWJ | U+200D | Zero-width joiner |
| ZWNJ | U+200C | Zero-width non-joiner |
| ZWSP | U+200B | Zero-width space |
| SHY | U+00AD | Soft hyphen |
| LRM | U+200E | Left-to-right mark |
| RLM | U+200F | Right-to-left mark |
| Tag block | U+E0000-U+E007F | Unicode tag block characters |
Step 4: Size Limit Enforcement
Truncates the sanitized output atmaxBodyLength 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
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
| Profile | Tools | Use Case |
|---|---|---|
minimal | read, write | Basic file operations only |
coding | read, edit, write, grep, find, ls, apply_patch, exec, process | Software development tasks |
messaging | message, session_status | Communication-only agents |
supervisor | agents_manage, obs_query, sessions_manage, memory_manage, channels_manage, tokens_manage, models_manage, skills_manage, mcp_manage, heartbeat_manage | Administrative agents |
full | All tools (empty array = unrestricted) | Fully trusted agents |
cron-minimal | web_search, message, read_file, write_file, list_dir, memory_store, memory_search, cron, discover | Conservative preset for cron jobs (opt-in only) |
heartbeat-minimal | message, memory_store, memory_search, discover | Conservative preset for heartbeat agents (opt-in only) |
Resolution Order
- Profile baseline — populate allowed set from the profile’s tool list
- Allow list additions — add explicitly allowed tools (with group expansion)
- 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:| Tool | Max Response Bytes | Max Chars | Extraction Strategy |
|---|---|---|---|
web_fetch | 2 MB | 50,000 | readability |
web_search | 500 KB | 40,000 | structured |
bash | 500 KB | 50,000 | tail |
file_read | 1 MB | 100,000 | raw |
mcp_default | 2 MB | 50,000 | raw |
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
| Layer | When | What | Action on Violation |
|---|---|---|---|
| Content Scanning | Skill load time | Inspects skill body for malicious patterns | Blocks skill loading (CRITICAL) or flags for review (WARN) |
| Sanitization | Skill load time | Strips hidden content and normalizes encoding | Silently removes dangerous content |
| Tool Policy | Execution time | Filters available tools by profile/allow/deny | Tool call rejected |
| Budget | Execution time | Tracks cumulative token spend | Execution halted |
| Circuit Breaker | Execution time | Tracks consecutive LLM failures | Requests rejected until cooldown |
| Step Limit | Execution time | Counts tool-use iterations | Execution completed with current state |
| Source Profiles | Tool result time | Caps external content size | Content truncated to limit |
| Exec Sandbox | Execution time | Wraps exec child process in OS namespace (bwrap/sandbox-exec) | Logs WARN, runs unsandboxed (graceful fallback) |
| Node Permissions | OS level | Restricts file system and network access | ERR_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)
Thesystem.exec tool wraps every shell command in a kernel-enforced
namespace. The provider is selected automatically at daemon startup based on
process.platform:
| Platform | Provider | Module |
|---|---|---|
| Linux | BwrapProvider (bubblewrap) | packages/skills/src/builtin/sandbox/bwrap-provider.ts |
macOS (darwin) | SandboxExecProvider (sandbox-exec) | packages/skills/src/builtin/sandbox/sandbox-exec-provider.ts |
| Anything else | none — 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.tsNetwork Modes
The exec sandbox supports two network modes, selected viaSandboxOptions.network:
| Mode | Config value | bwrap args | Description |
|---|---|---|---|
| Open (default) | { mode: "open" } or undefined | --unshare-all --share-net | Standard 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
WhensecureCredentialHome: 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)
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.ts — SandboxOptions.network union; packages/skills/src/tools/builtin/sandbox/bwrap-provider.ts — broker-only branch at line 234, secureCredentialHome bind removal at lines 196–218.Configuration Reference
All sandbox-related configuration fields consolidated:| Config Path | Type | Default | Description |
|---|---|---|---|
agents.{id}.toolPolicy.profile | string | "coding" | Tool policy profile name |
agents.{id}.toolPolicy.allow | string[] | [] | Additional tools to allow |
agents.{id}.toolPolicy.deny | string[] | [] | Tools to deny (overrides allow) |
agents.{id}.budget.maxTokens | number | varies | Maximum tokens per execution |
agents.{id}.budget.maxCostUsd | number | varies | Maximum cost per execution |
agents.{id}.skills.execSandbox.enabled | enum | "always" | OS-level sandbox mode: "always" or "never" |
agents.{id}.skills.execSandbox.readOnlyAllowPaths | string[] | [] | Additional read-only paths inside sandbox |
security.permission.enableNodePermissions | boolean | false | Enable OS-level sandboxing |
security.permission.allowedFsPaths | string[] | [] | Allowed file system paths |
security.permission.allowedNetHosts | string[] | [] | 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
