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.
Data-flow at a glance
Section titled “Data-flow at a glance” ┌─────────────────────────── 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.
Secrets storage
Section titled “Secrets storage”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, createdchmod 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 local gateway trust boundary
Section titled “The local gateway trust boundary”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
/apirequest. Presented as anAuthorization: Bearerheader, anHttpOnly; SameSite=Strictmuaz_tokencookie (set once on the?token=handshake), or a?token=query param (forEventSource). Comparison is constant-time. The token defaults to$MUAZ_GATEWAY_TOKENor 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 aContent-Security-Policy: frame-ancestors 'none'. - Request-size cap (32 MiB) so an unauthenticated body can’t exhaust memory.
- Read-only mode.
muaz gateway --readonlyrefuses 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).
Tool execution & approval
Section titled “Tool execution & approval”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.
Network egress controls
Section titled “Network egress controls”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: nonerefuses 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.
Plugin & connector trust
Section titled “Plugin & connector trust”- 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
coretier with a valid signature; unsigned/invalid remote entries are downgraded tocommunity. - 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.
Audit log
Section titled “Audit log”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.
event | Fields (besides ts) |
|---|---|
tool_execution | agent, session, tool, args_sha256, decision |
secret_change | action (set/delete), name |
change | category (config/plugin/connector), action, target |
plugin_hook | plugin, phase, command, exit_code |
auth_failure | detail (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_agentcall that launches a subagent is.
Data at rest
Section titled “Data at rest”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.