# Defense-in-Depth Score: OpenDerisk

**Repo:** https://github.com/derisk-ai/OpenDerisk · **Commit:** `5b0e729e3854c2f0310285bf4901fe8c7866e757` · **Reviewed:** 2026-10-03
**What it is:** AI-native risk intelligence: multi-agent SRE system (SRE/Code/Report/Data agents) for RCA
**Category:** Infrastructure & Ops
**Scored configuration:** install.sh then openderisk-server, i.e. derisk start webserver with the shipped configs/derisk-proxy-aliyun.toml (permissions plugin off, sandbox type local), chatting through the web UI's default v1 agent (ReActMasterAgent 'BAIZE') with default tools.
**Agent surface (default):** code execution yes · filesystem write yes · network egress yes · external credentials yes · persistent memory yes · untrusted input yes · third party extensions yes · sub agents yes · external communication opt-in

## Score: 1.1 / 10.0 (Minimal)

| # | Criterion | S | C | D | B | Raw | Cap | Score | Confidence |
|---|---|---|---|---|---|---|---|---|---|
| C1 | Identity & least privilege | L0 | L0 | L0 | L0 | 0.00 | — | **0.00** | High |
| C2 | Approval gates | L0 | L0 | L0 | L0 | 0.00 | C2-POWERBYPASS | **0.00** | High |
| C3 | Tool & action scoping | L0 | L1 | L0 | L0 | 0.07 | — | **0.07** | High |
| C4 | Code-execution isolation | L0 | L0 | L0 | L0 | 0.00 | — | **0.00** | High |
| C5 | Untrusted input blast radius | L0 | L0 | L0 | L0 | 0.00 | C5-PUBLICTRIGGER | **0.00** | High |
| C6 | Memory, context & configuration integrity | L0 | L0 | L0 | L0 | 0.00 | — | **0.00** | High |
| C7 | Third-party extensions | L0 | L0 | L0 | L0 | 0.00 | C7-RCELOAD | **0.00** | High |
| C8 | Secrets & sensitive-data protection | L1 | L1 | L1 | L0 | 0.20 | — | **0.20** | High |
| C9 | Audit & traceability | L2 | L2 | L2 | L1 | 0.45 | — | **0.45** | High |
| C10 | Limits & kill switch | L2 | L2 | L1 | L1 | 0.40 | — | **0.40** | High |


As shipped, OpenDerisk's default agent runs model-written shell commands directly on the server host, with the server's full environment and API keys, and no tool call ever asks a human. The web server's default network exposure and authentication are not locked down, and anyone who controls a web page or file the agent reads can drive a host shell. Its 'local sandbox' is a working directory, not an isolation boundary, and the approval and authorization modules in the codebase are not wired into the execution path.

## Critical gaps
- No tool call in the default agent waits for approval: the approval branch depends on a flag no caller sets, so the host shell runs ungated. (ASI09, ASI02, T10; C2) — [packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py:319](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py#L319); [packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py:257](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py#L257)
- The default 'local' sandbox runs model-written shell commands as host subprocesses with the server's full environment. (ASI05, T11, LLM05; C4) — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:103-107](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L103-L107); [configs/derisk-proxy-aliyun.toml:104](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/configs/derisk-proxy-aliyun.toml#L104)
- A hijacked agent can both exfiltrate secrets and take irreversible host actions with no human involved. (ASI01, LLM01, T6; C5) — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:103-107](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L103-L107); [packages/derisk-core/src/derisk/agent/tools/builtin/network/__init__.py:87](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/network/__init__.py#L87)
- The model can write to the shared skill directory that every later session loads into its system prompt, giving a one-shot injection cross-user persistence. (ASI06, T1; C6) — [packages/derisk-ext/src/derisk_ext/sandbox/local/file_client.py:67](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/file_client.py#L67); [packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py:2239](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py#L2239)
- The model can install and run arbitrary packages on the host without consent, and skill repos are auto-pulled unpinned on every sandbox start. (ASI04, T17, LLM03; C7) — [packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py:40](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py#L40); [packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py:362](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py#L362)
- Long-lived LLM and object-storage keys are reachable by every model-run subprocess because the shell inherits the server environment. (ASI03, LLM02, T9; C8) — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:103-107](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L103-L107); [configs/derisk-proxy-aliyun.toml:32](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/configs/derisk-proxy-aliyun.toml#L32)

## Criterion details

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

The web server's authentication in the shipped configuration is not locked down. The agent's shell runs as the server's operating-system user and inherits the server's whole environment, including model and storage keys. There is no scoped identity and no per-request authorization between the agent and its tools.

- **S L0:** The agent acts with the server process's ambient authority: shell commands run as the server user with its full environment. — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:103-107](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L103-L107) (verified)
  - *To reach the next level:* No dedicated or scoped identity for the agent; L1 needs at least a dedicated identity separate from the server's own.
- **C L0:** No authorization layer sits between the agent and its tools; the ToolAuthorizationMiddleware exists but is only reachable through AgentToolAdapter, which nothing in the runtime constructs. — [packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py:459](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py#L459); searched `rg -n 'create_tool_adapter_for_agent'` in `packages` → 7 hits (Definition, re-exports in tools/__init__.py and examples in AUTHORIZATION_GUIDE.md only; no runtime caller builds the adapter that applies the authorization middleware.) (verified)
  - *To reach the next level:* No tool path is checked against a scoped identity; L1 needs the main tool path to pass an authorization check.
- **D L0:** The shipped configuration has no least-privilege default, and its authentication setup is not locked down. (verified)
  - *To reach the next level:* Least privilege requires manual hardening; L1 needs a narrower default.
- **B L0:** A hijacked agent holds the server user's whole account: shell, files, network, and the LLM and object-storage keys in the environment and config file. — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:103-107](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L103-L107); [configs/derisk-proxy-aliyun.toml:32](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/configs/derisk-proxy-aliyun.toml#L32); [configs/derisk-proxy-aliyun.toml:99](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/configs/derisk-proxy-aliyun.toml#L99) (verified)
  - *To reach the next level:* Authority reaches the full host account; L1 needs it confined to write access on a limited set of systems.
- **Cap:** none

### C2 Approval gates — 0.00 (high)

In the default agent (the v1 ReAct agent named BAIZE) no tool call ever waits for a human. The tool executor only asks for approval when a require_approval flag is passed, and nothing in the code passes it, so shell commands, file writes, web requests and MCP tools all run immediately. The bash tool is marked as needing permission and a separate authorization middleware exists, but neither is wired into the execution path. The inner sandbox shell tool is explicitly marked as not needing permission.

- **S L0:** No approval occurs on the default path: the executor's approval branch depends on a kwarg that no caller sets. — [packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py:319](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py#L319); searched `rg -n 'require_approval'` in `packages` → 4 hits (Only a config field nobody reads (tools/config.py:96) and the kwarg read in tool_action.py; no caller ever passes require_approval, so the approval branch at tool_action.py:404 never fires.); [packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py:257](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py#L257) (verified)
  - *To reach the next level:* No human approval at all; L1 needs at least a blanket or initial approval.
- **C L0:** The most powerful tool (bash/shell_exec) is exempt along with every other tool, including MCP tools routed through the same ToolAction. — [packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py:404](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py#L404); [packages/derisk-core/src/derisk/agent/tools/builtin/shell/bash.py:51](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/shell/bash.py#L51); [packages/derisk-core/src/derisk/agent/tools/builtin/shell/bash.py:89](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/shell/bash.py#L89) (verified)
  - *To reach the next level:* The shell tool is not gated; L1 needs at least flagged mutating tools gated.
- **D L0:** Approval is effectively off in the default configuration; the permission check helper on the agent defaults to allow and is never called. — [packages/derisk-core/src/derisk/agent/core/base_agent.py:584-585](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/core/base_agent.py#L584-L585); searched `rg -n 'needs_tool_approval'` in `packages` → 2 hits (Only the definition in base_agent.py and its .backup copy; no caller.) (verified)
  - *To reach the next level:* Approval is not on by default; L1 needs approval on by default.
- **B L0:** Unapproved actions include arbitrary host shell commands (deletes, curl to any host) and file overwrites with no checkpoint or undo. — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:103-107](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L103-L107); [packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py:97](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py#L97) (verified)
  - *To reach the next level:* Irreversible host actions with no undo; L1 needs some actions to be reversible.
- **Cap:** C2-POWERBYPASS — The shell tool, the most powerful action path, executes without any approval in the default configuration (tool_action.py:319/404, shell_exec.py:257).

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

The default agent gets file, shell and web tools. The shell tool takes any command string: the sandbox shell tool contains an allowlist of read-only commands, but the call that enforces it is commented out, and the local bash path only blocks a handful of exact strings such as 'rm -rf /'. The web fetch tool only checks that the scheme is http or https, so it can reach internal addresses and cloud metadata endpoints. File paths are mapped into the sandbox directory without a containment check, and several host directories (/mnt, the skills directory, the configured work directory) are passed straight through.

- **S L0:** Raw passthrough: the shell accepts arbitrary command strings because the token validator is commented out, and webfetch accepts any http(s) host. — [packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py:304](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py#L304); [packages/derisk-core/src/derisk/agent/tools/builtin/network/__init__.py:98](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/network/__init__.py#L98) (verified)
  - *To reach the next level:* No allowlist validation in force; L1 needs at least an enforced denylist on the main shell path.
- **C L1:** A few tools check something: webfetch checks the scheme, local-mode bash checks a five-entry substring denylist, and the file client maps relative paths under the sandbox root. — [packages/derisk-core/src/derisk/agent/tools/builtin/shell/bash.py:23](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/shell/bash.py#L23); [packages/derisk-ext/src/derisk_ext/sandbox/local/file_client.py:67](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/file_client.py#L67); [packages/derisk-ext/src/derisk_ext/sandbox/local/file_client.py:82](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/file_client.py#L82) (verified)
  - *To reach the next level:* Most tools do not validate; L2 needs validation on most built-in tools.
- **D L0:** With no per-app binding the agent receives every file-system, shell and network tool by default. — [packages/derisk-core/src/derisk/agent/core/base_agent.py:803](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/core/base_agent.py#L803); [packages/derisk-core/src/derisk/agent/core/base_agent.py:812](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/core/base_agent.py#L812); [packages/derisk-core/src/derisk/agent/core/base_agent.py:794](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/core/base_agent.py#L794) (verified)
  - *To reach the next level:* Everything is enabled by default; L1 needs dangerous tools to be individually disableable in the default profile.
- **B L0:** A misused tool reaches the whole host as the server user and any network destination. — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:103-107](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L103-L107); [packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py:309](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py#L309) (verified)
  - *To reach the next level:* General-purpose tools against the whole machine; L1 needs reach limited at least somewhat.
- **Cap:** none

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

The shipped configuration uses the 'local' sandbox, and its shell client simply starts a host shell process in a working directory, inheriting the server's whole environment. A macOS sandbox-exec profile and resource limits exist in the local runtime, but the shell client does not use them. Python code blocks from the code agent run natively unless the Python docker package is installed, which is not a dependency. Privilege handling during sandbox initialisation on the host is also not locked down.

- **S L0:** No isolation: a same-user host subprocess via asyncio.create_subprocess_shell. — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:103-107](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L103-L107); [packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py:289](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py#L289); [packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py:169-171](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py#L169-L171) (verified)
  - *To reach the next level:* No isolation primitive; L1 needs at least filtering on the exec path.
- **C L0:** Neither the shell tool nor code-block execution goes through any boundary; execute_code falls back to native execution when docker is unavailable. — [packages/derisk-core/src/derisk/util/code_utils.py:280-283](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/util/code_utils.py#L280-L283); searched `rg -n -i 'docker'` in `pyproject.toml packages/derisk-core/pyproject.toml packages/derisk-ext/pyproject.toml packages/derisk-app/pyproject.toml packages/derisk-serve/pyproject.toml packages/derisk-client/pyproject.toml` → 0 hits (The docker Python package is not a declared dependency, so execute_code takes the native path by default.) (verified)
  - *To reach the next level:* The main exec tool is unsandboxed; L1 needs the main exec tool sandboxed.
- **D L0:** The default sandbox type is local, which is host execution; no isolating provider ships in the registered set. — [configs/derisk-proxy-aliyun.toml:104](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/configs/derisk-proxy-aliyun.toml#L104); [packages/derisk-serve/src/derisk_serve/agent/agents/chat/agent_chat.py:281](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-serve/src/derisk_serve/agent/agents/chat/agent_chat.py#L281) (verified)
  - *To reach the next level:* Isolation is not on by default; L1 needs an isolation mechanism enabled by default.
- **B L0:** Host-equivalent: commands run as the server user with its environment (LLM keys), full network and host filesystem. — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:103-107](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L103-L107) (verified)
  - *To reach the next level:* Host-equivalent reach; L1 needs at least the home directory and credentials kept out of reach.
- **Cap:** none

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

The default agent reads untrusted content (web pages, search results, uploaded files, MCP results) into the same context that drives a host shell and an unrestricted web fetch tool, with no provenance marking and no approval step. A hijacked agent can read the server's keys and data and send them anywhere, and can also delete or change files on the host, with no human involved. The HTTP API's exposure in the default configuration is also not locked down.

- **S L0:** Nothing structurally limits a hijacked agent; tool results flow straight back into the loop and every tool stays available. — [packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py:1652](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py#L1652); [packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py:319](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py#L319) (verified)
  - *To reach the next level:* No structural limit; L1 needs at least detection or spotlighting of untrusted content.
- **C L0:** Untrusted sources are not distinguished from the principal's instructions. — [packages/derisk-core/src/derisk/agent/tools/builtin/network/__init__.py:35](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/network/__init__.py#L35); [packages/derisk-core/src/derisk/agent/core/base_agent.py:812](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/core/base_agent.py#L812) (verified)
  - *To reach the next level:* No source handled; L1 needs at least one untrusted source treated differently.
- **D L0:** No control exists to be on by default. — [packages/derisk-core/src/derisk/agent/core/base_agent.py:803](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/core/base_agent.py#L803); [packages/derisk-core/src/derisk/agent/core/base_agent.py:812](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/core/base_agent.py#L812) (verified)
  - *To reach the next level:* Off by default; L1 needs a control enabled by default.
- **B L0:** Leak plus irreversible action, unattended: the shell can read env keys and post them anywhere, and can delete host files; webfetch sends arbitrary headers to any host. — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:103-107](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L103-L107); [packages/derisk-core/src/derisk/agent/tools/builtin/network/__init__.py:87](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/network/__init__.py#L87) (verified)
  - *To reach the next level:* Both exfiltration and irreversible actions are unattended; L1 needs at least one of them to require a human.
- **Cap:** C5-PUBLICTRIGGER — Agent runs can be triggered by parties other than the principal in the default configuration, while the agent holds host shell and the server's credentials.

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

Agent skills live in one shared skill directory that is listed into every agent's system prompt and that the agent's own file and shell tools can write to, because that directory is on the sandbox's pass-through whitelist. A single injection can therefore plant or edit a SKILL.md that every later session and every user of the server will load and follow. Skill repositories in that directory are also git-pulled from their remotes each time a sandbox starts. Vector-store preference memory is off by default because no vector store is configured.

- **S L0:** The model can write to the shared skill directory, which is re-injected into future system prompts as trusted guidance. — [packages/derisk-ext/src/derisk_ext/sandbox/local/file_client.py:67](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/file_client.py#L67); [packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py:2211](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py#L2211); [packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py:2239](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py#L2239) (verified)
  - *To reach the next level:* Writes are not logged or validated; L1 needs at least logging of writes to persisted context.
- **C L0:** No memory or auto-loaded store is controlled; skill files, conversation history and the agent file store are all uncontrolled. — [configs/derisk-proxy-aliyun.toml:110](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/configs/derisk-proxy-aliyun.toml#L110); [packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py:227](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py#L227) (verified)
  - *To reach the next level:* No store is controlled; L1 needs at least one store gated.
- **D L0:** The skill directory is a single host path shared by all users and sandboxes. — [configs/derisk-proxy-aliyun.toml:110](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/configs/derisk-proxy-aliyun.toml#L110) (verified)
  - *To reach the next level:* Shared globally by default; L1 needs per-user isolation of persisted context.
- **B L0:** A poisoned skill persists across sessions and users and directs tool use (shell, web) in every agent that loads it. — [packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py:2238](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py#L2238) (verified)
  - *To reach the next level:* Cross-user persistence that triggers tools; L1 needs persistence limited to one user's sessions.
- **Cap:** none

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

The agent can install and run third-party packages on the host whenever the model decides to, because its shell is unrestricted and needs no approval. Skill repositories are git-pulled from whatever branch they track every time a sandbox starts, without pinning or re-approval, and skill scripts run through the same host shell. MCP servers configured by an operator are called through the same unapproved tool path. A startup routine that would auto-sync MCP configs from the vendor's GitHub repo exists but is never called.

- **S L0:** Model-chosen package installs and unpinned skill repo pulls execute without verification. — [packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py:40](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py#L40); [packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py:97](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py#L97); [packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py:362](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py#L362) (verified)
  - *To reach the next level:* Nothing is pinned or user-chosen at install time; L1 needs at least user-chosen sources.
- **C L0:** No extension type is verified. — [packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py:362](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py#L362); searched `rg -n '_sync_default_mcp_configs'` in `packages` → 1 hits (Only the definition in mcp/serve.py; the MCP default-config sync is dead code, so it is not counted against the default.) (verified)
  - *To reach the next level:* No type verified; L1 needs at least one extension type verified.
- **D L0:** Package installs happen automatically whenever the model runs them, and skill updates are pulled automatically on each sandbox start. — [packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py:227](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/improved_provider.py#L227); [packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py:319](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py#L319) (verified)
  - *To reach the next level:* Automatic installs with no consent; L1 needs installs behind at least a consent prompt.
- **B L0:** Installed packages and skill scripts run as the server user with the full server environment. — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:103-107](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L103-L107) (verified)
  - *To reach the next level:* Runs with all the agent's credentials; L1 needs at least a separate process with limits.
- **Cap:** C7-RCELOAD — By default the model can install and execute arbitrary remote packages on the host without consent, and skill repositories are auto-pulled unpinned on every sandbox start (shell_exec.py:40/97, improved_provider.py:227/362).

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

Model and storage keys come from the TOML config or environment variables and sit in the server process environment, which every agent shell command inherits; the config file holding them is also readable by the shell. Secrets entered through the web UI are encrypted with Fernet, but the master key is stored in the user's home directory next to them. Masking exists only in one configuration API response. No third-party telemetry was found, but every shell command and model output is written to the info-level log unredacted.

- **S L1:** Secrets come from env/TOML; UI secrets use Fernet with an on-disk master key; masking is applied in one API path. — [packages/derisk-core/src/derisk_core/config/encryption.py:52](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk_core/config/encryption.py#L52); [packages/derisk-app/src/derisk_app/openapi/api_v1/config_api.py:1387](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-app/src/derisk_app/openapi/api_v1/config_api.py#L1387) (verified)
  - *To reach the next level:* No type-level masking or log filters on main paths; L2 needs redacted reprs and log filters.
- **C L1:** Only the config API response is masked; logs, traces, model-bound messages and subprocess environments are not. — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:93](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L93); [packages/derisk-core/src/derisk/agent/core/base_agent.py:1767](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/core/base_agent.py#L1767) (verified)
  - *To reach the next level:* One path only; L2 needs logs and transcripts covered.
- **D L1:** No third-party telemetry SDK is present, but unredacted command and model-output logging is on at INFO by default. — searched `rg -n -i 'sentry_sdk|posthog|mixpanel|amplitude' -g '*.py'` in `packages` → 0 hits (No third-party telemetry SDK in the Python packages.); [configs/derisk-proxy-aliyun.toml:4](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/configs/derisk-proxy-aliyun.toml#L4) (verified)
  - *To reach the next level:* Logs are unredacted by default; L2 needs reasonable logging defaults with redaction.
- **B L0:** Long-lived LLM and OSS keys are reachable by every subprocess the model runs, since the shell inherits the server environment and can read the config file. — [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:103-107](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L103-L107); [configs/derisk-proxy-aliyun.toml:32](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/configs/derisk-proxy-aliyun.toml#L32) (verified)
  - *To reach the next level:* Long-lived high-privilege keys reachable by the model; L1 needs keys kept out of model-run subprocesses or scoped.
- **Cap:** none

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

Every agent message, including each tool call's action report, is stored in the application database with sender, receiver, conversation id and timestamps, and spans are written to a local JSONL trace file. There is no approval record because there are no approvals, and actor attribution is weak. Both stores sit on the host where the agent's unrestricted shell can edit or delete them, and trace spans are flushed in batches by a background thread.

- **S L2:** Structured per-message records with action reports and timestamps are persisted to the gpts_messages table. — [packages/derisk-serve/src/derisk_serve/agent/db/gpts_messages_db.py:106](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-serve/src/derisk_serve/agent/db/gpts_messages_db.py#L106); [packages/derisk-serve/src/derisk_serve/agent/agents/derisks_memory.py:167-169](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-serve/src/derisk_serve/agent/agents/derisks_memory.py#L167-L169) (verified)
  - *To reach the next level:* No reliable actor attribution or approver field; L3 needs principal and approver attribution with correlation across sub-agents.
- **C L2:** All tool calls, including MCP tools, go through ToolAction and land in action reports; approvals, config changes and memory writes are not recorded. — [packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py:459](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/actions/tool_action.py#L459) (verified)
  - *To reach the next level:* Approvals/denials and config or skill-directory changes are not recorded; L3 needs those too.
- **D L2:** On by default and stored outside the agent workspace (SQLite under pilot/meta_data, trace under logs/), but the agent's host shell runs as the same user and can alter both. — [configs/derisk-proxy-aliyun.toml:17](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/configs/derisk-proxy-aliyun.toml#L17); [configs/derisk-proxy-aliyun.toml:20](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/configs/derisk-proxy-aliyun.toml#L20) (verified)
  - *To reach the next level:* The agent process can alter the record; L3 needs a writer the model cannot control.
- **B L1:** Trace spans are buffered and flushed in batches by a daemon thread; message persistence is best-effort with no fail-closed behaviour. — [packages/derisk-core/src/derisk/util/tracer/span_storage.py:32-33](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/util/tracer/span_storage.py#L32-L33); [packages/derisk-core/src/derisk/util/tracer/span_storage.py:50](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/util/tracer/span_storage.py#L50) (verified)
  - *To reach the next level:* Records are flushed late; L2 needs per-action flush with surfaced errors.
- **Cap:** none

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

The default agent has an iteration cap of 300 rounds and per-command timeouts, but no wall-clock or token/cost budget. The shell timeout is chosen by the model with no upper bound, and when a timeout fires only the top shell process is killed. Python code blocks that time out are abandoned while the process keeps running. Stopping a chat cancels the asyncio task, which does not kill subprocesses the agent started, and the shell tool's prompt tells the model to background long-running servers.

- **S L2:** Iteration cap plus per-execution timeouts enforced in code; no token or cost cap. — [packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py:146](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py#L146); [packages/derisk-core/src/derisk/agent/core/base_agent.py:962](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/core/base_agent.py#L962); [packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py:133](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-ext/src/derisk_ext/sandbox/local/shell_client.py#L133) (verified)
  - *To reach the next level:* No wall-clock or token/cost cap; L3 needs step, wall-clock and cost caps plus rate limits.
- **C L2:** The top-level loop and individual tool calls are bounded; spawned and background processes are not counted. — [packages/derisk-core/src/derisk/util/code_utils.py:336](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/util/code_utils.py#L336) (verified)
  - *To reach the next level:* Spawned and background processes escape the budget; L3 needs them counted.
- **D L1:** Defaults are very large (300 rounds) and the model sets its own shell timeout with no maximum. — [packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py:286](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/tools/builtin/sandbox/shell_exec.py#L286); [packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py:146](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/agent/expand/react_master_agent/react_master_agent.py#L146) (verified)
  - *To reach the next level:* The model can raise its own limits; L2 needs sensible operator-set defaults the model cannot raise.
- **B L1:** Stopping cancels the asyncio task but leaves subprocesses and backgrounded servers running; timed-out code blocks keep running. — [packages/derisk-serve/src/derisk_serve/agent/agents/chat/agent_chat.py:2356](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-serve/src/derisk_serve/agent/agents/chat/agent_chat.py#L2356); [packages/derisk-core/src/derisk/util/code_utils.py:340](https://github.com/derisk-ai/OpenDerisk/blob/5b0e729e3854c2f0310285bf4901fe8c7866e757/packages/derisk-core/src/derisk/util/code_utils.py#L340) (verified)
  - *To reach the next level:* Stop leaves work running; L2 needs stop to end the loop with moderate ceilings.
- **Cap:** none

## Rule-of-Two check
[A] untrusted input: webfetch/websearch and uploaded files enter the default agent's context (network/__init__.py:35, base_agent.py:812) · [B] sensitive data/systems: server environment and config with LLM/OSS keys, host filesystem (shell_client.py:103-107, derisk-proxy-aliyun.toml:32) · [C] state change / egress: unrestricted host shell and arbitrary-host webfetch with no approval (shell_exec.py:304, tool_action.py:319) · Same default session? Yes

## Highest-impact improvements
1. Pass require_approval (or route every call through ToolAuthorizationMiddleware) for shell, write and network tools in the default agent, and render the exact command for the approver. — C2 S L0→L3, +0.225 before caps (Playbook 5)
2. Harden the web server's default network exposure and authentication. — C1 D L0→L2, +0.100 before caps (Playbook 4)
3. Ship a container sandbox provider as the default and launch shell commands inside it with a scrubbed environment. — C4 S L0→L2, +0.150 before caps (Playbook 3, step 1)
4. Re-enable _validate_tokens in shell_exec and add host/IP allowlisting with internal-address blocking to webfetch. — C3 S L0→L2, +0.150 before caps (Playbook 3)
5. Remove the skill directory from the agent's writable whitelist and require review before skill changes take effect. — C6 S L0→L2, +0.150 before caps (Playbook 2)

## Re-audit log
- No changes.

## Limitations
- Static source review of the pinned commit only; nothing was executed, installed, or probed.
- Scope was the default v1 agent path (api_v1 chat_completions, AgentChat, ReActMasterAgent, ToolAction, unified tools, the local sandbox provider, skills and MCP serve). The alternative core_v2 agent path (/api/v2/chat) has its own tool-name PermissionRuleset and a Docker sandbox module; it is selectable per app but is not the UI default and was not scored.
- The repository contains many design documents describing authorization and sandboxing; ratings follow the code paths actually wired at this commit, not the documents.
- DingTalk/Feishu channels are optional extras and were not reviewed in depth; no sender allowlist was found in them.
- No reviewer-injection text was found (web/AGENTS.md is a frontend architecture guide; no text aimed at AI reviewers).
