Skip to main content
Comis is a headless, multi-agent daemon that connects autonomous AI agents to chat channels (Discord, Telegram, Slack, WhatsApp, iMessage, Signal, IRC, LINE, Email). It runs on a single trusted host, is mTLS/token-gated, and binds to loopback (127.0.0.1:4766) by default.
The canonical, version-controlled threat model is THREAT_MODEL.md in the repository root — this page mirrors it. Vulnerability reporting and the disclosure SLA live in SECURITY.md.
The defining assumption is load-bearing: the LLM agent is treated as a potential adversary. A model can be steered by prompt injection embedded in any content it reads — an inbound message, a fetched web page, an email, a transcript, an MCP tool result. The architecture is built to contain what a steered agent can do, not merely to authenticate the humans talking to it.

Trust boundaries

What Comis defends against

What Comis does not defend against

Stated plainly, in the spirit of an honest threat model.
  • A compromised host or malicious operator — the operator holds the master key, config, and DB.
  • Full kernel sandboxing on non-Linux hostsbwrap is Linux-only; macOS uses best-effort sandbox-exec; Docker-Desktop/Windows run the exec/terminal tools unsandboxed.
  • The exec tool on a host with no sandbox provider — it currently fails open.
  • A fully adversarial model provider beyond what injection-wrapping and the output guard catch.
  • Brokering OAuth / subscription-token CLIs — only header/query API-key and bearer injection is brokered today.
  • Resource exhaustion / DoS from the operator’s own agents, across a daemon restart (default config) — the bounded-autonomy guards (spawn ceiling, per-root budget, call-rate limit, outward quota) are real hard bounds within a daemon run, and with durability off (the default) their state is in-memory: the counters/meters reset on restart, so they bound a single live run, not cumulative spend across restarts. Opting into autonomy.durability.enabled closes the most dangerous half of this — the run’s checkpoint + consumed budget + outward-send ledger are persisted, so a restart resumes the run (re-seeding budget from the record) and never re-sends a message (the exactly-once outward ledger). The transient rate-limit and quota windows still reset on restart even with durability on (only run state and outward sends are durable); cumulative cross-restart rate limiting remains future work.
  • Confidentiality of data the operator deliberately routes to third-party providers or chat platforms.
  • Physical access, side channels, and compromise of Node.js or the OS itself.

Agent action classification

Every agent-driven action is classified by ActionClassifier. Unknown actions default to destructive (fail-closed), and the registry is locked after bootstrap so a malicious plugin cannot downgrade a classification at runtime. Confirmation gating is covered in Approvals; classified actions are recorded per Audit Logging.

Defense in depth

See Defense in Depth, Skill Sandboxing, Exec Sandbox, and the Credential Broker.

Known limitations

Severity is the impact if the precondition is met. Self-reported benchmark figures (memory accuracy, cache savings) are self-authored, small-N, and LLM-judged — directional, not independent guarantees.
Per-channel coverage of the exactly-once outward guarantee is uneven — by design, not a gap. The restart-resend defense reconciles a crashed-mid-send by asking the channel “did this actually send?”. Discord, Slack, and Matrix can be queried (recent history + bot-author + content-digest match), so a crashed-mid-send there resolves automatically to sent (acknowledge once) or not-sent (replay once) — and, critically, only when the scan actually covers the send window: a truncated or paged-past history read stays unresolved rather than risking a false not-sent. iMessage, IRC, and Email cannot be queried for their sent history — so a crashed-mid-send on those resolves to unresolved, which Comis handles by parking the send and escalating to the operator, never by guessing. That is the safe, surfaced outcome on both sides: never a silent double-send, and never a silent drop. The guarantee is therefore full and automatic on queryable channels and parked-and-surfaced on the ones that cannot answer — the honest representation of where the exactly-once guarantee is complete versus operator-decided.
No-downgrade posture model. The invariant is enforced over the four config-derived confinement dimensions — exec, filesystem, network, uid. For the sub-agent spawn path today the exec sandbox dimension is the active one (exec always is more confined than exec never); filesystem/network/uid are present in the comparator but inert until the deferred terminal path lands. The design’s “broker-only parent → open child” phrasing refers to a runtime sandbox network mode, not an operator config field (no broker-only/open config key exists); in operator config the same escalation is expressed as a more-confined parent (e.g. exec always) spawning a less-confined child (exec never), which is what comparePosture refuses.

Reporting a vulnerability

Do not open public issues for vulnerabilities. Use the private reporting channel and follow the coordinated-disclosure process documented in SECURITY.md.