Skip to main content
Docs navigation

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#

  1. 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 shell pack.
  2. Outbound-only network. Runners dial the control plane over TLS. No inbound ports, no SSH bastion to compromise.
  3. 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.
  4. 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.
  5. 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 allow cassandra.nodetool_drain while requiring approval for cassandra.nodetool_status. Shipped defaults: low and medium allow, high require_approval, critical deny. The runner sees the decision only; it doesn't reason about policy.
  6. 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.
  7. 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.
  8. 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.
  9. 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 5988 Link headers — 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.jsonl with a previous-line hash — that's the host-side forensics copy. emisar audit verify flags 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.
  10. 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.
  11. Local action admission (defense-in-depth). The runner config supports an admission.allow / admission.deny list of glob patterns over action ids (e.g. cassandra.*, *.repair), plus a risk ceiling — admission.max_risk: medium on a read-only box drops every high/critical action 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 as action_blocked_by_admission events for SIEM alerting.
  12. Client-attested dispatch (the strongest cloud-compromise defense). With signing.enforce_signatures on, 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 past max_attestation_age or 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.