Safety and permissions
Anymo's agents act on your machine, so capability is gated, audited, and reversible. Tools are safe by default, every call passes one policy engine, and there is no bypass anywhere in the codebase.
Three permission modes, nothing else
Every run executes under exactly one of three modes. There is no fourth mode and no "dangerously skip permissions" flag.
| Mode | Behavior |
|---|---|
auto | Low-risk tools proceed without friction. High- and critical-risk actions (shell, browser, desktop control, MCP tools) always stop and ask. |
manual | Every tool call asks for approval before it runs. |
plan | Read-only. The run can inspect and propose; anything destructive or with side effects is denied. |
Set the mode per run with --permission auto|manual|plan, or from the permission pill in the desktop composer. Goal turns and skill runs obey the same mode.
One gate for every tool
All tool calls flow through the same policy engine, registry, and hooks: built-in tools, skills, MCP tools, browser tools, and desktop control. Denials are recorded as events. There is no second path.
Tools declare their needs
Every tool provides a ToolSpec that declares, up front:
required_permissions: a closed set (FileRead,FileWrite,FileList,NetworkFetch,BrowserControl,ShellExecute,PythonExecute,FinanceRead,MemoryWrite,SkillWrite,ArtifactWrite)risk_level:none,low,medium,high, orcriticaldry_run_supported,approval_required(a call always needs approval regardless of policy defaults), andexecutor_enabled(whether this build will actually run it)
A tool can never quietly need more than it declares.
The policy engine decides every call
Before a tool runs, the policy engine evaluates the request and returns allow, require_approval, or deny. The default policy is:
- Shell and code execution always require approval.
- Destructive file operations (overwrite, delete) require approval.
- Otherwise, gate by risk:
none,low, andmediumare allowed;highandcriticalrequire approval.
Low-risk reads and ordinary in-workspace writes flow without friction, and only genuinely risky actions interrupt you. That keeps approvals meaningful instead of training you to rubber-stamp prompts.
Approvals are explicit, recorded, and time-boundable
When a call needs approval, the run enters awaiting_approval. You grant or deny, and the decision (approval.requested, granted, denied, expired) is appended to the event log. Granted actions proceed; denied actions never execute. Approvals can expire, so a forgotten prompt fails closed rather than blocking forever.
anymo approvals --websocket 127.0.0.1:9300 --token <token>
anymo approve --websocket 127.0.0.1:9300 --token <token> --approval-id <id> \
--decision approve --reason "looks safe"
Workspace containment
All agent file access goes through a confined workspace rooted at an allowlisted directory. It enforces, in order:
- Workspace-relative paths only. Absolute paths and
..traversal are rejected lexically. - Symlink-escape denial. The nearest existing ancestor is canonicalized and must stay under the canonical root, so a symlink cannot point outside.
- Size limits on reads and writes (default 10 MiB) and refusal of binary-as-text.
- Atomic writes. Content is staged to a temp file, then renamed.
Tool outputs and stored values report workspace-relative, redaction-safe paths, never the absolute host path, so machine-specific paths never reach events, logs, the UI, or memory.
Secrets
How keys are held depends on how you run Anymo:
- Desktop app: saved provider keys and connector tokens are sealed at rest, encrypted on disk with a master secret held in the OS keychain, with an encrypted-file fallback.
- Headless CLI: a key is never persisted. The CLI reads
ANYMO_PROVIDER_API_KEYfrom the environment each run.
In both cases, keys never appear in events, logs, artifacts, or the UI. Rendered provider errors redact bearer keys and custom header values before they enter the log.
Shell and code execution
shell.run and python.run exist as declared contracts whose executors are disabled in the current builds. They are registered so the protocol and UI know about them and so skills can reference them, but calling them returns an ExecutorDisabled error. Real process execution is a separate sandbox workstream with its own approval flow. Where process execution does exist today (local actions, the project harness, runtime coordination), it always runs through the guarded process path with an interactive approval.
Everything on the record
Approvals, denials, provider fallbacks, and tool results are events in the durable log. A run can be audited and replayed step by step, and what you were shown is exactly what replays. See Architecture for how the event log works.
The threat model and reporting process live in SECURITY.md.