Skip to main content
Comis has prompt-skill content safeguards and a separate OS-level sandbox for ordinary exec child processes. These controls have different scopes, and neither is a universal sandbox for every skill, tool, integration, or agent. For the detailed process boundary, see Exec Sandbox.
Sandbox vocabulary. Documentation has historically used “sandbox” for two different mechanisms:
  • Prompt-skill safeguards — load-time sanitization and optional pattern scanning for Markdown prompt-skill bodies. They are not process isolation. Agent-level tool policy and runtime limits apply separately. SKILL.md permissions and allowedTools declarations are currently advisory metadata, not an enforced per-skill boundary.
  • Exec sandbox — OS-level confinement for child processes launched through the ordinary exec tool when a supported provider is available and active. It does not wrap MCP stdio servers, browser processes, in-process tools, or every other way the daemon can reach the network or filesystem.
On Linux, working Bubblewrap provides the strongest supported boundary. macOS sandbox-exec is deprecated and best effort. If the provider is missing, disabled, or auto-disabled on a constrained container host, ordinary exec can run directly under the daemon account with a warning.

Overview

For Markdown prompt skills, the relevant layers are:
1

Content Scanning

Sanitized prompt-skill text is inspected for known patterns across six categories at load time when scanning is enabled.
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

Registered tools are filtered by the agent’s configured profile and allow/deny lists. This is agent-level policy, not enforcement of SKILL.md permission declarations.
4

Execution Limits

Agent-loop budgets, circuit breakers, step limits, and optional Node.js permissions constrain their respective runtime paths. They do not form one per-skill process sandbox.

Content Scanning

The content scanner inspects sanitized Markdown prompt-skill bodies at load time for known dangerous patterns. It does not inspect executable dependencies, MCP server binaries, browser code, or arbitrary files a skill later tells an agent to use. It is a pure function; callers handle audit emission and blocking decisions.
Source: packages/skills/src/skills/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/skills/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/skills/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)

Skill Manifest Declarations

Prompt skills can declare allowedTools and permissions in SKILL.md. Comis parses those fields, but they are not currently connected to an enforced per-skill runtime filter or OS permission boundary. Treat them as descriptive metadata. The agent’s toolPolicy, capability gates, and each tool’s own validation are the enforceable controls. For user-facing guide, see Tool Policy.

Execution Limits

Runtime constraints bound parts of the agent loop. They are not proof that a skill or every process it invokes is isolated.

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/tools/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 on supported launch paths, the Node.js permission model restricts configured filesystem and network operations. It is disabled by default and is not a general sandbox for arbitrary child processes, MCP servers, browsers, or non-Node tools. 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)

When agents.{id}.skills.execSandbox.enabled is "always", the ordinary exec tool uses the detected provider if one is available. This boundary is limited to that exec child process. Provider selection is 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 can return undefined, leaving ordinary exec to run without an OS sandbox. On bare-metal Linux where Bubblewrap exists but its smoke test fails, Comis keeps the provider so exec calls fail rather than silently bypassing that broken provider. Container hosts can explicitly rely on the container boundary and run ordinary exec without an inner sandbox.
Source: detectSandboxProvider() in packages/skills/src/tools/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