Reconnecting
Restoring connection…
Reconnecting
Restoring connection…
Docs navigation
Get started
Connect an LLM
Operate
Build packs
Security model
The precise mechanics behind every control — exactly what each layer checks, and what it deliberately doesn't. For the pitch and the trust-boundary picture, start with the security overview; this page is the reference your security team reads next.
The short version: emisar is a curated allowlist with an audit envelope, not a sandbox. Restarting Cassandra still restarts Cassandra — the value is the narrow attack surface and the audit trail, not process isolation.
The trust boundary#
-
Pre-approved actions only.
The runner loads action YAMLs at boot and refuses anything not in the catalog. A model
cannot choose a binary or command program. Some reviewed actions use a fixed shell
program; open-ended values reach it through environment or positional-argv data channels,
while only finite choices and two-sided bounded numbers may render into its text.
Arbitrary shell is isolated in the staging-only, critical-risk, default-denied
shellpack. - Outbound-only network. Runners dial the control plane over TLS. No inbound ports, no SSH bastion to compromise.
-
Bootstrap key + per-runner token. The enrollment key
(
emkey-enroll-…) is a bootstrap secret — single-use by default, or reusable with a use cap for fleet templates. The runner exchanges it for a long-lived per-runner token (rnrtok-…) on first connect. Single-use keys are atomically consumed: two concurrent registrations can never both succeed. - Argument validation on the runner. The runner is the authority on arguments: it validates every arg against the action's declared schema — rejecting anything undeclared, coercing types, and enforcing each arg's min/max — before it executes. The control plane gates the dispatch (policy, scope, pack trust); a misbehaving cloud cannot smuggle an undeclared argument past the runner.
-
Policy.
Two-layer per-account model: a default
decision per risk tier (
low/medium/high/critical), plus ordered per-action overrides that glob-match on action ID. First matching override wins; otherwise the tier default applies. Decisions:allow/require_approval/deny. Higher-risk tiers are forced to be at least as restrictive as lower-risk ones — you can't allowcassandra.nodetool_drainwhile requiring approval forcassandra.nodetool_status. Shipped defaults: low and mediumallow, highrequire_approval, criticaldeny. The runner sees the decision only; it doesn't reason about policy. - Approvals. When policy demands approval, the run is held until someone who can approve decides. By default that can be the requester — self-approval is on so a solo operator or a trial isn't stuck; for destructive actions, turn on four-eyes (two approvers, self-approval off) in the policy. Approvers are emailed; clicking the link lands them on the request page.
-
Output redaction.
20 built-in patterns — bearer/basic auth, JWTs, AWS keys, Google API keys,
GitHub tokens, private-key blocks, and common
password=/secret=/token=assignments — are masked before the chunk leaves the runner. Per-action rules layer on top, and per-rule hit counts are recorded so you can see what was masked. Because it's pattern-based, a secret in a novel shape can still slip through — treat it as defense-in-depth, not a guarantee the control plane never sees a secret. - Limits. Every action has a timeout and stdout/stderr byte ceiling. Cloud opts can lower these but not raise them above the action's declared maximum.
-
Searchable cloud audit + hash-chained runner journal.
Cloud-side, every mutation — dispatch, approval, runner state change,
policy edit, sign-in — appends an account-scoped audit event. Those events
ship as NDJSON over
GET /api/audit— forward-only with keyset cursor pagination and RFC 5988Linkheaders — gated on a read-only audit-export token minted from the audit page. Point any SIEM that speaks HTTP at it; that token reads events but never executes an action. Runner-side, every action attempt writes a JSONL line to/var/log/emisar/events.jsonlwith a previous-line hash — that's the host-side forensics copy.emisar audit verifyflags edits to the local journal, but host root can truncate the tail or re-chain a forgery and still pass it — the off-host cloud audit is the record that can't be tampered with from the host, so cross-reference the two. -
Setuid drop on Linux.
Actions with
user:set drop the child process to that local user's uid and primary gid before exec. A runner shipping under a privileged service account still runs the declared action as the lower-privilege user. -
Local action admission (defense-in-depth).
The
runner config supports an
admission.allow/admission.denylist of glob patterns over action ids (e.g.cassandra.*,*.repair), plus a risk ceiling —admission.max_risk: mediumon a read-only box drops everyhigh/criticalaction whatever the cloud policy allows. Blocked actions are hidden from the catalog this runner advertises to cloud AND refused at execution time, so even a compromised portal cannot push something the host operator did not sanction. Refusals land in the JSONL audit log asaction_blocked_by_admissionevents for SIEM alerting. -
Client-attested dispatch (the strongest cloud-compromise defense).
With
signing.enforce_signatureson, the runner runs a dispatch only if it carries a valid v4 Ed25519 signature over the canonical portal origin, action, immutable pack, exact-args digest, complete generation-bound runner-ref digest, reason, operation id, nonce, and timestamp — from a leaf key vouched for by a certificate the host's trusted certificate authority signed, inside the cert's validity window and a freshness window, with a nonce it hasn't seen. The leaf private key lives only in the operator's local MCP client and the CA private key stays offline; the control plane holds neither, so it can relay a user-signed action but never originate, forge, or replay one. The runner advertises enforcement, and the cloud then disables its own operator/runbook dispatch to that host. Stated plainly: a compromised cloud can still withhold a signed dispatch (this guarantees integrity, not availability); the replay-nonce cache is persisted to disk, so it survives a restart; and a request queued pastmax_attestation_ageor the cert's window is refused as stale. Each call's exact runner-ref set is signed; the cert's scope is a separate, coarser CA-authored group/label ceiling. Display-name prefixes remain discovery metadata while generation suffixes come from durable runner ids, so narrow scope and out-of-band suffix verification are prudent for the highest-trust workflows. See signed dispatch for setup, fleet key distribution, rotation, and revocation.
What emisar is not#
- Not a VM, container, or kernel sandbox. Process isolation is the host's job (use systemd hardening, namespaces, SELinux, gVisor — we recommend all of those).
- Not an EDR. emisar doesn't detect malicious binaries, lateral movement, or host compromise. It flags edits to the local runner journal (host root can still re-chain a forgery — the off-host cloud audit is the tamper-resistant record) and rejects a pack whose on-disk contents no longer match the cloud-pinned trusted hash before execution.
-
Not a replacement for OS-level access control.
The
user:drop only works because the OS already permits it.
Where your data lives#
The runner, its packs, and its local journal live on your hosts. emisar's control plane — the dashboard, the audit log, run history, and the MCP API — is a hosted service on Google Cloud in the United States. Data is encrypted at rest with provider-managed keys and in transit with TLS, and the runner only ever dials out, so nothing reaches back into your hosts. For the full posture — subprocessors, backups, and how to verify a release yourself — see the security overview and the trust page.