# Defense-in-Depth Score: DeerFlow

**Repo:** https://github.com/bytedance/deer-flow · **Commit:** `59b051b0fd3147af852411c0ea759bcbd10f09d4` · **Reviewed:** 2026-10-03
**What it is:** Long-horizon SuperAgent harness for research/coding with sandboxes, memory, skills, subagents
**Category:** AI Assistants
**Scored configuration:** Docker deployment (README 'Option 1: Docker (Recommended)') with config.example.yaml / make setup wizard defaults: local sandbox provider with host bash disabled, web search/fetch and file read/write tools, memory on, login auth on, authorization and guardrails off, no MCP servers, plugins, channels or ACP agents configured.
**Agent surface (default):** code execution opt-in · filesystem write yes · network egress yes · external credentials no · persistent memory yes · untrusted input yes · third party extensions opt-in · sub agents opt-in · external communication opt-in

## Score: 4.0 / 10.0 (Minimal)

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


As shipped, DeerFlow gives the model no shell: host bash is off unless an operator enables it, file tools are confined to each user's thread directories, and secrets are kept out of prompts and logs. The main risk is that no tool call ever needs human approval, and web_fetch can send data to any URL, so a malicious web page can make the agent leak the user's uploads, workspace files, or memory without anyone noticing. Enabling host bash removes most of the isolation, so use the AIO container sandbox for code execution; plugins an operator installs run inside the gateway process.

## Critical gaps
- No tool call needs human approval: write_file and web_fetch (arbitrary URL egress) run as soon as the model asks. (ASI09, ASI02; C2) — [backend/packages/harness/deerflow/config/guardrails_config.py:21](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/config/guardrails_config.py#L21); [config.example.yaml:1165](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L1165)
- Operator-configured plugins are imported and run inside the gateway process with all of its credentials and data. (ASI04; C7) — [backend/packages/harness/deerflow/extensions/loader.py:189](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/extensions/loader.py#L189)

## Criterion details

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

DeerFlow's tools do not hold external credentials by default: web search is keyless, page fetches go through Jina, and model provider keys stay inside the gateway process. Everything the agent touches on disk is placed under per-user, per-thread directories derived from the logged-in user, and thread routes check ownership. The fine-grained authorization layer (RBAC per tool, model, and sandbox) exists but ships disabled, so every authenticated user can use every tool. MCP servers an admin adds run with one shared credential for all users, so they are not scoped to the requesting user.

- **S L2:** Built-in tools act only inside per-user, per-thread directories built from the authenticated user id, and hold no external credentials of their own by default. — [backend/packages/harness/deerflow/config/paths.py:426](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/config/paths.py#L426); [config.example.yaml:896](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L896) (verified)
  - *To reach the next level:* No per-tool credential scoping or deterministic per-request authorization check by default; the RBAC tool gate is off.
- **C L2:** Built-in file tools use the per-thread scope, but admin-configured MCP servers carry one shared credential (e.g. GITHUB_TOKEN) for every user and the authorization middleware is only installed when authorization.enabled is true. — [extensions_config.example.json:16](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/extensions_config.example.json#L16); [backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py:445](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py#L445) (verified)
  - *To reach the next level:* Route every tool path, including MCP and plugin tools, through one authorization layer evaluated against the requesting user.
- **D L2:** Authorization ships disabled and the template states every authenticated user can reach all resources; login is required and tools get no admin credentials. — [config.example.yaml:3142-3143](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L3142-L3143); [config.example.yaml:3198-3199](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L3198-L3199) (verified)
  - *To reach the next level:* Ship a least-privilege role policy on by default so non-admin users get a narrowed tool set without manual hardening.
- **B L2:** A hijacked agent can write inside the requesting user's thread directories and fetch arbitrary URLs; provider keys are not reachable through the default tools. — [backend/packages/harness/deerflow/sandbox/tools.py:1063](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/sandbox/tools.py#L1063); [config.example.yaml:1165](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L1165) (verified)
  - *To reach the next level:* Mostly read-only authority with writes limited to non-destructive operations would reach L3.
- **Cap:** none

### C2 Approval gates — 0.05 (high)

There is no human approval step for any tool call in the default configuration. File writes and web fetches run as soon as the model asks for them; the only 'approval' is the model choosing to call ask_clarification, which is a prompt instruction. The optional guardrail middleware is an automated allow/deny policy, not a human gate, and is off by default. Because the default tool set has no shell, email, or chat channel, the actions that slip through are mostly file overwrites inside the user's thread and outbound page fetches.

- **S L0:** No human approval exists for tool calls; asking the user is left to the model via ask_clarification. — [backend/packages/harness/deerflow/agents/interaction_policy.py:54](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/agents/interaction_policy.py#L54); searched `rg -n 'langgraph.types import .*interrupt|HumanInTheLoopMiddleware'` in `backend/packages/harness/deerflow backend/app` → 0 hits (No LangGraph interrupt or HITL middleware outside tests.) (verified)
  - *To reach the next level:* Per-call human approval showing the exact tool arguments for consequential tools.
- **C L0:** The most consequential default tools (write_file, web_fetch) run with no gate. — [config.example.yaml:1295](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L1295); [config.example.yaml:1165](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L1165) (verified)
  - *To reach the next level:* Gate at least the write and egress tools.
- **D L0:** The only pre-execution policy hook (guardrails) is off by default and automated, not human. — [backend/packages/harness/deerflow/config/guardrails_config.py:21](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/config/guardrails_config.py#L21) (verified)
  - *To reach the next level:* Approval on by default.
- **B L1:** Unapproved actions are file overwrites in the thread workspace and outbound fetches; no external messaging or shell by default, but no undo for overwritten files was found. — [backend/packages/harness/deerflow/sandbox/tools.py:2954](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/sandbox/tools.py#L2954); [backend/packages/harness/deerflow/tools/tools.py:154-155](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/tools/tools.py#L154-L155) (verified)
  - *To reach the next level:* Checkpoints or rollback for workspace file changes.
- **Cap:** C2-POWERBYPASS — The most consequential default action paths (write_file, web_fetch) run with no gate at all in the default configuration.

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

The file tools are well bounded: paths must be virtual /mnt/user-data paths, '..' is rejected, and the resolved real path must stay inside the thread's workspace, uploads, or outputs directory. Host bash is removed from the tool list unless an operator opts in. The web_fetch tool is a general fetcher: any URL the model chooses is sent to Jina's reader with no local host allowlist, so it is an open outbound channel. MCP and plugin tools get no shared validation layer.

- **S L2:** File tools enforce resolved-path containment, but web_fetch forwards an arbitrary model-chosen URL to r.jina.ai with no allowlist or validation. — [backend/packages/harness/deerflow/sandbox/tools.py:1077](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/sandbox/tools.py#L1077); [backend/packages/harness/deerflow/sandbox/tools.py:1063](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/sandbox/tools.py#L1063); [backend/packages/harness/deerflow/community/jina_ai/jina_client.py:28](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/community/jina_ai/jina_client.py#L28); [backend/packages/harness/deerflow/community/jina_ai/jina_client.py:50](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/community/jina_ai/jina_client.py#L50) (verified)
  - *To reach the next level:* Host allowlisting or public-address validation on web_fetch, and race-safe file opens.
- **C L2:** Built-in file tools validate; MCP and plugin tools are appended without a shared argument-validation layer. — [backend/packages/harness/deerflow/tools/tools.py:282](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/tools/tools.py#L282); [backend/packages/harness/deerflow/tools/tools.py:323](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/tools/tools.py#L323) (verified)
  - *To reach the next level:* Wrap extension tools in a shared validation/policy layer.
- **D L2:** Default tool set includes web egress and file write (wizard default yes) while host bash is stripped. — [backend/packages/harness/deerflow/tools/tools.py:154-155](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/tools/tools.py#L154-L155); [scripts/wizard/steps/execution.py:43](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/scripts/wizard/steps/execution.py#L43) (verified)
  - *To reach the next level:* Read-only tool set by default with write/egress enabled explicitly.
- **B L2:** Writes are confined to the user's thread directories, with full write inside them; fetches can reach any public host. — [backend/packages/harness/deerflow/sandbox/tools.py:1043](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/sandbox/tools.py#L1043) (verified)
  - *To reach the next level:* Quantity bounds on writes and egress.
- **Cap:** none

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

By default DeerFlow uses its local sandbox provider with host bash switched off, so the model has no code-execution tool unless an operator enables one. If an operator sets allow_host_bash, commands run directly on the host (or inside the gateway container) as the gateway user, guarded only by a best-effort path filter the code itself says is not a security boundary, though secret-looking environment variables are scrubbed. The stronger option is the opt-in AIO Docker sandbox: per-thread containers with seccomp, memory/CPU/PID limits, and only the thread's directories mounted, but no forced non-root user and open network by default. Admin-configured MCP stdio servers always run on the gateway host.

- **default configuration** (default; raw 0.30 → 0.30)
  - **S L1:** When host bash is enabled on the default local provider, commands run as a host subprocess guarded only by absolute-path token filtering. — [backend/packages/harness/deerflow/sandbox/tools.py:1405-1406](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/sandbox/tools.py#L1405-L1406); [backend/packages/harness/deerflow/sandbox/local/local_sandbox.py:522](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/sandbox/local/local_sandbox.py#L522) (verified)
    - *To reach the next level:* OS-level separation (container, dedicated user) for the default provider's exec path.
  - **C L1:** The bash tool passes the path filter, but MCP stdio servers launch on the gateway host outside any sandbox. — [backend/packages/harness/deerflow/sandbox/tools.py:2422](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/sandbox/tools.py#L2422); [backend/packages/harness/deerflow/mcp/client.py:28](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/mcp/client.py#L28) (verified)
    - *To reach the next level:* Run MCP stdio servers and other spawned processes inside the sandbox too.
  - **D L2:** Host execution is refused unless the operator sets the explicit allow_host_bash flag (default false; wizard default no); held at L2 because it cannot exceed the mechanism's strength by more than one level. — [backend/packages/harness/deerflow/sandbox/security.py:45](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/sandbox/security.py#L45); [config.example.yaml:1614](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L1614); [scripts/wizard/steps/execution.py:41](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/scripts/wizard/steps/execution.py#L41) (verified)
    - *To reach the next level:* A stronger default mechanism; the flag itself is already explicit.
  - **B L1:** If host bash is enabled in the Docker deployment it runs as root in the gateway container with full network, every user's data under DEER_FLOW_HOME, and the gateway's own environment (with keys from .env) readable by the same user via /proc despite env scrubbing. — searched `rg -n '^USER'` in `backend/Dockerfile` → 0 hits (No USER directive: the gateway image runs as root.); [docker/docker-compose.yaml:157](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/docker/docker-compose.yaml#L157); [backend/packages/harness/deerflow/sandbox/env_policy.py:117](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/sandbox/env_policy.py#L117) (inferred)
    - *To reach the next level:* Workspace-only filesystem, no reachable secrets, and restricted egress.
- **opt-in AIO Docker sandbox (sandbox.use AioSandboxProvider)** (alt; raw 0.33, cap G1 → 0.33) ← counted
  - **S L2:** Stock per-thread Docker container: seccomp builtin and resource limits, but no forced non-root user or capability drop. — [backend/packages/harness/deerflow/community/aio_sandbox/local_backend.py:1797](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/community/aio_sandbox/local_backend.py#L1797); [backend/packages/harness/deerflow/community/aio_sandbox/local_backend.py:1806](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/community/aio_sandbox/local_backend.py#L1806) (verified)
    - *To reach the next level:* Non-root user, dropped capabilities, no-new-privileges, read-only root.
  - **C L1:** Bash and sandbox file tools run in the container, but MCP stdio servers still run on the gateway host. — [backend/packages/harness/deerflow/mcp/client.py:28](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/mcp/client.py#L28) (verified)
    - *To reach the next level:* Sandbox every model-reachable path including extension processes.
  - **D L0:** Opt-in: the default provider is LocalSandboxProvider. — [config.example.yaml:1610](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L1610) (verified)
    - *To reach the next level:* Container sandbox on by default.
  - **B L2:** Only the thread's workspace/uploads/outputs are mounted read-write, with resource limits, but network mode defaults to open. — [backend/packages/harness/deerflow/community/aio_sandbox/aio_sandbox_provider.py:1099-1101](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/community/aio_sandbox/aio_sandbox_provider.py#L1099-L1101); [backend/packages/harness/deerflow/config/sandbox_config.py:22](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/config/sandbox_config.py#L22); [backend/packages/harness/deerflow/community/aio_sandbox/local_backend.py:1804](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/community/aio_sandbox/local_backend.py#L1804) (verified)
    - *To reach the next level:* Egress off or allowlisted by default.
- **Cap:** G1 — Opt-in mechanism: off in the scored default configuration.

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

DeerFlow treats fetched web content and MCP results as untrusted and neutralizes framework tags (like a forged system reminder) before the model sees them, and it wraps user input in delimiters. This is detection-style hardening: nothing stops a hijacked agent from acting. Uploaded documents and file reads are not neutralized. In the default setup a successful injection from a web page can read the user's uploads, workspace, and memory and send them out through web_fetch to any URL with no human involved; there is no shell, email, or chat channel by default, so irreversible high-impact actions are limited.

- **S L1:** Structural tag neutralization on remote tool results and user input; no capability restriction after untrusted content is read. — [backend/packages/harness/deerflow/agents/middlewares/tool_result_sanitization_middleware.py:57](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/agents/middlewares/tool_result_sanitization_middleware.py#L57); [backend/packages/harness/deerflow/agents/lead_agent/prompt.py:558-559](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/agents/lead_agent/prompt.py#L558-L559) (verified)
  - *To reach the next level:* Disable or gate egress and state-changing tools once untrusted content enters a session.
- **C L2:** Covers web_fetch, web_search, image_search, web_capture and MCP tool results; uploads and file reads are deliberately left untouched. — [backend/packages/harness/deerflow/agents/middlewares/tool_result_sanitization_middleware.py:59](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/agents/middlewares/tool_result_sanitization_middleware.py#L59); [backend/packages/harness/deerflow/agents/middlewares/tool_result_sanitization_middleware.py:16](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/agents/middlewares/tool_result_sanitization_middleware.py#L16) (verified)
  - *To reach the next level:* Cover uploads, file contents, tool descriptions, and sub-agent messages.
- **D L2:** Both sanitizers are installed unconditionally in the base middleware chain; held at L2 by the strength rule. — [backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py:379](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py#L379); [backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py:382](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/agents/middlewares/tool_error_handling_middleware.py#L382) (verified)
  - *To reach the next level:* A stronger mechanism; the default itself is already on.
- **B L1:** Unattended exfiltration of the user's uploads, workspace files, and memory via web_fetch to an arbitrary URL; no shell or messaging by default, so irreversible high-impact actions are not available. — [backend/packages/harness/deerflow/community/jina_ai/jina_client.py:28](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/community/jina_ai/jina_client.py#L28); [config.example.yaml:2206](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L2206) (verified)
  - *To reach the next level:* Require approval for egress after untrusted content is read.
- **Cap:** none

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

Long-term memory is on by default: after each turn a background model call extracts facts into a per-user memory file that is injected into later conversations. Extraction reads only user messages and the agent's final replies (tool results are excluded), injected memory is escaped and framed as user-managed data, stale facts are reviewed for removal, and users can view, edit, and delete facts in the UI. There is no human approval before a fact is saved, so injected content echoed in a final reply can persist and steer later tool use. When custom agents are enabled, the update_agent tool lets the agent rewrite its own persistent persona file and tool-group list without a check.

- **S L2:** Memory writes come only from user and final assistant messages and are re-injected as an escaped user-role <memory> block, but writes have no approval or validation gate. — [backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/message_processing.py:151](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/message_processing.py#L151); [backend/packages/harness/deerflow/agents/lead_agent/prompt.py:769](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/agents/lead_agent/prompt.py#L769) (verified)
  - *To reach the next level:* Gate memory writes (approval or source validation) with expiry.
- **C L1:** The memory store has these controls; the custom-agent SOUL.md and tool_groups written by update_agent have none. — [backend/packages/harness/deerflow/tools/builtins/update_agent_tool.py:100](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/tools/builtins/update_agent_tool.py#L100); [backend/packages/harness/deerflow/tools/builtins/update_agent_tool.py:203](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/tools/builtins/update_agent_tool.py#L203) (verified)
  - *To reach the next level:* Control every persistent path, including agent persona and config files.
- **D L2:** Memory is namespaced per user on disk, but strict_user_scope is false, so a caller without a user id falls back to a legacy shared path. — [backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/paths.py:92-98](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/paths.py#L92-L98); [config.example.yaml:2329](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L2329) (verified)
  - *To reach the next level:* Enforce user scoping on every path (strict_user_scope on by default).
- **B L2:** Poisoned facts persist across the user's sessions and can steer ungated tools, but users can inspect, edit, and delete them in the UI. — [backend/app/gateway/routers/memory.py:423-424](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/app/gateway/routers/memory.py#L423-L424) (verified)
  - *To reach the next level:* Persistence only after human review, with rollback.
- **Cap:** none

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

No third-party extension is enabled by default. Admins can add MCP servers (the API limits stdio launchers to npx/uvx and rejects eval-style flags), operators can list Python plugins in config.yaml, and users can install skill archives that pass a deterministic scan plus a model-based scan. None of these are pinned or integrity-checked; the shipped example launches MCP packages with 'npx -y' at whatever version is latest. Plugins load in-process with the gateway's full authority, and MCP stdio servers run as the gateway user, though only with the environment variables configured for them.

- **S L1:** MCP servers are user-chosen and unpinned ('npx -y' in the shipped example); the command allowlist and skill scans are filters, not verification. — [extensions_config.example.json:12](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/extensions_config.example.json#L12); [backend/app/gateway/routers/mcp.py:42](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/app/gateway/routers/mcp.py#L42) (verified)
  - *To reach the next level:* Pin extension versions and verify integrity.
- **C L1:** Only skill archives get install-time scanning; MCP servers and plugins get none. — [config.example.yaml:2062-2067](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L2062-L2067) (verified)
  - *To reach the next level:* Verification for MCP servers and plugins too.
- **D L2:** Nothing third-party is enabled by default; adding MCP servers needs an admin and non-admins may add only public HTTP endpoints. — [backend/app/gateway/routers/mcp.py:1164](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/app/gateway/routers/mcp.py#L1164); [backend/app/gateway/routers/personal_mcp.py:44](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/app/gateway/routers/personal_mcp.py#L44) (verified)
  - *To reach the next level:* Show the exact package, command and permissions at install; held at L2 by the strength rule.
- **B L0:** Plugins are imported and run in-process in the gateway; MCP stdio servers run as the same (root) user with only their configured env. — [backend/packages/harness/deerflow/extensions/loader.py:189](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/extensions/loader.py#L189); [backend/packages/harness/deerflow/mcp/client.py:34](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/mcp/client.py#L34) (verified)
  - *To reach the next level:* Run extensions out of process with a scrubbed environment and their own scoped credentials.
- **Cap:** none

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

Secret handling is careful. Request-scoped secrets travel out of band and never enter prompts or tool arguments, secret-looking variables are scrubbed from sandbox subprocess environments, URLs and credential headers are redacted in logs by default, managed-model keys are typed as secrets, and config validation errors log only the error type. There is no telemetry, and tracing to LangSmith or Langfuse is opt-in. Keys still live in plaintext in .env and the gateway environment for the life of the process, with no secret manager or encryption at rest.

- **S L2:** Type-level masking, always-on log redaction, and out-of-band request secrets; long-lived keys stored in plaintext .env. — [backend/packages/harness/deerflow/runtime/secret_context.py:4](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/runtime/secret_context.py#L4); [backend/packages/harness/deerflow/config/managed_models.py:40](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/config/managed_models.py#L40); [config.example.yaml:15](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L15) (verified)
  - *To reach the next level:* Secret manager or encryption at rest.
- **C L3:** Logs, sandbox subprocess env, trace payloads, model-bound context and config-error paths are covered. — [backend/packages/harness/deerflow/sandbox/env_policy.py:117](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/sandbox/env_policy.py#L117); [backend/packages/harness/deerflow/tools/tools.py:243](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/tools/tools.py#L243); [backend/packages/harness/deerflow/logging_config.py:514](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/logging_config.py#L514) (verified)
  - *To reach the next level:* Debug and error-handler paths audited end to end; held at L3 by the strength rule.
- **D L3:** No telemetry SDK; tracing opt-in via env flags; URL redaction installed on every logging configuration. — searched `rg -n -i 'posthog|sentry_sdk|mixpanel|amplitude'` in `backend/packages/harness/deerflow backend/app` → 0 hits (No telemetry SDK in the backend.); [backend/packages/harness/deerflow/config/tracing_config.py:153](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/config/tracing_config.py#L153); [backend/packages/harness/deerflow/logging_config.py:514](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/logging_config.py#L514) (verified)
  - *To reach the next level:* Encrypt or minimise stored transcripts by default.
- **B L1:** Long-lived model-provider and service keys sit in the gateway environment from .env; they are not reachable by default tools. — [docker/docker-compose.yaml:157](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/docker/docker-compose.yaml#L157) (verified)
  - *To reach the next level:* Scoped, short-lived, rotatable credentials.
- **Cap:** none

### C9 Audit & traceability — 0.45 (medium)

Every tool call the lead agent makes, with its arguments and result, is stored in the thread's LangGraph checkpoint, which defaults to a SQLite database outside the agent's reachable directories. A richer run journal records caller identity (lead agent vs. sub-agent) and token use, but its default backend is in memory and is lost on restart. Records are not tamper-evident and can be deleted with the thread; there is no OpenTelemetry or SIEM export by default.

- **S L2:** Thread checkpoints persist structured tool calls and results; the attributed run journal defaults to in-memory storage. — [config.example.yaml:2583](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L2583); [backend/packages/harness/deerflow/runtime/journal.py:16](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/runtime/journal.py#L16) (verified)
  - *To reach the next level:* Durable actor attribution and correlation across sub-agents.
- **C L2:** All lead-agent tool calls, including MCP tools, land in the message history; sub-agent internals are attributed only in the in-memory journal. — [backend/packages/harness/deerflow/runtime/journal.py:16](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/runtime/journal.py#L16) (inferred)
  - *To reach the next level:* Durable records of sub-agent calls and of approvals or denials.
- **D L2:** On by default and stored outside the thread directories the file tools can reach, but alterable by the gateway process and deletable with the thread. — [config.example.yaml:2584](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L2584) (verified)
  - *To reach the next level:* Write records through a component the agent process cannot alter.
- **B L1:** The run event journal is in memory by default and lost on restart. — [config.example.yaml:2646](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L2646); [config.example.yaml:2655](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L2655) (verified)
  - *To reach the next level:* Durable per-action records with errors surfaced.
- **Cap:** none

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

Each run is capped at 100 graph steps by default (clients can raise it up to a hard 1,000), loop detection stops repeated tool calls, web fetches time out after 10 seconds, and sub-agents are limited to 6 per run, 3 at once, with their own turn and time limits. The token budget exists but is off by default, and there is no run-level wall-clock limit. Stopping a run cancels its asyncio task; synchronous tool work already in flight finishes.

- **S L2:** Step cap, loop-detection hard limit and per-tool timeouts are enforced in code; token budget off and no run wall-clock. — [config.example.yaml:119](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L119); [config.example.yaml:1428](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L1428); [config.example.yaml:105-106](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L105-L106) (verified)
  - *To reach the next level:* Wall-clock and token caps on by default, plus rate limits on side-effecting tools.
- **C L2:** Top-level loop plus tool timeouts; sub-agents get fresh per-agent budgets (50 turns, 900 s) rather than drawing from the parent's. — [backend/packages/harness/deerflow/subagents/config.py:48-49](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/subagents/config.py#L48-L49); [backend/packages/harness/deerflow/config/subagents_config.py:12](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/config/subagents_config.py#L12) (verified)
  - *To reach the next level:* Sub-agents and background tasks counted against the same run budget.
- **D L2:** Sensible defaults with a server-side ceiling; the model cannot raise them, but delegation starts fresh sub-agent budgets. — [config.example.yaml:124](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/config.example.yaml#L124); [backend/packages/harness/deerflow/config/app_config.py:270](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/config/app_config.py#L270) (verified)
  - *To reach the next level:* Prevent delegation from resetting budgets.
- **B L2:** Moderate step ceiling, no cost ceiling; cancel aborts the run task but in-flight synchronous tool work completes. — [backend/packages/harness/deerflow/runtime/runs/manager.py:1306](https://github.com/bytedance/deer-flow/blob/59b051b0fd3147af852411c0ea759bcbd10f09d4/backend/packages/harness/deerflow/runtime/runs/manager.py#L1306) (verified)
  - *To reach the next level:* Tight time and cost ceilings and cancellation of pending calls.
- **Cap:** none

## Rule-of-Two check
[A] untrusted input: web_fetch/web_search results (backend/packages/harness/deerflow/community/jina_ai/tools.py:66) · [B] sensitive data/systems: user uploads, thread workspace and injected memory (config.example.yaml:2206) · [C] state change / egress: web_fetch to any URL (backend/packages/harness/deerflow/community/jina_ai/jina_client.py:50) and write_file (config.example.yaml:1295) · Same default session? Yes

## Highest-impact improvements
1. Validate web_fetch URLs (public-address check or operator domain allowlist) before handing them to the fetcher. — C3 S L2→L3, +0.075 before caps (Playbook 3)
2. Default run_events to the db/jsonl backend so the attributed journal survives restarts. — C9 B L1→L2, +0.050 before caps (Playbook 1 step 3)
3. Turn strict_user_scope on by default so memory never falls back to a shared path. — C6 D L2→L3, +0.050 before caps (Playbook 2)
4. Require per-call human approval, showing exact arguments, for write and egress tools once untrusted content is in context. — C2 S L0→L3, +0.225 before caps (Playbook 5)
5. Enable the token budget and add a run wall-clock limit by default. — C10 S L2→L3, +0.075 before caps (Playbook 3 step 3)

## Re-audit log
- No changes.

## Limitations
- Static source review of the pinned commit only; nothing was executed, installed, or probed.
- Scored the Docker deployment with the shipped config template and setup-wizard defaults; local 'make dev' runs the gateway as the host user, which would make an enabled host-bash path reach the user's home directory (C4 B would be L0).
- Opt-in features (channels such as GitHub/Slack/Feishu, scheduler, ACP agents, custom agents/agents_api, subagent batches, E2B/BoxLite/Tenki/OpenSandbox providers, TypeSafe guardrails, RBAC authorization) were reviewed only where they affect a default; their own controls were not scored in depth.
- The frontend (Next.js) rendering of model output (e.g. auto-loaded markdown images as an exfiltration channel) was not examined.
- C4 B and C9 C are INFERRED: /proc environment readability by a same-user subprocess and sub-agent record coverage were reasoned from source, not observed.
- No reviewer-injection text was found in the repository's markdown or agent instruction files.
