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.
Targets mass-dump patterns that extract all environment variables. Individual $VAR references are NOT flagged because they are common in configuration documentation.
Very low false-positive risk. These terms almost never appear in legitimate AI skill instructions.
Focuses on piped execution patterns (curl/wget output piped to interpreter) rather than standalone URL references. Reverse shell patterns are elevated to CRITICAL.
Only flags long encoded blocks (likely obfuscated payloads) or decode-and-execute chains. Short base64 examples in documentation are not flagged.
Detects attempts to escape the skill XML structure and inject system-level instructions at a higher privilege level.

Severity Levels

Scan Result Interface

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: 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

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

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: 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

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.

Bound comis-agent binary

The in-jail comis-agent CLI is itself a defense surface, so the binary the daemon makes available inside an agent’s jail is locked down two ways:
  • Read-only bind (--ro-bind). The binary is bind-mounted read-only into the jail (source path equals destination, so COMIS_AGENT_BIN / PATH resolves it) — exactly like the daemon’s own Node binary. A writable interpreter or binary inside a sandbox is a host-RCE vector, so it is never bound read-write and never copied to a writable location. The writable-path audit (bwrap-hardening.linux.test.ts) proves a write to the bound binary from inside the jail fails (the read-only bind rejects it).
  • sha256-pinned. A committed build manifest (comis-agent-manifest.json) pins the sha256 of the Comis-built binary. At jail construction the daemon re-hashes the bound bytes and refuses to bind a binary whose hash does not match the pin — a swapped or tampered binary is never made available.
Honest-degrade. When the binary is missing, fails the hash check, or in-jail Node is unavailable, the daemon emits a loud WARN, leaves COMIS_AGENT_BIN unset, and the comis-agent CLI surface is unavailable — while the orchestrate(script) surface still runs (the degrade is scoped to the CLI, never a silent bind of an unverified binary). See the comis-agent CLI 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: 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: 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:
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