Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
122 changes: 122 additions & 0 deletions libs/code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,128 @@ By default, `dcode` trusts the directory you run it in. Human-in-the-loop approv

Do not run `dcode` in a directory you do not trust without a sandbox backend. For untrusted repositories, use a [remote sandbox](https://docs.langchain.com/oss/python/deepagents/code/remote-sandboxes) so execution is isolated from your machine. Running `dcode` in a directory lets that directory's files shape execution. See [`THREAT_MODEL.md`](https://github.com/langchain-ai/deepagents/blob/main/libs/code/THREAT_MODEL.md) for details.

## Managed configuration

Administrators can enforce any supported `config.toml` setting with a read-only
`managed_config.toml` using the same TOML schema:

- macOS: `/Library/Application Support/dcode/managed_config.toml`
- Windows: the `ProgramData` directory reported by the registry, usually
`C:\ProgramData\dcode\managed_config.toml`. The `%ProgramData%` environment
variable is ignored, because any user can change it.
- Linux and other supported POSIX systems: `/etc/dcode/managed_config.toml`

Managed values override two lower layers: the `DEEPAGENTS_CODE_` and
compatibility environment variables, and `~/.deepagents/config.toml`.

For an agent launch, managed values also override these CLI flags: the model,
the auto-classifier model, the interpreter toggle, the programmatic tool-calling
list, the recursion limit, the shell allow list, and the startup mode. A managed
`[sandboxes].default` names the backend of a launch that is already sandboxed;
it does not sandbox a launch that asked for no sandbox. A managed startup mode
only revokes `--auto-approve` and `--yolo`; the mode itself reaches the runtime
through the merged configuration, so a headless launch still works. Subcommand
display flags such as `dcode threads --relative` are not overridden.

Tables merge recursively. Deny lists are unioned. An explicitly managed allow or
trust list replaces lower-precedence grants. An empty managed list removes every
lower-precedence grant.

A managed value whose type contradicts the manifest is ignored, and the
lower-precedence value stays in effect. Two exceptions:

- The enforced keys (`startup.mode`, `startup.yolo_switcher`,
`shell.allow_list`, `skills.extra_allowed_dirs`,
`interpreter.enable_interpreter`, `interpreter.ptc`,
`interpreter.ptc_acknowledge_unsafe`, `models.allowed`,
`models.auto_classifier`, `runtime.recursion_limit`, `sandboxes.default`,
`tracing.langsmith_redact`)
stop every command except `config`, `doctor`, `auth path`, and the help
screens. If one is ignored, the user's flag or environment variable stays in
force. This grants the escalation, or it removes the boundary that the policy
declared. Three
cases stop the launch and block `/reload`: a value the manifest rejects, a
`runtime.recursion_limit` outside its bounds, and a key shadowed by a scalar
ancestor (`startup = "manual"` in place of `[startup]` and `mode`). A managed
`[sandboxes].default` that names an unavailable backend stops a sandboxed
launch; a launch that asked for no sandbox is unaffected. A scalar at any
known configuration section (for example, `threads = "bad"` instead of
`[threads]`) also stops launch and reload rather than replacing the user's
whole section.
- Inside structured tables (`[models.providers]`, `[themes]`,
`[async_subagents]`, `[sandboxes.providers]`) the dedicated typed reader
validates instead, so a wrong-typed managed leaf can displace a valid user
leaf. The reader then falls back to the built-in default. A managed scalar
still replaces a colliding user table there, the same as on the top-level
merge.

`[shell].allow_list` is read from `~/.deepagents/config.toml` and from
`DEEPAGENTS_CODE_SHELL_ALLOW_LIST`, so a managed file can enforce it. A user can
also grant themselves shell auto-approval from their own config file. An empty
managed list removes every lower-precedence grant.

`[models].allowed` narrows dcode to exact `provider:model` specifications:

```toml
[models]
allowed = ["acme:production", "acme:production-fast"]
```

The rules:

- A missing key preserves unrestricted model selection.
- An empty list blocks all local model use.
- A list that dcode cannot parse also blocks all local model use. A typo must not
silently remove the restriction the user asked for. Fix or remove the key.
- Matching is exact and splits only the first colon, so model identifiers may
contain additional colons. Matching is also case-sensitive.
- Entries must be fully qualified. A bare model name that you type at a prompt
is first resolved to `provider:model`, the same as at construction, so
`gpt-5.6-terra` matches an `openai:gpt-5.6-terra` entry. A bare name whose
provider dcode cannot infer never matches.
- Write a Bedrock model as `bedrock:<id>`. A bare Bedrock ID is rejected,
because its version colon would make it a specification that nothing matches.
- The list filters model discovery and the selector. Construction-time checks
are authoritative for CLI flags, runtime switches, Auto classifiers, rubric
graders, and explicit local subagent models.
- A managed list replaces a user list. Managed default, recent, and
Auto-classifier models must also appear in it.
- `[models.providers.<name>].models` registers models additively. It does not
grant permission, so a custom model may need both registration and an
allowlist entry.

`dcode` never writes the managed file. Users can still save a preference. The
theme, terminal-mapping, UI-toggle, and MCP-server screens, and the
`--auto-update` flag, report when a managed value keeps a saved preference from
taking effect. The model-default, recent-model, and Auto-classifier writers
refuse a value outside the effective allowlist, and report the policy as the
reason. Other save paths do not check the allowlist.

A missing managed file applies no policy. If one exists but is unreadable, not
UTF-8, or invalid TOML, every command fails closed except the ones needed to
diagnose it: `--help`, `--version`, `help`, `config`, `doctor`, and
`auth path`. A managed file that becomes unusable later also blocks `/reload`:
the session keeps the policy that was in force, and the reload reports that it
kept it. Use `dcode config path` and `dcode doctor` to inspect its
fixed path and parse health; `dcode config` also warns when the file exists but
could not be parsed, or parses and declares a value that cannot be enforced.

A deny list that cannot be read denies everything rather than nothing. This
covers a managed `[mcp].disabled_servers` that is neither an array of names nor
a comma-separated string, and a `[mcp]` section that is not a table. A managed
`[mcp].enabled_project_server_approvals` that is not an array is treated the
same way: the key is present, so policy means to narrow access, and reading its
presence as absence would leave both the user's approvals and the
`DEEPAGENTS_CODE_DANGEROUSLY_ENABLE_PROJECT_MCP_SERVERS` bypass in force.

A corrupt `~/.deepagents/config.toml` does not disable managed policy: the user
file is ignored and managed values still apply.

Deployment tooling must create and protect this file with administrator or root
permissions. `dcode` does not validate the file owner or mode. `dcode` provides
no privileged writer. Deployment and `sudo` policy are the administrator's
responsibility.

## 📖 Resources

- **[Documentation](https://docs.langchain.com/deepagents-code)**
Expand Down
17 changes: 12 additions & 5 deletions libs/code/THREAT_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
### Assumptions

1. The CLI runs locally on the user's machine; the user is a developer who invoked `deepagents` themselves.
2. The project provides the HITL approval framework; users control model selection, API keys, and whether to disable approval gates.
2. The project provides the HITL approval framework. Users control model selection, API keys, and whether to disable approval gates. Managed policy can narrow each of these; `[models].allowed` narrows model selection.
3. `~/.deepagents/` is only writable by the authenticated local user — no multi-user shared home directories.
4. Sandbox backends are trusted third-party services. CLI responsibility ends at correctly constructing and dispatching requests to them.
5. LangSmith tracing, if enabled, is user-opted-in via environment variables.
Expand Down Expand Up @@ -140,7 +140,7 @@
| C14 | Async Subagent Config | Loads remote LangGraph deployment specs from `[async_subagents]` in `config.toml` | user-controlled | No | `agent.load_async_subagents` |
| C15 | LocalContext Middleware | Runs a bash detection script via backend; injects git/project/env context into system prompt each turn | framework-controlled | Yes⁶ | `local_context.LocalContextMiddleware.before_agent`, `local_context.build_detect_script` |
| C16 | Custom Subagent Loader | Reads `{dir}/{name}/AGENTS.md` YAML frontmatter from `.deepagents/agents/` and project `.agents/` directories | user-controlled | No | `subagents.list_subagents`, `subagents._parse_subagent_file` |
| C17 | Model Config Loader | Resolves model provider, supports `class_path` for arbitrary `BaseChatModel` instantiation via `importlib` | user-controlled | N/A | `config.create_model`, `config._create_model_from_class`, `model_config.ModelConfig.load` |
| C17 | Model Config Loader | Resolves model providers, enforces the `models.allowed` policy (exact specs and `provider:*` wildcards), and supports `class_path` for arbitrary `BaseChatModel` instantiation via `importlib` | administrator/user-controlled | N/A | `config.create_model`, `config._create_model_from_class`, `model_config.ModelConfig.load` |
| C18 | Goal/Rubric State Notice | Projects persisted goal objectives, active criteria, and status notes into synthetic messages for the primary model | framework-controlled | Yes | `goal_state_notice.build_goal_state_notice`, `goal_tools.GoalToolsMiddleware` |

**Notes:**
Expand Down Expand Up @@ -212,7 +212,7 @@
| TB10 | RemoteAgent → LangGraph Dev Server | CLI communicates with agent via HTTP+SSE on localhost | Server bound to `127.0.0.1` (`client/launch/server.py:_DEFAULT_HOST`); ephemeral per session | No authentication (`LANGGRAPH_AUTH_TYPE=noop`); any localhost process can reach the API |
| TB11 | Config File → Code Execution | `class_path` in `config.toml` triggers `importlib.import_module()`; project/global `.env` values are loaded into the process environment and can reach Bash startup hooks | Format validation (`module:ClassName`); `issubclass(BaseChatModel)` check; dotenv loading denies shell startup / environment-hijack keys (`BASH_ENV`, `ENV`) | Module-level side effects execute during import; user controls config file; project files in the working directory influence execution |
| 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; diagnostics remain available | Filesystem ownership/mode and privileged deployment are outside the CLI; a host administrator can weaken or strengthen policy |
| 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 |

### Boundary Details

Expand Down Expand Up @@ -287,10 +287,17 @@

- **Inside**: The resolver reads one fixed OS path. The writer rejects that path, so the managed source is read-only by guard and not only by convention. Valid managed values take the highest precedence. The rules are:
- Tables deep-merge. Deny lists union. An explicit managed allow or trust list replaces lower-precedence grants.
- `[models].allowed` is a local-model ceiling of exact `provider:model` specs and `provider:*` wildcards. Discovery and selector filtering improve usability only.
- `create_model` is authoritative. It checks the canonical `provider:model` before credential bridging, provider hooks, imports, or constructors, so a blocked specification touches no stored key and runs no provider hook.
- Preflight checks on text a user typed resolve a bare name to the same canonical form first, so they neither reject a model that construction would allow nor accept one it would block. A name whose provider cannot be established stays unmatchable.
- `create_cli_agent` checks every model *string* it forwards: the primary model, the Auto classifier, the rubric grader, and an explicit local-subagent model. The SDK resolves a string through `init_chat_model`, which does not pass through `create_model`. A prebuilt model object came from a path that already checked.
- Runtime-context switches are rechecked server-side. A policy denial propagates instead of falling back to the previous model.
- A malformed `[models].allowed` blocks all model use at either layer. The managed layer also refuses to start.
- Remote async-subagent deployments select their models outside this local boundary. Tool enumeration also skips the check, because it compiles a graph it never invokes.
- A managed scalar replaces a colliding user table at any depth, so a user cannot defeat policy by changing the shape of a key. This holds on the top-level merge and inside a structured table: both apply the manifest validator, so the effective value and the audited provenance agree.
- A wrong-typed managed scalar is skipped, and the lower-precedence value stays in effect.
- Inside a structured table (`[models.providers]`, `[themes]`, `[async_subagents]`, `[sandboxes.providers]`, `[ui.terminal_themes]`, `[threads.columns]`) the dedicated typed reader validates instead. A wrong-typed managed leaf there can displace a valid user leaf, after which the reader falls back to the built-in default.
- An enforced key whose managed value cannot be applied stops every command except the diagnostics listed below. It also blocks `/reload`. Skipping it would leave the user's CLI flag or environment variable in force. The enforced keys are `startup.mode`, `startup.yolo_switcher`, `shell.allow_list`, `skills.extra_allowed_dirs`, `interpreter.enable_interpreter`, `interpreter.ptc`, `interpreter.ptc_acknowledge_unsafe`, `models.auto_classifier`, `runtime.recursion_limit`, `sandboxes.default`, and `tracing.langsmith_redact`.
- An enforced key whose managed value cannot be applied stops every command except the diagnostics listed below. It also blocks `/reload`. Skipping it would leave the user's CLI flag or environment variable in force. The enforced keys are `startup.mode`, `startup.yolo_switcher`, `shell.allow_list`, `skills.extra_allowed_dirs`, `interpreter.enable_interpreter`, `interpreter.ptc`, `interpreter.ptc_acknowledge_unsafe`, `models.allowed`, `models.auto_classifier`, `runtime.recursion_limit`, `sandboxes.default`, and `tracing.langsmith_redact`.
- Three conditions make an enforced key unapplicable: a wrong-typed value, a `runtime.recursion_limit` outside its bounds, and a key made unreachable by a scalar ancestor (`startup = "manual"` in place of `[startup]` and `mode`). A managed `[sandboxes].default` that names an unavailable backend stops a sandboxed launch.
- A scalar at a known section is rejected for the same reason, so it cannot replace the user's whole section. `[effort]` is included, although it has no manifest option.
- Any other rejected managed value is ignored, and `dcode doctor` and `dcode config` name it. An ignored key is never silent.
Expand All @@ -300,7 +307,7 @@
- A failed `/reload` keeps the last snapshot that parsed cleanly, so policy is never dropped mid-session.
- An unusable user `config.toml` drops only the user layer, so managed policy still applies.
- A deny list that cannot be read is treated as denying everything, never as empty. This covers a managed `[mcp].disabled_servers` that is neither an array of names nor a comma-separated string, and a `[mcp]` section that is not a table. A managed `[mcp].enabled_project_server_approvals` that is not an array is treated the same way: the key is present, so policy means to narrow access, and reading its presence as absence would keep both the user's approvals and the `DEEPAGENTS_CODE_DANGEROUSLY_ENABLE_PROJECT_MCP_SERVERS` bypass in force.
- **Outside**: Ownership, permission-mode validation, privileged installation, and `sudo` policy are deployment responsibilities. Anyone who can replace the administrator-managed file can control model, sandbox, interpreter, MCP trust, and other supported runtime settings. On macOS, note that stock `/Library/Application Support` is group-writable by `admin`, so any admin-group member can create the file and grant themselves policy. Deployments that rely on this boundary must tighten ownership and mode on the `dcode` directory.
- **Outside**: Ownership, permission-mode validation, privileged installation, and `sudo` policy are deployment responsibilities. Anyone who can replace the administrator-managed file can control model, sandbox, interpreter, MCP trust, and other supported runtime settings. Model policy constrains official dcode selection and construction paths; it does not stop a hostile local user from replacing the package, modifying the process, or calling a provider SDK directly. On macOS, note that stock `/Library/Application Support` is group-writable by `admin`, so any admin-group member can create the file and grant themselves policy. Deployments that rely on this boundary must tighten ownership and mode on the `dcode` directory.
- **Crossing mechanism**: Synchronous local file read through `configuration.TomlFileProvider`, followed by typed resolution and CLI/server startup gates.

---
Expand Down
Loading