Approvals and sandbox
The policy layer that governs which operations run without prompting and which paths they can touch.
The desktop client's security model has two layers: a sandbox that constrains the filesystem, and an approval policy that constrains which operations can run unattended.
Sandbox
Every path input on the gateway is filtered by isPathAllowed in src/server/security.ts:
- The allowlist is derived solely from
sandboxBaseDirectoryin settings, expanded bybuildAllowedDirectories()insrc/shared/sandbox-policy.ts. - Paths are canonicalized with
fs.realpathSync.nativeto prevent symlink escapes. - Violations return
HTTP 403 {"error":"directory not allowed"}and emit asandbox_blocked_operationevent plus aSECURITYentry in the activity log.
Hard-denies
Certain paths are always blocked, even inside the sandbox:
~/.ssh~/.gnupg~/.aws~/Library/Keychains/etc/bin/sbin
Approvals
The approval policy uses three pieces of state:
OPERATION_RISK_TIERS— per-operation tier (low,medium,high). Deploy is high, health check is low.defaultApprovalTier— the user's minimum risk tier that requires approval (defaulthigh).alwaysAllowRules[]— scoped rules that auto-approve specific operations.
Auto-approval
shouldAutoApprove() compares the operation's risk tier against defaultApprovalTier. If an always-allow rule matches, the operation is approved without prompt.
Always-allow rules are scoped to { operationId, method, path, scopePath? } with a default 7-day TTL.
Forced approval
The cloud can force an approval by sending x-desktop-force-approval: 1 with x-desktop-approval-reason. This overrides auto-approval.
Decisions
Every approval resolves to one of:
approved— one-time approvaldenied— request rejectedalways_allow— creates an always-allow ruleexpired— approval timed out
Pending approvals are persisted in desktop-approvals. The tray badges the pending count.
Risk tiers
- high – destructive or sensitive (
deploy, writing files, anything that modifies the system beyond the sandbox in practice). - medium – normal loop and git operations (
symphony_loop,git_action,pr_reply,learnings_record). - low – read-only health and status (
health_check,symphony_status). - none – effectively unclassified; always prompts unless auto-approved by your default tier.
Default approval tier
Configurable under Settings → Policies → Default Approval Tier:
- high – auto-approve everything up to and including
high-risk operations. This is the default for personal productivity setups; it is equivalent to "trust the platform". - medium – auto-approve low and medium; prompt on high.
- low – auto-approve low only; prompt on medium and high.
- none – prompt on every operation.
Per-operation overrides
Settings → Policies → Risk Tier Overrides lets you raise or lower the effective tier per operation id. For example, you can keep the global default at high but force deploy to always prompt.
Tiering examples
| Operation | Risk tier |
|---|---|
health_check | low |
git_action (read) | low |
filesystem (read) | low |
symphony_chat | medium |
git_pr (create) | medium |
symphony_loop | medium |
deploy | high |
binary_paths_settings | high |
(Tiers are read from OPERATION_RISK_TIERS at runtime; the list above reflects the defaults.)
Dangerous auto-approve
A dev-only setting (desktop:set-dangerous-auto-approve IPC) bypasses approval evaluation entirely. It is guarded by UI warnings and is not intended for normal use.
Why two layers
The sandbox constrains what an operation can touch; the approval policy constrains when it can run without asking. Together they give you:
- a hard guarantee that nothing outside the sandbox is writable
- a scoped way to auto-approve known safe operations (for example, read-only git on a specific repo)
- a forced-review path for anything above your configured tier
That is enough policy to run an autonomous system safely while still letting humans see and gate risky work.
Automated enforcement testing
The desktop ships with an automated security test suite that runs on every CI build and exercises the sandbox boundary directly:
- Symlink escape attempts — paths that resolve outside the configured sandbox via symlink (verified against
/etcfor CI portability) must return403 directory not allowed. - Hard-deny coverage — every entry in the deny list (
~/.ssh,~/.gnupg,~/.aws,~/Library/Keychains,/etc,/bin,/sbin) is asserted to fail even when explicitly added to the sandbox base directory. - Path canonicalization —
..traversals, mixed-case path components, and trailing slashes all canonicalize to the same allow/deny decision.
The suite is the deterministic backstop behind every release. If a code change weakens path enforcement, the build fails before the change ships.