Security engineered in. Not bolted on.
Comis wasn't patched for security after launch. Every component was built from scratch with the question: what happens when an AI agent has real power and someone tries to abuse it?
On
Exec sandbox where supported
18
Skill content-scanner rules
0
Secrets in plaintext
Fail-closed
Broker & action classifier
Flagship feature
Sandboxed process isolation.
Shell commands are isolated with the strongest sandbox provider the host can supply. On supported Linux and macOS systems, the agent sees its workspace and approved runtime paths, not your whole machine.
- • Full namespace unsharing - mount, PID, user, cgroup, IPC all isolated
- • Private /tmp and /dev - tmpfs mounts, no host filesystem leakage
- • Read-only system paths - /usr, /bin, /lib, /etc (subset) mounted read-only
- • --die-with-parent - sandbox process dies if daemon dies, no orphans
- • Network preserved - network isolation is a separate concern (SSRF guard)
- • Default-deny profile - starts from (deny default), explicitly allows each capability
- • Dynamic SBPL generation - per-invocation profile based on agent workspace
- • Symlink resolution - resolves /tmp → /private/tmp for kernel path matching
- • Startup smoke test - verifies sandbox-exec works on current macOS version
- • Explicit downgrade - unavailable sandbox providers are detected and surfaced
Production runs on Linux. Docker Desktop's lightweight VM kernel can't run the exec sandbox - Comis detects unsupported dev/container sandbox hosts at startup, logs the downgrade, and auto-disables exec sandboxing rather than pretending isolation exists. Detection is explicit, not silent: you see it in the startup logs and choose how to proceed.
6 layers of exec defense, inside out:
Layer 1
Tool policy can remove exec entirely
Layer 2
Command denylist blocks rm -rf, mkfs, etc.
Layer 3
Env denylist blocks LD_PRELOAD, DYLD_*
Layer 4
CWD validation via safePath()
Layer 5
OS kernel sandbox (bwrap / sandbox-exec)
Layer 6
Subprocess env filtering strips API keys
Credential broker
Your API keys are never where the agent can read them.
Secrets live in an AES-256-GCM encrypted store; the key is injected at the network boundary, never inside the sandbox. The code that builds the sandbox env never even reads the real key. An in-process MITM broker terminates TLS with its own CA, validates a single-use session token, matches host and path against the allow-list, and swaps the placeholder at the header layer. It fails closed: any gate failure (407/403/502) destroys the tunnel before a single byte reaches upstream.
Credential broker deep dive →Defense in depth
Layered runtime defenses, not a single guardrail.
Defense in depth - layered runtime defenses, benchmarked, not a single guardrail. Each layer protects against a specific attack vector; no single failure compromises the system.
Block threats before they reach the LLM
Encrypt, scope, redact, and broker credentials at every layer
Prevent outbound requests to private networks
Sandboxed filesystem execution where the host supports it
Limit what each agent can do and require approval
Partition trust levels to prevent memory poisoning
Detect when the LLM leaks system instructions
Authenticate and verify every connection
Prevent runaway costs and sanitize tool output
Screen skills and packages, block insecure code before merge
Every layer explained
What each layer protects against - and how.
The first principle: treat the LLM as the attack surface, not the security boundary. Every defense below works even if the model itself is fully compromised.
Input Guard
PerimeterProtects against: Prompt injection & jailbreaks
A semantic scoring engine weighs jailbreak phrasing, role markers, dangerous commands, secret formats, prompt-extraction and credential-logging attempts. Typoglycemia detection catches scrambled-letter evasion; code-block exclusion avoids false positives. Three risk levels (low/medium/high) with configurable actions: pass, warn, reinforce, or block.
Output Guard
PerimeterProtects against: Secret leakage & data exfiltration
Scans every LLM response for 15 secret patterns (AWS keys, bearer tokens, JWTs, database connection strings), canary token leakage, and system prompt extraction attempts. Critical findings are redacted as [REDACTED] before delivery.
External Content Wrapping
PerimeterProtects against: Indirect prompt injection
All external content (web fetches, API responses, emails, webhooks) is wrapped in randomized 24-hex-char security delimiters with an explicit warning header. The LLM sees clear boundaries between trusted instructions and untrusted content.
Zero-Width Character Stripping
PerimeterProtects against: Invisible character injection
Strips 15+ categories of invisible Unicode characters (U+200B-200F, U+2060, FEFF, U+00AD, tag block U+E0000-E007F) while preserving legitimate flag emoji. Prevents hidden instruction embedding.
Injection Rate Limiter
PerimeterProtects against: Repeated injection attempts
Per-user sliding 5-minute window with progressive thresholds: warn at 3 detections, audit at 5. TTL-based eviction with 10K entry cap to prevent memory exhaustion.
Secret Manager
SecretsProtects against: Credential exposure
AES-256-GCM encryption at rest with HKDF-SHA256 key derivation and per-encryption random salts. Defensive snapshot of environment at creation - no mutation tracking. No enumeration API. Diagnostic errors include the missing key name but never the value.
Scoped Secret Manager
SecretsProtects against: Cross-agent credential access
Per-agent glob-pattern filtering (e.g., OPENAI_*, ANTHROPIC_*). Decorator pattern: indistinguishable from plain SecretManager. Audit events (secret:accessed) log every access attempt with success/denied/not_found outcomes.
Credential Broker
SecretsProtects against: API key exposure via agentic CLIs
Works for API-key CLIs - Claude Code included. The real key stays in the daemon's AES-256-GCM encrypted store and is resolved per request by the in-daemon HTTPS broker. The CLI runs with a placeholder; the broker terminates TLS with its own CA, verifies the single-use proxy token, and injects the real credential for allow-listed hosts (`x-api-key`, `Authorization: Bearer`, or query param). On supported Linux hosts, broker-only network mode prevents direct egress from credentialed sandboxes. Broker failures stop before upstream forwarding: bad token -> 407, no binding -> 403, missing secret -> 502. Every inject, deny, and blocked-egress event is audited with `agentId` and `traceId`.
Log Sanitizer
SecretsProtects against: Secrets in logs
Three layers deep: Pino fast-redact for structured fields, a regex-based sanitizer for free-text credential detection, and ReDoS mitigation with a 1MB input cap. Defense-in-depth - any single layer failing doesn't expose secrets.
SSRF Guard
NetworkProtects against: Server-side request forgery
DNS-pinned URL validation before every outbound fetch. Blocks private RFC 1918 ranges, loopback, link-local, and explicitly blocks cloud metadata IPs (169.254.169.254 for AWS/GCP/Azure, 100.100.100.200 for Alibaba). HTTP/HTTPS only.
OS-Level Exec Sandbox
Process IsolationProtects against: Filesystem escape & host compromise
Sandboxed filesystem isolation for shell commands where the host supports it. Linux uses bubblewrap (bwrap) with full namespace unsharing (mount, PID, user, cgroup, IPC). macOS uses sandbox-exec with SBPL deny-default profiles where available. Agents can be limited to their own workspace, with package manager caches redirected into that workspace and process tree kill cascades via --die-with-parent.
Tool Policy Engine
Access ControlProtects against: Unauthorized tool access
Named profiles (minimal, coding, messaging, supervisor, full) with group-based expansion. Per-agent allow/deny lists. The minimal profile exposes only exec, read, write - no memory, no web, no messaging.
Approval Gates
Access ControlProtects against: Unsupervised destructive actions
In-memory request queue with configurable timeouts. Dual caching: approval cache (30s) and denial cache (60s) with mutual invalidation. Batch parallel requests join existing pending entries. Graceful shutdown denies all pending.
Memory Trust Partitioning
MemoryProtects against: Memory poisoning via indirect injection
Three trust levels: system (platform-injected, highest), learned (from conversation), external (from tools/APIs/web, lowest). Low-trust sources can't overwrite high-trust memories. Provenance tracking records who stored each entry, from which channel, at which trust level.
Pre-Write Security Scan
MemoryProtects against: Dangerous patterns persisted in memory
Every memory entry passes through a security scan before storage. Blocks injection patterns, encoded payloads, and instruction-like content from being persisted where it could later surface via RAG retrieval and influence agent behavior.
RAG Trust Filtering
MemoryProtects against: Poisoned memories surfacing in retrieval
RAG retrieval excludes external-trust memories by default. Only system and learned memories are returned unless explicitly requested. Prevents low-trust content from web scrapes, API responses, or tool outputs from influencing agent reasoning through semantic search.
Canary Tokens
DetectionProtects against: System prompt extraction
Deterministic per-session HMAC-SHA256 tokens (CTKN_{16 hex chars}) injected into the system prompt. If the token appears in a response, the OutputGuard detects leakage and redacts it. Proves the LLM was tricked into revealing system instructions.
Bearer Token Authentication
TransportProtects against: Unauthorized API access via token guessing
Timing-safe bearer token comparison using crypto.timingSafeEqual. Eliminates timing side channels that could leak token bytes through response latency differences. All API endpoints require authentication by default.
mTLS Authentication
TransportProtects against: Unauthorized client connections
Mutual TLS certificate validation with Common Name (CN) extraction for client identity. Establishes cryptographic client identity at the transport layer before any application logic executes.
Webhook Verification
TransportProtects against: Forged webhook deliveries
HMAC-SHA256/384/512 signature verification on all incoming webhooks. Timestamp freshness checks with a 5-minute replay window prevent captured webhook payloads from being replayed later.
Budget Guard
RuntimeProtects against: Runaway LLM costs
Three-tier token budgets (per-execution, per-hour, per-day) checked before the LLM call, not after. A prompt injection that tries to exhaust your API budget is stopped before it costs anything. Context window guard blocks at 95% utilization.
Circuit Breaker
RuntimeProtects against: Cascading provider failures
Three-state machine (closed/open/halfOpen) per model and provider. Tracks consecutive failures and latency. When open, returns a fallback response immediately instead of propagating the failure. Automatic recovery via half-open probes.
Tool Output Sanitizer
RuntimeProtects against: Indirect injection via tool results
Normalizes NFKC Unicode, strips invisible characters, detects instruction-like patterns in tool output, and truncates oversized results at newline boundaries (50K char default). Catches injection attempts that arrive through tool results rather than user input.
Skill Content Scanner
Supply ChainProtects against: Malicious skills loaded at runtime
Every skill is screened before it can run. 18 rules cover exec injection, exfiltration, and XML breakout, applied at skill-load time. This is the canonical 18 - it belongs to the content scanner.
MCP Malware Screening
Supply ChainProtects against: Known-bad third-party tool servers
MCP packages are checked against the OSV malware database before they ever spawn for the first time. A flagged package never starts.
ESLint Security Bans
Supply ChainProtects against: Insecure code patterns reaching main
Named bans block insecure patterns in CI before merge: no path.join, no process.env, no eval/new Function, no swallowed errors - backed by architecture-as-tests that fail the build on regression.
Action Classifier
Access ControlProtects against: Unsupervised or unrecognized destructive actions
Destructive actions pause for HMAC-signed operator approval via chat buttons. Unknown action types classify as destructive and fail closed - the default is to stop and ask, never to assume an unrecognized action is safe.
Design principles
Why "security-first" isn't a marketing claim.
Single source of truth for all patterns
Every injection pattern, secret format, and dangerous command is defined once in injection-patterns.ts and shared across InputGuard, OutputGuard, Tool Sanitizer, and Memory Validator. No drift between components.
Constant-time comparisons everywhere
Bearer token verification, HMAC webhook validation, and secret comparison all use crypto.timingSafeEqual. No timing side channels.
Result type - no thrown exceptions
Every security operation returns Result<T, E>. Error handling is explicit and type-checked. No catch blocks hiding security failures.
Audit events on every decision
TypedEventBus emits security:warn, secret:accessed, approval:requested/resolved, and tool:executed events. Full audit trail without touching application logic.
Trust boundaries on memory
Memory entries carry provenance (who stored it, from which channel, trust level). External content from web/APIs cannot overwrite system or learned memories. RAG excludes external by default.
Pre-commit budget checks
Token budgets are checked before the LLM call, not after. A prompt injection that tries to exhaust your API budget is stopped before it costs you anything.
Audit every line. Run it on your infrastructure.
Comis is Apache-2.0-licensed, fully open source, and self-hosted. No telemetry, no cloud lock-in, no trust required.