Skip to content

Security

muaz is designed to be run locally, by one person, on their own machine. There is no hosted muaz service: your prompts, files, sessions, and secrets never leave your computer except to reach the LLM providers and MCP servers you configure. This page is the reference to hand a security team evaluating muaz for use inside a regulated organization.

┌─────────────────────────── your machine ───────────────────────────┐
│ │
you ──┼─▶ browser UI ──▶ local gateway (127.0.0.1) ──▶ muaz-core ──┐ │
│ or REPL (token-gated) │ │
│ ▼ │
│ ┌──────── tools ───────────────────┐ │
│ ~/.muaz/ ◀──────────┤ files · shell · web · MCP servers │ │
│ (config, sessions, └───────────────────────────────────┘ │
│ secrets, memory, audit) │ │ │
└────────────────────────────────────┼────────────────┼───────────────┘
▼ ▼
LLM provider API MCP servers you added
(Anthropic/OpenAI/…) (stdio child / HTTPS)

Outbound network happens only to (1) the LLM provider bound to the agent and (2) the web tools / MCP servers you explicitly enable. Both are governed by the controls below.

Provider and plugin API keys are never written into config files. Config references a key by name (api_key: ${ANTHROPIC_API_KEY}); the value is stored via core/src/secrets.rs:

  • OS keychain first — Keychain (macOS), Credential Manager (Windows), or Secret Service (Linux).
  • Fallback file~/.muaz/secrets.json, created chmod 600 (owner-only), used only when no keychain is available.
  • Plugin secrets live under a namespaced key (plugin:<id>:<key>), same backends.

Secret values are only ever read server-side to construct a provider or MCP transport — they are never returned over the gateway HTTP API. ${VAR} resolves store-first, then process environment. Setting or deleting a secret is recorded in the audit log by name only.

The gateway serves the browser UI and exposes the full agent runtime — including shell execution — over HTTP, so it is gated:

  • Loopback by default. It binds 127.0.0.1; exposing it to the network (--host 0.0.0.0) is an explicit opt-in that prints a warning and still requires the token.
  • Token on every /api request. Presented as an Authorization: Bearer header, an HttpOnly; SameSite=Strict muaz_token cookie (set once on the ?token= handshake), or a ?token= query param (for EventSource). Comparison is constant-time. The token defaults to $MUAZ_GATEWAY_TOKEN or a freshly generated secret persisted at ~/.muaz/gateway-token (chmod 600).
  • Same-origin only. No CORS is configured, so no other web page can read the (cookie-authenticated) API.
  • Security headers on every response: X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: no-referrer, and a Content-Security-Policy: frame-ancestors 'none'.
  • Request-size cap (32 MiB) so an unauthenticated body can’t exhaust memory.
  • Read-only mode. muaz gateway --readonly refuses every state-changing request (POST/PUT/DELETE/PATCH → 403, audited) — for auditors and demos who should browse config, sessions, and the audit log without being able to run agents or mutate anything.
  • Failed auth attempts are audited (auth_failure, path only — never the token).

Every tool call passes through a per-tool access policy — off (hidden), ask (prompt each call), or auto (run freely) — resolved most-specific-first from the agent and global config. File-mutating tools (edit_file/write_file/patch_file) show a diff preview at the approval prompt, so you approve the exact change rather than opaque JSON. File tools are confined to the agent’s workspace_root; ~/.muaz and the home muaz directory are always protected from writes.

Two independent locks, both off by default:

  • Allowlist-only egress (web_tools.allowlist_only, global or per-agent): when on, the web tools may fetch only URLs matching the allowlist — and an empty allowlist blocks all web access.
  • network: none (per agent): a hard local egress lock that strips the built-in web tools and refuses to attach HTTP MCP servers for that agent.

Honest limitation. A stdio MCP server is a child process muaz launches; muaz cannot prevent that process from opening its own network sockets. network: none refuses HTTP MCP and the web tools, but stdio MCP children are outside that boundary. That risk is mitigated by only installing trusted, signed plugins/connectors (see below) and by the OS-level sandboxing you already run muaz under — not by muaz enforcing egress on child processes.

  • Signing. Plugins from the remote registry are verified with minisign against a publisher key embedded in the binary: a valid signature marks the install verified, unsigned installs are community (warned), and a present-but-invalid signature is a hard tamper error. Connector-registry entries may only present as the muaz-verified core tier with a valid signature; unsigned/invalid remote entries are downgraded to community.
  • Declared permissions. A plugin’s manifest declares the MCP servers, network use, and built-in tools it wants; these are shown at the trust prompt.
  • Trusted-only capabilities. A plugin’s lifecycle hooks and command-backed custom tools run only after you explicitly trust it; hook commands are shown verbatim at trust time and every run is audited.

An append-only JSON-Lines log at ~/.muaz/audit/audit.jsonl (chmod 600) records security-relevant events. It records what happened, never secret values — tool arguments are stored as a SHA-256 hash, and secret events carry only the name.

eventFields (besides ts)
tool_executionagent, session, tool, args_sha256, decision
secret_changeaction (set/delete), name
changecategory (config/plugin/connector), action, target
plugin_hookplugin, phase, command, exit_code
auth_failuredetail (method + path + reason)

Inspect it with muaz audit tail [--count N] [--json] or dump the whole file with muaz audit export. Each line is one self-contained JSON object with an RFC-3339 UTC ts, so it ingests cleanly into a SIEM.

$ muaz audit tail --json
{"event":"secret_change","action":"set","name":"ANTHROPIC_API_KEY","ts":"2026-07-05T18:22:04+00:00"}
{"event":"tool_execution","agent":"@local/coding","tool":"built_in__shell","args_sha256":"9f2c…","decision":"approved","ts":"2026-07-05T18:22:31+00:00"}

Pipeline and subagent (non-interactive, policy-only) tool calls are not individually logged; the interactive spawn_agent call that launches a subagent is.

Everything muaz stores lives under ~/.muaz/, created owner-only where it holds sensitive data: config.yaml, models.yaml, mcp.json, secrets.json, the gateway-token, per-session transcripts under sessions/, per-agent notebooks under memory/, and audit/audit.jsonl are all written chmod 600. muaz never transmits this directory anywhere.