Skip to main content
Tool security encompasses four subsystems that protect against malicious input from external sources: SSRF prevention for outbound HTTP, tool policy enforcement for access control, content scanning for skill body inspection, and sanitization for hidden content defense.

SSRF Guard

The SSRF guard prevents server-side request forgery by validating every outbound URL before any HTTP request is made. The validateUrl() function performs DNS-pinned validation: it resolves the hostname, then checks the resolved IP against blocked ranges.
Source: packages/core/src/security/ssrf-guard.ts — requirement: every web-facing tool must pass through validateUrl() before fetch.

Validation Steps

validateUrl() performs 5 checks in order: The function returns Result<ValidatedUrl, Error> where ValidatedUrl contains hostname, ip (resolved address), and url (parsed URL object).

Allowed Protocols

Only two protocols are permitted:
  • http:
  • https:
All other protocols (e.g., file:, ftp:, gopher:) are rejected.

Blocked IP Ranges

Six IP range categories are blocked using ipaddr.js range classification:

Cloud Metadata IPs

Three explicit cloud metadata service IPs are blocked regardless of their range classification:

DNS Resolution

The SSRF guard resolves hostnames before checking IPs. This prevents DNS rebinding attacks where a hostname initially resolves to a safe IP but later resolves to a blocked one. IPv6 literal hostnames (bracketed like [::1]) have brackets stripped before DNS lookup.

ValidatedUrl Interface

On successful validation, validateUrl() returns a ValidatedUrl object:
The caller uses the resolved ip for the actual HTTP request, ensuring the fetched IP matches the validated one (DNS pinning).

Usage Example

Tool Policy

Tool policies control which tools are available to an agent. Policies use named profiles as a baseline, with allow/deny overrides for fine-grained control.
Source: packages/skills/src/policy/tool-policy.ts — config-driven tool filtering with group expansion.

Built-in Profiles

Built-in profiles define baseline tool sets. The five general-purpose profiles plus two narrow opt-in presets for non-interactive workloads: The *-minimal presets are opt-in via toolPolicy.profile on a CronJob or heartbeat config respectively. They are never applied as a silent default — operators are expected to widen them per-job via allow.

Tool Groups

Groups provide convenient bulk operations using group:xxx syntax in allow/deny arrays:
The shipped DAG context tools are ctx_search, ctx_inspect, and ctx_expand. They are wired only for a live, fully scoped DAG session and are read-only and never-export. There is no group:context convenience group; operators can name an individual tool in an allow or deny list when needed.
The ordinary exec tool is not fail-closed on sandbox availability. With skills.execSandbox.enabled: "always", Comis uses a detected OS sandbox, but if no supported provider is available it logs a warning and executes on the host. The persistent terminal driver and jailed autonomy surfaces have separate Linux isolation requirements and refusal behavior.

Resolution Precedence

Tool policy resolution follows a strict order:
  1. Profile baseline — start with the profile’s tool set
  2. Allow additions — add explicitly allowed tools and expand group references
  3. Deny removals — remove denied tools (deny always takes precedence)
Per-skill tool restrictions from skill manifests provide an additional layer of filtering applied after the agent-level policy. For user-facing configuration guide, see Tool Policy.

MCP Export Policy

Tool policy (above) governs which tools an agent may call. A separate, stricter layer governs which tools an external MCP client may reach over POST /mcp/v1: every platform tool carries an mcpExportPolicy of safe, permission-gated, or never-export (a missing annotation is treated as never-export — default-deny). See the MCP server bucket reference for the full classification.
Source: packages/skills/src/skills/bridge/tool-metadata-registry.ts (the mcpExportPolicy annotations) + packages/daemon/src/api/mcp-server-handlers.ts (the registration filter + per-tool dispatch).

obs_explain — read-only incident report, allowlist-gated

obs_explain is a permission-gated, isReadOnly MCP tool that surfaces the obs.explain IncidentReport (root-cause post-mortem for a session or trace) to an external coding agent. It is reachable only when the operator adds "obs_explain" to that client’s gateway.tokens[].mcpClient.allowlist. Three properties make this safe to expose with no new privilege:
  • The allowlist IS the granted permission — not admin trust. obs_explain does not invoke the admin-gated obs.explain RPC. Its dispatch branch runs the report assembler directly under daemon authority, bypassing the trust-flag indirection entirely. No _trustLevel:"admin" is ever injected, and the admin RPC’s own ["admin"] gate is unchanged for non-MCP callers. A client without obs_explain allowlisted is rejected (default-deny).
  • Digest-only and bounded. The report is summary-bounded (no raw tool bodies, no raw message content cross the boundary) and flows through wrapExternalContent — the calling agent receives it wrapped in a SECURITY NOTICE block + untrusted-content markers, like any other external text.
  • Read-only + rate-limited. The tool performs no mutation, and the standard per-client per-tool minute-bucket rate limit applies.
Enumeration implication. An allowlisted client can request an IncidentReport for any sessionKey or traceId it can name — there are no per-session ACLs on top of the allowlist grant (out of scope; the report is digest-only/bounded regardless). The operator decides which clients receive obs_explain by editing mcpClient.allowlist; it must not be granted by default. Before flipping /mcp/v1 live on a publicly reachable instance, a security reviewer should confirm the grant list and the digest-only bounding meet the deployment’s threat model.

Content Scanner

The content scanner inspects skill body content at load time for dangerous patterns across six categories. It is a pure function with no side effects — callers handle audit emission and blocking decisions.
Source: packages/skills/src/prompt/content-scanner.ts — iterates CONTENT_SCAN_RULES array against content, producing ContentScanResult.

Scan Categories

Targets injection syntax operators combined with dangerous binaries, not mere mentions of command names.
Targets mass-dump patterns, not individual $VAR references which are common in documentation.
Very low false-positive risk — these terms rarely appear in legitimate skill instructions.
Focuses on piped execution rather than standalone URL references.
Only flags long encoded blocks or decode-and-execute chains, not short base64 examples.
Detects attempts to escape skill XML structure and inject system-level instructions.

Severity Levels

  • CRITICAL: Patterns that indicate active exploitation attempts (exec injection, reverse shells, crypto mining, base64 decode piping, XML breakout)
  • WARN: Patterns that are suspicious but may have legitimate uses (environment harvesting, curl piping, long encoded strings, mining pool domains)
With the default blockOnCritical: true policy, CRITICAL findings block a prompt skill and WARN findings do not. A warning is detection evidence, not a clean bill of health. Learned Mental Model documents use a separate static validator built on the memory-write scan. It rejects critical secret or poison findings, but warning-level findings are recorded and allowed. This means some jailbreak-like or otherwise harmful language can pass learned-document validation and later influence model tool selection. Learned docs remain advisory and add no execution authority, but operators should not treat validation or the learned trust label as proof of reliability.

Scan Result

The scanSkillContent() function returns a ContentScanResult:
For user-facing guide, see Security Scanning.

Sanitization Pipeline

The sanitization pipeline processes skill body content through four ordered steps before it reaches the system prompt.
Source: packages/skills/src/prompt/sanitizer.ts — pure functions, no side effects. Audit metadata returned for caller to emit events.

Pipeline Steps

1

Strip HTML Comments

Removes all &lt;!-- ... --&gt; sequences using non-greedy regex to handle multiple separate comments correctly. Returns the count of comments removed for audit logging.
2

NFKC Normalization

Applies Unicode NFKC normalization (compatibility decomposition + canonical composition). This decomposes fullwidth characters and ligatures into their standard equivalents, preventing homoglyph-based obfuscation.
3

Strip Invisible Characters

Removes zero-width and invisible characters that could hide malicious content:
  • Zero-width joiner (U+200D)
  • Zero-width non-joiner (U+200C)
  • Zero-width space (U+200B)
  • Soft hyphen (U+00AD)
  • Left-to-right mark (U+200E)
  • Right-to-left mark (U+200F)
  • Unicode tag block characters (U+E0000-U+E007F)
4

Size Limit Enforcement

Truncates the final sanitized output at maxBodyLength characters (default: 20,000). Size enforcement applies to the output after all other sanitization steps, preventing unnecessary truncation when HTML comments inflate raw size. Appends [TRUNCATED] marker when truncation occurs.

Sanitization Result

Audit Wrapper

Classified tool executions can generate audit events through the TypedEventBus, including action type, classification (from the Action Classifier), outcome, and timing metadata. These events support investigation of covered tool paths; they are not a complete record of every model decision or external side effect. The audit event structure:
Every tool execution — whether built-in, prompt skill, or MCP — generates an audit event. The classification field comes from the Action Classifier registry. Failed executions still emit audit events with outcome: "failure" and the error message in metadata.

Action Classifier

Complete registry of action classifications

Security Model

Defense-in-depth security architecture

Sandbox

Skill sandboxing implementation

Tool Policy Guide

User-facing tool policy configuration