Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions libs/code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@ The fastest way to start using Deep Agents. `deepagents-code` is a pre-built cod
- **Headless mode** — run non-interactively for scripting and CI
- **Human-in-the-loop** — approve or reject tool calls before execution

## 🔗 Open from the browser

Register `dcode` as the handler for `dcode://` links, and a page can offer to open a project or resume a thread in dcode:

```bash
dcode url install # register the handler (per-user, no sudo)
dcode url status # see what your system does with dcode:// links
dcode url uninstall # give the scheme back
```

```text
dcode://open?dir=/path/to/project
dcode://open?dir=~/proj&agent=research
dcode://open?dir=~/proj&thread=<thread-id>
dcode://open?dir=~/proj&prompt=review%20the%20diff
```

Two gates stand between a link and a session. Your browser asks whether to hand the link to dcode; then dcode opens a terminal, shows the whole request — directory, agent, thread, and the prompt text in full — and waits for you to approve it, with "Cancel" preselected. Nothing is skippable: a link cannot change the session's approval mode, model, or sandbox, and unknown parameters are refused rather than ignored.

## 🔒 Security model

By default, `dcode` trusts the directory you run it in. Human-in-the-loop approval gates model-requested tool calls, but project artifacts are read before any approval prompt.
Expand Down
16 changes: 16 additions & 0 deletions libs/code/THREAT_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
| TB12 | Goal/Rubric State → Model Context | Persisted user- and agent-controlled goal state becomes a synthetic `HumanMessage` in a primary-model request | State projection, lifecycle filtering, notice fingerprinting, raw-character limits, HTML escaping of boundary tags | Natural-language instructions, sensitivity, post-escape size, and provider-specific byte/token budgets |
| TB13 | Managed Config → Runtime | A fixed administrator-deployed TOML file overrides CLI, environment, and user preferences | Fixed non-redirectable path; the CLI never writes the file; fail-closed startup for every command except diagnostics; typed resolution; model-policy checks before credentials/imports/construction; diagnostics remain available | Filesystem ownership/mode and privileged deployment are outside the CLI; a host administrator can weaken or strengthen policy |
| TB14 | Launch Env → User Trust Root | Inherited `DEEPAGENTS_HOME` selects the profile whose config, credentials, and user MCP file are trusted | Captured and normalized once before dotenv loading; absolute/`~/` validation; denied from every dotenv layer; propagated unchanged to the server | A user can deliberately select a profile inside or above a checkout; only the exact profile `.mcp.json` receives user provenance |
| TB15 | Browser / OS URL Dispatch → dcode Launch | Where a `dcode://` link published by a web page becomes a local dcode launch, after the desktop hands it to the registered handler | Explicit opt-in registration (`dcode url install`, never on package install); one action and a closed parameter set (`url_scheme.request`); refusal of unknown, repeated, or session-altering parameters; refusal of control characters and deceptive Unicode rather than stripping; UUID-only thread ids; existing-absolute-directory-only paths; an unconditional, cancel-first in-terminal confirmation showing the whole request (`url_scheme.handler`); a launch argv carrying no approval, model, or sandbox flags | Link content — any page, message, or email can emit one; whether the browser still prompts (browsers offer to remember the answer); which application the desktop binds to the scheme |

### Boundary Details

Expand Down Expand Up @@ -390,10 +391,16 @@
| DF27 | C18 Server Offload Boundary | C8 Sessions | Checkpoint message read; summarization event and additive cost update (never a messages write) | DC2 | None | In-process LangGraph SDK |
| DF28 | User / Host FS | C19 Goal/Rubric State Notice | Goal objective, criteria, and status notes; `/rubric file` content | DC2 | TB1, TB12 | TUI command + local file read + checkpoint update |
| DF29 | C19 Goal/Rubric State Notice | External LLM | Synthetic user-role message containing actionable objective, active criteria, and status note | DC2 | TB12, TB7 | LangChain model request over configured provider transport |
| DF30 | External web page | C2 TUI (via `url_scheme.handler`) | `dcode://open` link: target directory, agent name, thread id, first-message prompt text | — | TB15, TB1 | OS URL dispatch → registered handler argv → user approval → `execv` |
| DF30 | Administrator | C9 Config | Managed TOML policy | DC1 | TB13 | Fixed local file read |

### Flow Details

#### TB15: Browser / OS URL Dispatch → dcode Launch

- **Inside**: `url_scheme.registration` claims the scheme only when the user runs `dcode url install`; nothing on the package-install path registers a handler. `url_scheme.request.parse_open_url` admits one action (`open`) and a closed parameter set (`dir`, `thread`, `agent`, `prompt`), refusing unknown or repeated keys, non-UUID thread ids (which also keeps the `-r __MOST_RECENT__` sentinel unreachable), non-absolute or non-existent directories, and prompt text carrying control characters or deceptive Unicode. `url_scheme.handler._confirm` prints the resolved directory, agent, thread, and full prompt and requires an explicit approval with the cancel row preselected; it fails closed on a non-interactive terminal, an interrupt, or an unavailable picker. `url_scheme.handler._launch` builds the argv from validated fields only — `-a`, `-r`, `-m` — so no approval, model, or sandbox flag can originate in a link. Per-platform command construction avoids shell interpolation: the desktop entry's `%u` and the Windows `"%1"` expand into an argument vector, and the macOS applet quotes the dcode path with `shlex.quote` at build time and the link with AppleScript's `quoted form of` at dispatch time.
- **Outside**: Link content — any page, message, or email can emit a `dcode://` link. Whether the browser prompts at all, since browsers offer to remember the answer. Which application the desktop ultimately binds to the scheme. What the opened project's files then do, which is TB5/TB4/TB11's subject: opening a directory does not trust it, and project hooks, MCP servers, and extensions still face their own trust prompts.

#### DF8/DF9: External Web Content → Agent Context

- **Data**: Arbitrary HTML/JSON from the internet, converted to markdown by `markdownify`. Can be megabytes.
Expand Down Expand Up @@ -458,6 +465,7 @@
| T15 | DF28, DF29 | DC2 | Stored prompt injection through a goal, rubric, or status note influences later primary-model tool requests | TB12 | Medium | Likely | `goal_state_notice.build_goal_state_notice`, `goal_tools.GoalToolsMiddleware._request_with_goal_notice` |
| T16 | DF28, DF29 | DC2 | Sensitive local-file content, up to the 12,000-character rubric limit, is automatically persisted and transmitted to the configured model provider as rubric criteria | TB12 | Medium | Verified | `app.DeepAgentsApp._set_rubric_from_file`, `goal_state_notice.build_goal_state_notice` |
| T17 | DF28, DF29 | DC2 | Character-bounded goal/rubric/status-note text can still exceed provider context budgets after escaping or tokenization | TB12 | Medium | Verified | `goal_state_limits`, `goal_state_notice.build_goal_state_notice`, `goal_tools.GoalToolsMiddleware._request_with_goal_notice` |
| T18 | DF30 | — | A web page emits a `dcode://open` link that starts a session in an attacker-chosen directory, or submits attacker-written prompt text as the session's first message | TB15 | Medium | Verified | `url_scheme.request.parse_open_url`, `url_scheme.handler._confirm`, `url_scheme.handler._launch` |

### Threat Details

Expand Down Expand Up @@ -485,6 +493,12 @@
- **Description**: Direct, file-loaded, generated, and tool-authored goal-state paths enforce raw-character limits before persistence or notice construction. HTML escaping happens afterward and can expand the rendered notice (for example, `&` becomes `&amp;`), while provider tokenization and available context budgets vary. The middleware restores or re-pins the current notice after compaction. A valid near-limit notice therefore remains recurring model-request overhead. This increases spend. It can also contribute to a provider context-limit failure.
- **Preconditions**: (1) A user, file, or model-supplied status note produces a valid near-limit notice; (2) its escaped or tokenized representation is large relative to the configured provider's available context; (3) the corresponding goal or rubric remains model-visible.

#### T18: Attacker-Chosen Session Target or First Message via a `dcode://` Link

- **Flow**: DF30 (web page link → OS URL dispatch → registered handler → user approval → session launch)
- **Description**: With the handler registered, any page the user visits can emit `dcode://open?...`. Two consequences are worth separating. Opening a session in an attacker-named directory brings that directory's project artifacts into a launch, which is the same exposure as running `dcode` there by hand (see TB5, TB4, TB11) rather than a new one. Submitting a `prompt` is the sharper case: the text becomes the session's first message, so it reaches the model as though the user had typed it, and the model may request tool calls on the strength of it. The browser's own prompt does not cover either case, because it names an application rather than a request and browsers offer to remember the answer.
- **Preconditions**: (1) The user has registered the handler with `dcode url install`; (2) the user follows an attacker-supplied link and lets the browser hand it to dcode; (3) the user approves dcode's own confirmation, which shows the resolved directory and the entire prompt text with the cancel row preselected; (4) for the prompt case, the model then requests an action and the user approves that too, since a link cannot alter the session's approval mode.

#### T2: Shell Allow-List Bypass via `SHELL_ALLOW_ALL`

- **Flow**: DF7 (LLM tool call) → C4 Tools (execute)
Expand Down Expand Up @@ -578,6 +592,7 @@
| Input Source | Data Flows | Threats | Validation Points | Responsibility | Gaps |
|-----------------------|-----------------------|---------------|----------------------------------------------------------------------------|----------------|----------------------------------------------------------------------------------------------|
| User direct input | DF1, DF2 | None (TB1) | None — prompts accepted verbatim | User | No content filtering — intentional; HITL gates downstream tool calls |
| `dcode://` link | DF30 | T18 | Closed action and parameter set; unknown/repeated key refusal; UUID-only thread ids; absolute existing-directory-only paths; control-character and deceptive-Unicode refusal; length bounds; unconditional cancel-first confirmation showing the whole request; argv restricted to `-a`/`-r`/`-m` | Shared | Prompt text remains untrusted prose once approved; the browser's own prompt can be remembered away, so dcode's confirmation is the only per-request gate |
| LLM output | DF6, DF7 | T1, T2, T3, T4, T13, T14| HITL gate; shell allow-list; Unicode/URL warnings on tool args; Auto classifier review | Project | LLM-generated tool args not scanned for injection beyond Unicode/URL; shell allow-list matches only the command's first token, so allow-listed interpreters/wrappers bypass it (T13); Auto classifier review quality follows the user-selected classifier model, and the classifier reads untrusted tool arguments, prior output, and model-authored `ask_user` question text (T14) |
| Tool/function results | DF9, DF11 | T1 | Unicode warning on URL args; `markdownify` HTML conversion | Shared | Tool *results* pass to context without prompt-injection scan |
| URL-fetched content | DF8, DF9 | T1 | `check_url_safety` on URL arg; HTML→markdown conversion | Shared | Markup-embedded instructions survive markdownify; no LLM-layer guardrail |
Expand Down Expand Up @@ -668,3 +683,4 @@ Threats that appear valid in isolation but fall outside project responsibility b
| 2026-08-24 | langster-threat-model (diff) | Removed the client-seeded `/offload` fallback. `/offload` is now available only through C18 on built-in servers; local in-process and ACP agents do not support it, and custom or older servers without the route fail at the HTTP boundary. Updated DC5, TB2, TB10, and T6 to remove the client self-approval and synthetic-message attack surface. The server route, hook behavior, archive guard, and state-only persistence controls are unchanged; no new threat was identified. |
| 2026-08-24 | manual update | The C18 boundary now strips endpoint/proxy/transport keys (`base_url`, `openai_proxy`, `http_client`, and similar) from client-supplied `model_params` before they reach `config.create_model` (`offload_api._strip_transport_model_params`), closing the credential-redirection consequence of T6 for this route. Client-supplied `model` and behavioral params still flow through; in-process `CLIContextSchema` model params remain trusted and unfiltered |
| 2026-08-25 | manual update | Stopped replacing bounded superseded goal-state notices in model requests while retaining bounded same-index stand-ins for oversized legacy notices. Goal/rubric history now remains append-only for prompt-cache stability where safe, and the latest notice explicitly supersedes earlier notices. Updated T15 to record the residual risk that a model can still attend to older bounded goal text until compaction. |
| 2026-08-31 | manual update | Added TB15, DF30, and T18 for the opt-in `dcode://` URL scheme (`dcode url install`). Recorded the boundary's controls: a closed action and parameter set in `url_scheme.request` that refuses unknown, repeated, and session-altering parameters as well as control characters and deceptive Unicode; UUID-only thread ids, which also keep the `-r __MOST_RECENT__` sentinel out of a link's reach; an unconditional cancel-first confirmation in `url_scheme.handler` that shows the resolved directory and the whole prompt and fails closed on a non-interactive terminal; and a launch argv limited to `-a`/`-r`/`-m` so a link cannot change approval mode, model, or sandbox. Noted that per-platform dispatch avoids shell interpolation (`%u`, `"%1"`, and `shlex.quote` plus AppleScript `quoted form of`), and that opening a directory does not trust it — project hooks, MCP servers, and extensions still face TB5/TB4/TB11. Added the matching input-source coverage row |
Loading
Loading