Skip to content

Latest commit

 

History

History
279 lines (209 loc) · 10.8 KB

File metadata and controls

279 lines (209 loc) · 10.8 KB

Snotra Security Model

Status: accepted baseline for the security/runtime roadmap
Scope: desktop app, pi-server, tools, MCP, scheduled tasks, platform replies, Goal Loop, and sub-agents

1. Purpose

Snotra is a local desktop agent that can read files, modify a workspace, execute commands, call MCP tools, and send data to external services. This document defines the security rules that every execution surface must share.

This is an application-level policy boundary. It is not an operating-system sandbox: an approved shell command still runs with the host user's privileges. Strong process isolation is a separate runtime option evaluated in roadmap phase 8.

2. Trust boundaries

The protected assets are:

  • files outside user-authorized roots;
  • credentials, environment variables, connector tokens, and MCP secrets;
  • local pi-server APIs and event streams;
  • external accounts and destinations;
  • task, goal, approval, and audit state;
  • the authority assigned to a parent agent.

Untrusted or partially trusted inputs include:

  • model-generated tool calls;
  • prompts and attachments;
  • repository content and repository-owned configuration;
  • installed Skills and extensions;
  • MCP server schemas and tool results;
  • remote platform messages;
  • web content returned by search or browser tools;
  • any web page capable of reaching localhost.

3. Non-negotiable invariants

These rules take precedence over agent configuration and model instructions:

  1. A tool call is evaluated by one shared policy engine before execution.
  2. allow mode does not bypass hardline destructive-command rules, sensitive-host path rules, or authorized-root boundaries.
  3. Unknown tools and tools without security metadata fail closed and require approval.
  4. Writes are allowed only under roots explicitly marked read-write.
  5. A sub-agent can never receive more authority than its parent.
  6. Unattended execution never converts an approval requirement into automatic consent.
  7. Standing approvals bind an exact tool to an exact target and never cover arbitrary shell commands or unrestricted local writes.
  8. The local sidecar requires a per-launch secret for HTTP, SSE, and WebSocket access.
  9. Credentials must not enter prompts, model-visible traces, URLs, or normal logs.
  10. A retried or resumed external side effect must carry an idempotency identity.

4. Permission and interaction are separate dimensions

Snotra keeps its three user-facing permission modes:

Mode Read Local write Shell External/MCP side effect
allow Allow Allow inside writable roots Allow unless hardline-blocked Allow unless a stricter tool rule applies
ask Allow Ask Ask Ask by default
deny Deny tool execution Deny Deny Deny

Tool-specific rules may only keep or reduce the authority granted by the mode. They must not bypass the non-negotiable invariants.

Interaction mode is independent:

Interaction Approval/question handling
attended Show an inline request and wait for the user
unattended Persist the request in the Inbox, park the run, and resume after a decision

Changing interaction mode does not change the permission mode.

For compatibility, existing saved agents retain their explicit mode. New agents should default to ask once the migration is implemented.

5. Tool security metadata

Every built-in, extension, Skill-provided, and MCP tool must resolve to metadata with this logical shape:

type ToolRisk = 'read' | 'write-local' | 'exec' | 'external'

interface ToolSecurityMetadata {
  risk: ToolRisk
  pathArguments?: string[]
  targetArgument?: string
  approvalDefault: 'allow' | 'ask' | 'deny'
  parallelSafe?: boolean
  source: 'builtin' | 'extension' | 'skill' | 'mcp'
}

Name-based inference is a compatibility fallback for vetted built-ins only. It is not an authority source for third-party or previously unknown tools.

Base risk rules:

  • read: no durable local or external side effect;
  • write-local: changes files or local application state;
  • exec: starts a command, process, package manager, or interpreter;
  • external: sends, creates, deletes, or mutates data outside the machine.

MCP tools default to external plus ask until the user records a narrower local override. A remote server's own description cannot lower its risk.

6. Policy evaluation order

The policy engine evaluates a call in this order and stops on the first hard denial:

  1. Validate the tool exists and has valid security metadata.
  2. Apply hardline command and sensitive-host-path blocks.
  3. Resolve all declared path arguments canonically, including existing symlink ancestors, and enforce root access.
  4. Intersect parent and child authority for delegated calls.
  5. Apply global mode (allow, ask, or deny).
  6. Apply stricter tool/category rules.
  7. Check session-scoped approvals.
  8. Check task-scoped exact-target standing approvals.
  9. Return allow, deny, or approval-required with a machine-readable reason.

The decision object must include the effective risk, matched rule, resolved targets, and whether a human decision is required. This information feeds both UI and audit.

7. Workspace roots

The target model is:

interface WorkspaceRoot {
  id: string
  path: string
  access: 'read' | 'read-write'
  primary: boolean
  source: 'workspace' | 'user-grant' | 'task'
}

Rules:

  • exactly one primary root exists for a workspace-bound session;
  • relative paths resolve against the primary root;
  • reads may use any authorized root;
  • writes require a read-write root;
  • roots are canonicalized and compared case-insensitively on Windows;
  • a missing leaf is canonicalized through its nearest existing ancestor;
  • an agent requests additional access through a dedicated directory request; it cannot add roots directly;
  • changing roots updates the running session immediately and is audited.

Shell approval is not proof of path isolation. The shell remains a high-risk host capability until an OS-level sandbox exists.

8. Delegation

The effective child policy is the intersection of:

  • the parent mode and tool rules;
  • the child definition's requested mode and tool list;
  • the parent's authorized roots;
  • the execution surface's interaction mode.

A child may remove tools, request read-only roots, or switch from allow to ask/deny. It may not add tools, writable roots, secrets, or standing approvals that the parent does not possess.

Explorer is a fixed profile with read/search/git-query tools only, no shell, no writes, no external sends, and no recursive delegation.

9. Attended, unattended, and durable approval

An approval record contains:

  • request ID and tool call ID;
  • session, task/goal, agent, and parent IDs;
  • redacted arguments and resolved target;
  • risk, reason, and matched policy rule;
  • creation/expiry timestamps;
  • decision and deciding user;
  • idempotency key and resume checkpoint.

Unattended runs transition:

running -> waiting_for_approval -> resuming -> completed | failed
                              \-> denied

No tool is executed before approval. On restart, Snotra resumes from the unanswered tool call and must not repeat calls already recorded as completed.

Standing approvals are owned by one task or automation and have the form tool -> exact target. They are eligible only for external side effects with a declared target argument. Shell and local-write tools are never eligible.

10. Local sidecar boundary

Binding to 127.0.0.1 is necessary but insufficient because an untrusted web page can attempt to reach localhost.

The desktop shell must:

  • generate a high-entropy token for every sidecar launch;
  • pass it through the child environment, not command-line arguments;
  • inject it into the trusted webview before application code starts;
  • never persist or log it.

The sidecar must:

  • require constant-time token verification on HTTP, SSE, and WebSocket traffic;
  • allow only trusted Tauri and development origins;
  • permit unauthenticated preflight and a minimal health response only where needed;
  • limit body/frame sizes and request rates;
  • bind only to loopback.

11. Secrets and child processes

Snotra retains its sanitized child environment and Windows Job Object behavior. Only an explicit environment allowlist plus per-server MCP configuration reaches child processes. API keys, tokens, NODE_OPTIONS, NODE_PATH, and unrelated parent variables are excluded.

Secret values are stored through an OS-protected secret interface. Plain configuration may reference a secret, but must not contain its resolved value in model-visible state.

12. Audit requirements

The durable audit lifecycle is:

proposed -> policy_decided -> approval_requested -> approval_resolved
         -> started -> completed | failed | interrupted

Audit events identify the session, agent, execution surface, tool, risk, matched rule, resolved resource, status, and standing approval. Values whose keys or semantics imply tokens, passwords, authorization headers, cookies, message bodies, or typed browser input are redacted before persistence.

13. Required contract tests

Each execution surface (chat, scheduled task, Goal Loop, platform reply, sub-agent, and MCP) must run against the same matrix:

  1. allow cannot escape roots or bypass hardline blocks.
  2. ask auto-allows reads and requests approval for writes, shell, and external calls.
  3. deny executes no model-proposed tool.
  4. Unknown/unannotated tools require approval.
  5. Read-only roots reject writes without offering an override.
  6. A child cannot exceed parent mode, roots, or tool set.
  7. Unattended approval parks and survives restart.
  8. Resume executes an approved call once.
  9. Standing approval matches only its exact target.
  10. Unauthorized browser-origin and missing-token sidecar requests fail.
  11. Audit output contains no configured secret.
  12. Closing the desktop process terminates its managed process tree.

14. Current gaps and roadmap mapping

The baseline review identified these implementation gaps:

  • pi-server currently allows wildcard CORS and has no per-launch API token: phase 1.
  • accessiblePaths does not distinguish read-only and writable roots: phase 2.
  • third-party tool risk still relies on name/category inference: phase 3.
  • non-interactive approvals are blocked rather than parked: phase 4.
  • tool policy and execution do not yet share a durable audit store: phase 5.
  • remote MCP uses layered config, DPAPI-backed secret references, tool filters, and OAuth 2.1/PKCE: phase 6 complete.
  • delegation derives a formal least-authority envelope and includes a fixed Explorer contract: phase 7 complete.
  • approved shell commands are not OS-isolated: phase 8.

Security changes should be implemented in this order so later features depend on one policy decision model rather than adding surface-specific exceptions.