# Defense-in-Depth Score: Magentic-UI

**Repo:** https://github.com/microsoft/magentic-ui · **Commit:** `d3c9d13c39288257286a66daabf7c5b5fb72ee69` (v0.2.1-37-gd3c9d13) · **Reviewed:** 2026-10-04
**What it is:** Microsoft's human-in-the-loop web and local-file agent (MagenticLite): an orchestrator model plus the Fara browser-use model, served as a local web UI.
**Category:** AI Assistants
**Scored configuration:** `magentic-ui --port 8081` after onboarding, no config.yaml: Quicksand VM sandbox, agent_mode=all (OmniAgent + Fara web surfer), approval_policy=require_approval_untrusted, max_rounds 100/100.
**Agent surface (default):** code execution yes · filesystem write yes · network egress yes · external credentials yes · persistent memory no · untrusted input yes · third party extensions no · sub agents yes · external communication yes

## Score: 4.1 / 10.0 (Minimal)

| # | Criterion | S | C | D | B | Raw | Cap | Score | Confidence |
|---|---|---|---|---|---|---|---|---|---|
| C1 | Identity & least privilege | L2 | L2 | L2 | L1 | 0.45 | — | **0.45** | High |
| C2 | Approval gates | L3 | L0 | L2 | L0 | 0.33 | G2 | **0.25** | High |
| C3 | Tool & action scoping | L1 | L1 | L2 | L1 | 0.30 | — | **0.30** | High |
| C4 | Code-execution isolation | L4 | L4 | L2 | L1 | 0.75 | — | **0.75** | High |
| C5 | Untrusted input blast radius | L1 | L0 | L1 | L0 | 0.12 | C5-WORSTCASE | **0.12** | High |
| C6 | Memory, context & configuration integrity | L2 | L2 | L1 | L2 | 0.45 | — | **0.45** | High |
| C7 | Third-party extensions | L1 | L1 | L2 | L1 | 0.30 | — | **0.30** | High |
| C8 | Secrets & sensitive-data protection | L1 | L2 | L2 | L1 | 0.38 | — | **0.38** | High |
| C9 | Audit & traceability | L3 | L3 | L2 | L1 | 0.60 | — | **0.60** | High |
| C10 | Limits & kill switch | L2 | L2 | L2 | L2 | 0.50 | — | **0.50** | High |


MagenticLite runs its shell and browser inside a Quicksand virtual machine and refuses to start without it, so model-written code never touches your host directly. But the web-browsing agent clicks, types and submits on any site with no approval, and the shell approval classifier is not a strict boundary. The dominant risk is prompt injection from a web page: it can make the agent read your shared folders and send their contents out through the browser, or act in web accounts you've logged into, without asking you.

## Critical gaps
- The web-browsing agent acts with no human approval: delegation is always auto-allowed and Fara's only check-in is a model-declared 'critical point'. (ASI09, ASI02; C2) — [src/magentic_ui/teams/omniagent/_omni_agent.py:1008](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L1008); [src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py:524](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py#L524)
- Worst case under prompt injection: a malicious page can make the agent read shared files and send them out through the ungated browser agent, and take irreversible web actions, with no human involved. (ASI01, LLM01; C5) — [src/magentic_ui/teams/omniagent/_registry.py:132](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_registry.py#L132); [src/magentic_ui/teams/omniagent/_omni_agent.py:1008](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L1008); [src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py:568](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py#L568)

## Criterion details

### C1 Identity & least privilege — 0.45 (high)

MagenticLite does not hand the agent your own credentials: its shell and browser run inside a Quicksand virtual machine that receives none of the host environment, and the folders you can share are checked against a list of credential locations. But the agent has one static set of powers for the whole session: write access to every folder you share, unrestricted internet, and a browser profile whose logins persist across all sessions. There is no per-request authorization in code, so a hijacked agent can act in any web account you have ever signed into through the app.

- **S L2:** Agent authority is the VM: shared folders, network and the persistent browser profile; host env and credentials are not passed in (only explicit extra_env). — [src/magentic_ui/sandbox/_quicksand.py:201](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L201); [src/magentic_ui/sandbox/_quicksand.py:89-93](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L89-L93) (verified)
  - *To reach the next level:* No per-tool or per-capability scoping; read and write share one authority and browser logins are available to every task.
- **C L2:** All built-in tools (bash, file tools, browser) run inside the same VM authority in the default config. — [src/magentic_ui/teams/omniagent/_bash_observers.py:268](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_bash_observers.py#L268); [src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py:412](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py#L412) (verified)
  - *To reach the next level:* No authorization layer evaluates each action; the browser profile and shared folders are reachable from every tool path.
- **D L2:** Quicksand is the default; switching to sandbox: null (host env copied to every command) is a plain config value with no warning. — [src/magentic_ui/magentic_ui_config.py:207](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/magentic_ui_config.py#L207); [src/magentic_ui/sandbox/_local.py:60](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_local.py#L60) (verified)
  - *To reach the next level:* Widening to host execution needs no explicit loudly named flag and is not time-bounded.
- **B L1:** A hijack reaches write access to every shared host folder plus every web account logged into the shared, persistent browser profile. — [src/magentic_ui/sandbox/_quicksand.py:287](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L287); [src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py:11](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py#L11) (verified)
  - *To reach the next level:* Not limited to one system: shared folders and multiple web accounts are writable.
- **Cap:** none

### C2 Approval gates — 0.25 (high)

Shell commands and file writes outside the scratch workspace go through a rule-based classifier, and anything it flags shows you the exact command before it runs, with approve, deny or alternative choices. Two things undercut it. Handing a task to the web-browsing agent is always auto-allowed, and that agent clicks, types and submits forms on any site with no approval at all; its only 'check-in' is when the model itself decides to call something a critical point. And the classifier's allowlist is not a strict boundary, which can let the model step around the shell prompt. A session-wide 'auto-approve all' switch and an auto_approve policy in config.yaml turn the prompts off entirely.

- **S L3:** Per-call approval for flagged bash/file calls shows the exact command or arguments, with risk categories, deny and alternative as first-class outcomes. — [src/magentic_ui/teams/omniagent/_omni_agent.py:966](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L966); [src/magentic_ui/teams/omniagent/_omni_agent.py:560-564](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L560-L564); [src/magentic_ui/teams/omniagent/_command_policy.py:843](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_command_policy.py#L843) (verified)
  - *To reach the next level:* No argument-level policy robust to shell syntax; the classifier is regex/allowlist matching on raw strings.
- **C L0:** The browser agent (delegate_cua then Fara actions) is never gated, and the bash allowlist is not a strict boundary. — [src/magentic_ui/teams/omniagent/_omni_agent.py:1008](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L1008); [src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py:568](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py#L568) (verified)
  - *To reach the next level:* The most powerful external-action path (the browser) is exempt from the gate.
- **D L2:** require_approval_untrusted is the default, but approval_policy: auto_approve in any config.yaml (auto-loaded from the launch directory) disables it silently, and a session 'auto-approve all' accumulates client-side. — [src/magentic_ui/magentic_ui_config.py:154](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/magentic_ui_config.py#L154); [src/magentic_ui/teams/omniagent/_omni_agent.py:1027-1028](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L1027-L1028); [src/magentic_ui/backend/cli.py:155](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/cli.py#L155); [frontend/src/hooks/useWebSocketManager.tsx:484](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/frontend/src/hooks/useWebSocketManager.tsx#L484) (verified)
  - *To reach the next level:* Disabling should need an explicit loudly named flag and persisted allow-rules should be visible and bounded.
- **B L0:** Browser actions (form submissions, bookings, messages in logged-in accounts) and writes to shared host folders are irreversible with no checkpoint or undo. — [src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py:568](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py#L568); [src/magentic_ui/sandbox/_quicksand.py:287](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L287) (verified)
  - *To reach the next level:* No checkpoints for shared folders and no preview/dry-run or rate limit on external browser actions.
- **Cap:** G2 — The bash approval classifier is not strictly enforced at runtime.

### C3 Tool & action scoping — 0.30 (high)

The agent's main tools are general-purpose: a raw shell inside the VM and a browser that can go to any URL. Real argument validation exists only when you pick folders to share (resolved-path containment to your home directory plus a denylist of credential folders such as .ssh and .aws), but enforcement of the denylist is not a complete boundary. File-content handoffs to the browser agent are size-capped. Tool groups can be trimmed with agent_mode, but the default enables everything.

- **S L1:** Core tools are raw shell and arbitrary-URL browsing; validation is limited to mount selection (realpath + home containment + a credential-folder denylist) and size caps. — [src/magentic_ui/sandbox/_quicksand.py:279](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L279); [src/magentic_ui/sandbox/_path_validator.py:201](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_path_validator.py#L201); [src/magentic_ui/teams/omniagent/_registry.py:132](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_registry.py#L132) (verified)
  - *To reach the next level:* No allowlist validation on shell or URL arguments; the mount denylist is not a complete boundary.
- **C L1:** Only mount selection and file-tool path classification validate inputs; bash and browser actions are unvalidated. — [src/magentic_ui/teams/omniagent/_bash_observers.py:268](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_bash_observers.py#L268); [src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py:568](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py#L568) (verified)
  - *To reach the next level:* Most built-in tools do not validate their arguments.
- **D L2:** agent_mode can select tool groups, but the default 'all' enables shell, file writes and browser. — [src/magentic_ui/magentic_ui_config.py:210](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/magentic_ui_config.py#L210) (verified)
  - *To reach the next level:* Default tool set is not read-only.
- **B L1:** A misused tool reaches any internet host and full write inside every shared folder. — [src/magentic_ui/sandbox/_quicksand.py:89-93](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L89-L93); [src/magentic_ui/sandbox/_quicksand.py:287](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L287) (verified)
  - *To reach the next level:* Not scoped to one project with bounded quantities; browser reach is unbounded.
- **Cap:** none

### C4 Code-execution isolation — 0.75 (high)

This is the strongest part of MagenticLite. By default every shell command, file tool and the browser run inside a Quicksand QEMU virtual machine, so model-generated code never runs on your host, and if the VM fails to boot the app refuses to start instead of falling back to the host. The weak points are what lives inside the VM: the folders you share are mounted read-write, network access is fully open, and the browser profile with your saved logins and cookies sits on the same VM disk the shell can read. Setting sandbox to null in config runs everything directly on your host, with your full environment, and nothing warns you.

- **S L4:** Default sandbox is a Quicksand QEMU virtual machine (kernel-separated). — [src/magentic_ui/sandbox/_quicksand.py:89-93](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L89-L93); [src/magentic_ui/sandbox/_quicksand.py:102](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L102); [uv.lock:1854](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/uv.lock#L1854) (verified)
- **C L4:** bash, search, file tools and Chromium all execute in the VM; VM boot failure aborts startup (no host fallback when quicksand is configured). — [src/magentic_ui/teams/omniagent/_bash_observers.py:268](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_bash_observers.py#L268); [src/magentic_ui/backend/web/deps.py:155](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/deps.py#L155); [src/magentic_ui/backend/web/deps.py:170](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/deps.py#L170); [src/magentic_ui/task_team.py:124-129](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/task_team.py#L124-L129) (verified)
- **D L2:** On by default, but sandbox: null is an ordinary config value (config.yaml auto-loaded from the launch dir) with no warning. — [src/magentic_ui/magentic_ui_config.py:207](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/magentic_ui_config.py#L207); [src/magentic_ui/magentic_ui_config.py:85](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/magentic_ui_config.py#L85); [src/magentic_ui/backend/cli.py:155](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/cli.py#L155) (verified)
  - *To reach the next level:* Disabling isolation should require an explicit, loudly named operator flag.
- **B L1:** Inside the VM: shared folders read-write, NetworkMode.FULL egress, and the persistent browser profile (cookies/logins) readable by the shell; CPU/memory are capped. — [src/magentic_ui/sandbox/_quicksand.py:89-93](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L89-L93); [src/magentic_ui/sandbox/_quicksand.py:287](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L287); [src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py:353](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py#L353); [src/magentic_ui/sandbox/_quicksand.py:55](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L55) (verified)
  - *To reach the next level:* Network egress is unrestricted and credentials (browser cookies) are inside the sandbox.
- **Cap:** none

### C5 Untrusted input blast radius — 0.12 (high)

Nothing structural limits what a malicious web page or file can make the agent do. Tool output is wrapped in a tag but otherwise enters the conversation like your own instructions, and reading untrusted content changes nothing about which tools are allowed. A hijacked agent can read your shared files (reads need no approval), pass their contents to the browser agent through the auto-allowed delegation, and have it type them into any site, or take irreversible actions in logged-in web accounts, all without asking you.

- **S L1:** Only delimiters: tool results are wrapped in <tool_response> tags; there is no taint tracking or provenance-based gating. — [src/magentic_ui/teams/omniagent/_omni_agent.py:771](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L771) (verified)
  - *To reach the next level:* Egress and state-changing tools are not disabled or forced through approval after untrusted content is read.
- **C L0:** Web content, files and sub-agent output all enter context as user-role messages with no distinction from the principal. — [src/magentic_ui/teams/omniagent/_omni_agent.py:771](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L771); [src/magentic_ui/teams/omniagent/_omni_agent.py:636](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L636) (verified)
  - *To reach the next level:* Untrusted sources are not distinguished from user instructions.
- **D L1:** The delimiter is always on but there is no configurable defense to tamper with. — [src/magentic_ui/teams/omniagent/_omni_agent.py:771](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L771) (verified)
  - *To reach the next level:* No provenance-based control exists to be on by default.
- **B L0:** A hijack can exfiltrate shared-folder contents via delegate_cua(files=...) to the browser and take irreversible web actions, unattended. — [src/magentic_ui/teams/omniagent/_registry.py:132](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_registry.py#L132); [src/magentic_ui/teams/omniagent/_omni_agent.py:1008](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L1008); [src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py:568](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py#L568) (verified)
  - *To reach the next level:* Exfiltration and irreversible actions should require human approval.
- **Cap:** C5-WORSTCASE — Worst case (B L0): a hijacked agent can leak data and take irreversible actions unattended.

### C6 Memory, context & configuration integrity — 0.45 (high)

MagenticLite has no long-term memory tool and does not auto-load instruction files from your folders. Conversation state for resuming a session is stored on the host outside the VM, so the agent cannot edit it directly. What does persist across sessions is the shared browser profile (cookies, site storage, logins), which every session reuses and merges back, and the long-lived VM disk. Separately, a config.yaml in whatever directory you launch from is silently merged into the settings database and can switch off the sandbox or approvals for all future runs.

- **S L2:** No model-writable memory store; session state lives in a host-only dir; but config.yaml from the launch cwd is loaded with no trust prompt and can change security settings. — [src/magentic_ui/backend/cli.py:155](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/cli.py#L155); [src/magentic_ui/teams/omniagent/_omni_agent.py:196](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L196); searched `rg -n -i 'AGENTS\.md|CLAUDE\.md|\.cursorrules'` in `src` → 0 hits (verified)
  - *To reach the next level:* Security-relevant config from the launch directory should need an explicit trust decision.
- **C L2:** Session state is controlled; the cross-session browser profile and launch-dir config are not. — [src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py:11](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py#L11); [src/magentic_ui/backend/cli.py:155](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/cli.py#L155) (verified)
  - *To reach the next level:* The persistent browser profile and auto-loaded config are uncontrolled.
- **D L1:** Per-session directories exist, but one VM and one master browser profile are shared by every session, enforced only by path conventions. — [src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py:353](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py#L353); [src/magentic_ui/sandbox/_quicksand.py:253](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L253) (verified)
  - *To reach the next level:* No enforced per-session namespace for the VM filesystem or browser profile.
- **B L2:** Poisoned browser state persists across sessions but is not re-injected as instructions; conversation history is session-scoped. — [src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py:11](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py#L11) (verified)
  - *To reach the next level:* Cross-session browser state is not easily inspected or purged from the UI.
- **Cap:** none

### C7 Third-party extensions — 0.30 (high)

MagenticLite loads no plugins or MCP servers. The only third-party code path is the agent installing packages inside the VM with pip, npm or apt, which the shell classifier sends to you for approval and shows the exact command. Packages are not pinned or verified, and they run inside the shared VM with access to your shared folders, the network and browser cookies.

- **S L1:** Model-requested package installs are user-approved but unpinned and unverified. — [src/magentic_ui/teams/omniagent/_command_policy.py:398](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_command_policy.py#L398); searched `rg -n -i mcp` in `src` → 1 hits (Only hit is an unused legacy DB column (datamodel/db.py:94); no MCP client or plugin loader.) (verified)
  - *To reach the next level:* No version pinning or integrity check for installed packages.
- **C L1:** The approval prompt covers recognised installers only, and it does not cover every install path. — [src/magentic_ui/teams/omniagent/_command_policy.py:398](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_command_policy.py#L398) (verified)
  - *To reach the next level:* Not all install paths are covered.
- **D L2:** Nothing third-party enabled by default; installs require an explicit approval showing the command but not what will run. — [src/magentic_ui/teams/omniagent/_command_policy.py:398](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_command_policy.py#L398) (verified)
  - *To reach the next level:* Approval does not show package contents/permissions.
- **B L1:** Installed packages run in the shared VM with the agent's full in-VM authority (shared folders, network, browser profile). — [src/magentic_ui/sandbox/_quicksand.py:89-93](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L89-L93); [src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py:353](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py#L353) (verified)
  - *To reach the next level:* No per-extension sandbox or scrubbed environment.
- **Cap:** none

### C8 Secrets & sensitive-data protection — 0.38 (high)

The LLM API key you enter during onboarding is stored in plain text in the local settings database, masked when shown in the UI, and scrubbed from websocket debug logs. It is never passed into the VM, so the agent's shell cannot read it. There is no telemetry. The main gap is the browser profile: cookies and saved logins for any site you signed into live on the VM disk, where the agent's shell can read them without approval and send them out over the open network.

- **S L1:** API key stored plaintext in the Settings DB; masking only in API responses and a key-name log scrub. — [src/magentic_ui/backend/web/routes/onboarding.py:138](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/routes/onboarding.py#L138); [src/magentic_ui/backend/web/routes/onboarding.py:153](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/routes/onboarding.py#L153); [src/magentic_ui/backend/web/managers/connection.py:73](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/managers/connection.py#L73) (verified)
  - *To reach the next level:* No type-level masking, encryption at rest, or redaction before model-bound messages.
- **C L2:** Protected paths: API responses, websocket debug logs, and VM subprocess environment (no host env); browser cookies and transcripts are not. — [src/magentic_ui/backend/web/routes/onboarding.py:153](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/routes/onboarding.py#L153); [src/magentic_ui/backend/web/managers/connection.py:73](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/managers/connection.py#L73); [src/magentic_ui/sandbox/_quicksand.py:201](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L201) (verified)
  - *To reach the next level:* Model-bound content and in-VM browser credentials are not protected.
- **D L2:** No telemetry SDK; debug logging is lazy and scrubbed; .env loaded only from the app dir. — searched `rg -n -i 'sentry|posthog|opentelemetry|applicationinsights|segment\.io'` in `src` → 1 hits (Only hit is an ad/tracker domain in the browser DNS blocklist (quicksand_browser_manager.py:146); no telemetry SDK.); [src/magentic_ui/backend/web/initialization.py:72](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/initialization.py#L72) (verified)
  - *To reach the next level:* Redaction is partial and stored transcripts are not minimised.
- **B L1:** Long-lived LLM key on host; web session cookies (moderately to highly privileged) reachable by the model in the VM. — [src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py:412](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/tools/playwright/browser/quicksand_browser_manager.py#L412); [src/magentic_ui/backend/web/routes/onboarding.py:138](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/routes/onboarding.py#L138) (verified)
  - *To reach the next level:* Credentials are long-lived and not task-scoped.
- **Cap:** none

### C9 Audit & traceability — 0.60 (high)

Every event the agents produce, including each tool call with its arguments, which agent issued it, and whether it was approved by you, by a session auto-approve, or by the safe-command classifier, is written by the backend to a local database outside the VM, along with your approval decisions. That gives a reasonable record of what happened. It is not tamper-evident, write failures are not checked, and its protection from the VM depends on mount validation that is not a complete boundary.

- **S L3:** Structured DB record per event with tool, args, source agent, approval_status, run/session/user ids and timestamps. — [src/magentic_ui/backend/web/managers/connection.py:666](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/managers/connection.py#L666); [src/magentic_ui/teams/omniagent/_omni_agent.py:593](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L593); [src/magentic_ui/agents/message_schemas.py:300](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/agents/message_schemas.py#L300) (verified)
  - *To reach the next level:* No tamper-evident or off-host storage or standard export.
- **C L3:** Orchestrator and Fara sub-agent tool calls flow through the same stream and are saved; approval responses are saved separately. — [src/magentic_ui/backend/web/managers/connection.py:666](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/managers/connection.py#L666); [src/magentic_ui/backend/web/managers/connection.py:958](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/managers/connection.py#L958) (verified)
  - *To reach the next level:* Configuration changes and credential use are not recorded.
- **D L2:** On by default, written by the host backend outside the VM; but mount validation that keeps the database out of the VM is not a complete boundary. — [src/magentic_ui/backend/web/managers/connection.py:666](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/managers/connection.py#L666) (verified)
  - *To reach the next level:* The record is not protected from every model-reachable path.
- **B L1:** upsert result is not checked, so a failed write is silent and the action proceeds. — [src/magentic_ui/backend/web/managers/connection.py:666](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/managers/connection.py#L666) (verified)
  - *To reach the next level:* Errors should be surfaced and high-risk actions blocked when the record cannot be written.
- **Cap:** none

### C10 Limits & kill switch — 0.50 (high)

The orchestrator pauses after 100 rounds and the browser agent after 100 actions per delegation, asking you whether to continue, and each shell command times out after 60 seconds. Stop cancels the agent loop. There is no wall-clock or spending limit, and every delegation to the browser agent gets a fresh 100-action budget, so a long task can run thousands of browser actions between check-ins.

- **S L2:** Iteration caps with human continuation plus a 60 s per-command timeout; stop is asyncio task cancellation. — [src/magentic_ui/teams/omniagent/_omni_agent.py:300](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/teams/omniagent/_omni_agent.py#L300); [src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py:379](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py#L379); [src/magentic_ui/sandbox/_quicksand.py:188](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L188); [src/magentic_ui/backend/web/managers/connection.py:737](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/managers/connection.py#L737); searched `rg -n -i 'max_cost|budget_usd|total_cost|spend_limit'` in `src` → 0 hits (verified)
  - *To reach the next level:* No wall-clock or token/cost cap and no rate limits on side-effecting tools.
- **C L2:** Top-level loop and per-command timeouts; the browser sub-agent has its own separate budget. — [src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py:379](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/agents/web_surfer/fara/_fara_web_surfer.py#L379); [src/magentic_ui/magentic_ui_config.py:171](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/magentic_ui_config.py#L171) (verified)
  - *To reach the next level:* Sub-agents do not count against the parent budget.
- **D L2:** Sensible defaults (100/100), bounded to 1000 by validation, operator-configurable. — [src/magentic_ui/magentic_ui_config.py:156](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/magentic_ui_config.py#L156); [src/magentic_ui/magentic_ui_config.py:171](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/magentic_ui_config.py#L171) (verified)
  - *To reach the next level:* Delegation resets the browser budget per call.
- **B L2:** Moderate ceilings with human continuation; in-VM commands may outlive the cancelled task (not verified). — [src/magentic_ui/backend/web/managers/connection.py:737](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/backend/web/managers/connection.py#L737); [src/magentic_ui/sandbox/_quicksand.py:188](https://github.com/microsoft/magentic-ui/blob/d3c9d13c39288257286a66daabf7c5b5fb72ee69/src/magentic_ui/sandbox/_quicksand.py#L188) (verified)
  - *To reach the next level:* No spend ceiling and no guaranteed kill of in-flight VM processes.
- **Cap:** none

## Rule-of-Two check
[A] untrusted input: Web pages read by Fara and files in shared folders (_fara_web_surfer.py:568, _registry.py:132) · [B] sensitive data/systems: User-shared host folders mounted read-write and the persistent browser profile with logins (_quicksand.py:287, quicksand_browser_manager.py:412) · [C] state change / egress: Ungated browser actions on any site, full VM network egress (_omni_agent.py:1008, _quicksand.py:93) · Same default session? Yes

## Highest-impact improvements
1. Gate delegate_cua and Fara's consequential browser actions (form submit, typing into fields, navigation to new domains) with a deterministic human approval instead of a model-declared critical point. — C2 C L0→L2, +0.150 before caps (Playbook 5)
2. Harden the bash auto-allow classifier so allowlisted commands cannot run arbitrary subcommands. — C2 S L3→L4, +0.075 before caps (Playbook 5)
3. After untrusted web content enters a session, force approval on delegate_cua with files and on any browser egress carrying workspace data. — C5 S L1→L3, +0.150 before caps (Playbook 1)
4. Restrict VM egress to the browser (or an allowlisting proxy) and move the browser profile out of reach of the agent's shell. — C4 B L1→L2, +0.050 before caps (Playbook 3)
5. Stop auto-loading config.yaml from the launch directory and warn loudly when sandbox: null or approval_policy: auto_approve is set. — C4 D L2→L3, +0.050 before caps (Playbook 3)

## Re-audit log
- No changes.

## Limitations
- Static source review of the pinned commit only; nothing was executed, installed, or probed.
- Quicksand (third-party VM library) internals were not reviewed: VM kernel separation is inferred from the quicksand-qemu dependency and code comments; VM-to-host network reachability was not verified.
- Whether cancelling a run kills in-flight commands inside the VM depends on quicksand's execute() and was not verified.
- Frontend reviewed only for approval auto-accept and HTML rendering; one rendering path was not traced end to end.
- The previous Magentic-UI 0.1 line (magentic-ui-0.1 branch, Docker-based) was not scored.
- No text attempting to steer AI reviewers was found in the repository.
