You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We should create a read-only mode toggle, with it easily accessible via /readonly and in the dashboard with an additional context overflow toggle button thingy (I hope you know what I mean by that LOL). The core concept here would be that in read-only mode, there is no write, edit, or bash tool use, in that session, and for all subagent sessions spawned by that session. The global setting would be the default state that a session starts in, and then inside any session it should be toggleable at any point. I am aware that this may cause some cache invalidation but its probably worth it.
As a concept, additionally, it may be an alternative solution to issue #437. Or at least a first-step approach toward it. We can consider that we might have read-only mode automatically toggle on whenever the agent does web_search, web_fetch, or uses git clone or bash curl/wget etc. Would need some careful design to not make it useless, and it would need to be very clear in the UX (like a special border and making the button to toggle it suddenly visible?) what has happened and why, and how to undo it (return the model to edit-allowed mode).
Design Direction
From follow-up discussion (verbatim):
Okay so the structure would be more like:
add read-only mode and default on for new sessions
toggle off when ready to make edits
parent agent and subagents lose web_search and web_fetch.... no wait ->
3 (amended). web_search and web_fetch still exist, but instead route to a specialized subagent that has no filesystem read and no edit tools, it only cares about reading and summarizing web content.
Tool usage instructions are changed to make the parent/subagents aware that they need to use prompting to describe exactly what is needed from the fetch or search tools. This is also the only subagent that is callable by a subagent, it's more of a silent bg_agent that does one special job.
we keep bash accessible, but in read-only mode it swaps from an exclude list to an INCLUDE list which only allows certain commands.
really hard complication: how to deal with getting content from git/github safely
On the relationship to the sentinel concept in #437 (verbatim):
I don't see why we would keep the old sentinel concept here. This boundary IS the sentinel... the external data fetcher will have to be aware and alert for injection via our own prompting. A possible followup issue would be to add some kind of lightweight classifier, but this isn't that, and those are adversarially attackable anyway and thus security theater IMO.
On the scope of the quarantine (verbatim, from post-creation discussion):
Yes this is the key point that needs to be updated in the issue body. This fundamentally changes the ENTIRE NATURE of the web_fetch and web_search tools to include a quarantine layer through a LLM intermediary. This is complimentary but technically independent of the read-only toggle, which is just an additional layer to help reduce accidents. Edits are typically much safer in the tail end of a session AFTER vetted research has been completed.
On the maturity of the git/GitHub part of the design (verbatim, from post-creation discussion):
also let's note that I still will want, at the time of assessment/planning, to consider the external data fetcher's ability to use gh and git in limited ways. I feel like we haven't seriously arbitrated that and it's one of the weakest points of the plan. Having it in the acceptance criteria with the same level of authority as the others is misleading.
Summary
This issue covers two complementary but technically independent layers:
Quarantined external-content retrieval (always on, all modes) — web_search and web_fetch fundamentally change nature: they no longer fetch raw content into the caller's context. Instead they route through a specialized "external content retriever" subagent that has network access but no filesystem read and no mutation tools, and returns distilled, injection-aware summaries. Raw untrusted content never enters any process that holds local file access or mutation tools. This applies in every mode, not just read-only — injection pays off precisely when mutation tools are present, so quarantining only in read-only mode would protect the mode that least needs it.
Read-only session mode (the toggle) — an additional accident-reduction layer: new sessions start read-only (no write/edit; bash restricted to an include list), toggleable at any point via /readonly in the TUI and a dashboard overflow-menu toggle, with leaving read-only mode always a deliberate user action. The natural workflow this supports: research and exploration happen safely in read-only mode, and edits happen in the tail end of a session after vetted research has been completed.
Supersedes #459 (manual read-only toggle — absorbed into this design) and #437 (external-content sentinel — the quarantine boundary replaces the classifier concept; see Design Direction above).
Security Invariant
The design goal is that no single process ever simultaneously holds:
read access to local files (secrets, proprietary code),
raw untrusted external content, and
network egress.
The parent session holds (1) — and, in edit mode, mutation tools — but never sees (2) and has no direct (3); it receives only distilled summaries. The retriever holds (2) and (3) but has no (1) and no mutation tools, so it has nothing worth exfiltrating beyond its task prompt. This is the dual-LLM / privilege-separation pattern (Simon Willison 2023; CaMeL, arXiv:2503.18813) adapted to dreb's process-per-subagent architecture, and it structurally breaks the "lethal trifecta" instead of trying to detect attacks.
Read-only mode strengthens the invariant further by removing mutation tools from the parent, but the quarantine does not depend on it.
Threat Model
In scope (strongly mitigated):
Destructive script-kiddie prompt injection (rm -rf, scrambling working directories "for the lulz") — injected instructions never reach a process holding mutation tools; in read-only mode, no mutation tools exist at all.
Credential/source exfiltration via injected instructions — the process that reads local files has no network tools; the process with network tools can't read local files; retriever call arguments are additionally secret-scrubbed.
Known residual risks (must be documented, not claimed as solved):
Summary integrity: the retriever's distilled output may still contain misleading content or instruction-shaped text from poisoned sources. An injected instruction that survives summarization could still manipulate the parent — but it must pass through the retriever's injection-aware filtering first, and in read-only mode the parent cannot act on it destructively anyway.
Task-prompt leakage: the parent's request to the retriever (queries, URLs) may itself contain sensitive context. Tool descriptions must instruct the model to phrase retrieval tasks using only information it's comfortable sending to a third party.
Repo-code execution via allowed bash commands: if the read-only include list ever permits test runners / build tools (npm test, make, pytest), those execute repo-controlled code and become mutation/exfil-by-proxy.
Retriever credential inheritance: if the retriever ends up able to run gh/git with the user's credentials, a hijacked retriever could attempt authenticated actions. This is unresolved — see Open Design Questions.
Proposed Architecture
1. External-content retriever (the quarantine boundary — all modes)
web_search and web_fetch keep their tool names but transparently delegate to a specialized leaf subagent in all modes. This is the fundamental nature change: these tools now mean "delegated, quarantined retrieval," everywhere.
The retriever has no filesystem read tools, no write/edit. Its only local capability beyond web tools is whatever git/GitHub access mechanism survives arbitration — see Open Design Questions; the current sketch (read-only gh/git) is not a settled decision.
The retriever's system prompt is injection-aware: it is instructed to treat all fetched content as adversarial data, never follow instructions found in it, and flag suspected injection attempts in its summary instead of complying with or silently propagating them. On request it can quote source material verbatim in its summary (for exact error messages, API specs, etc.).
It is the only subagent callable by a subagent — a leaf retriever available at any depth so the chokepoint can't be bypassed by nesting.
Quiet but auditable: no user interaction needed per fetch, but every fetched/cloned URL is logged, and retriever session logs are viewable in the TUI/dashboard.
Tool descriptions for web_search/web_fetch are updated so callers describe precisely what information they need extracted, and understand the task prompt crosses a trust boundary.
Escape hatch: a global-only opt-in setting (default off) re-enables direct fetching into the caller's context for power users who accept the risk. Changing it is a deliberate user action and must be loudly visible when active.
2. Read-only mode (independent toggle layer)
Global-only setting controls the default; new sessions start in read-only mode by default (following the industry pattern: Claude Code plan mode, Codex CLI read-only sandbox, Gemini CLI plan mode default).
/readonly slash command toggles mid-session in the TUI; dashboard session-header overflow menu gets a matching toggle showing current state.
Current mode is clearly indicated in both UIs (e.g. border/badge). Leaving read-only mode is a deliberate user action, never model-initiated.
In read-only mode, write and edit are removed from the tool set of the session and of every subagent spawned while it is active. (Subagents already running when the mode toggles are grandfathered — they're separate processes and can't be recalled.)
Mid-session tool-set swaps invalidate the provider prompt cache; accepted trade-off.
3. Git/GitHub content — direction only, mechanism NOT arbitrated
Settled principle: GitHub content (PR/issue bodies, comments, API responses) is attacker-controlled text and must not bypass the quarantine — whatever mechanism is chosen, raw git/GitHub content must not flow into any process that holds local file access or mutation tools.
Also settled: the current web_fetch behavior of bouncing GitHub URLs to gh CLI guidance is incompatible with this design (it assumes the caller has bash access, which the quarantine removes) and must be revisited.
NOT settled: how the retriever actually accesses git/GitHub content. The candidate mechanisms and their hazards are enumerated in Open Design Questions below. Treat everything in the earlier drafts of this section (gh pr view allowlists, git clone to /tmp, etc.) as candidate sketches, not decisions.
4. Bash include list (read-only mode only)
In read-only mode, bash remains but inverts from the existing forbidden-command exclude list to an include list of allowed commands.
Implementation guidance: reject shell metacharacters (chaining, redirection, command substitution) rather than prefix-matching raw command strings — prefix matching is trivially bypassable (git status; rm -rf x). Single simple commands matched on argv.
Explicit decision needed during implementation on whether repo-code-executing commands (test runners, build tools) belong in the default include list, given the residual risk above.
5. Outbound argument scrubbing
Extend the existing secret-scrubbing layer to the arguments of the retriever's network calls (URLs, queries) before the request fires. Tool-result scrubbing is insufficient here — exfiltration happens at request time.
Scrub failures block the call loudly.
Open Design Questions (must be arbitrated at assessment/planning time)
Q1: How does the retriever access git/GitHub content?(Flagged by the issue author as one of the weakest points of the plan — it has not been seriously arbitrated and must not be treated as decided.)
Candidate mechanisms, none endorsed yet:
(a) Constrained bash inside the retriever running an allowlist of gh/git commands. Hazards: gh api is not inherently read-only (--method POST, -f fields make arbitrary authenticated writes); the retriever inherits the user's GH_TOKEN/git credentials, so a hijacked retriever with any bash surface could attempt authenticated mutations; argv-level allowlisting must assume a hostile model, not a cooperative one; git clone flags matter (--recurse-submodules fetches attacker-controlled URLs, ext:: transports, credential helpers).
(b) Dedicated typed tools (e.g. a github_fetch tool with a fixed schema of read-only operations: view PR, view issue, read file at ref, list comments) implemented over the GitHub API, with no general shell at all. Stronger isolation (no argv parsing to get wrong, credentials can be scoped server-side per-call) at the cost of building and maintaining a new tool surface.
(c) Plain web_fetch only for public GitHub content, accepting the known reliability problems that caused the current bounce-to-gh guidance (rendered HTML chrome on /blob/ pages, no auth for private repos), and no private-repo access at all.
Some hybrid, e.g. (b) for GitHub API content + tightly-scoped clone-to-/tmp via a dedicated tool rather than shell git.
Sub-questions: Does the retriever get credentials at all, or a downscoped token? Is clone-to-/tmp done via shell or a dedicated tool with fixed flags? What is the full enumeration of git/GitHub operations the retriever legitimately needs?
Acceptance Criteria
web_search and web_fetch route to the retriever subagent in all modes; no agent process fetches external content directly into its own context by default.
A global-only opt-in escape hatch re-enables direct fetching; it defaults off and is loudly indicated when active.
The retriever has no filesystem read tools and no mutation tools; its complete tool surface is web_search, web_fetch, plus whatever git/GitHub mechanism the planning phase decides (Q1).
The retriever's prompting instructs injection awareness: treat fetched content as adversarial, never obey embedded instructions, flag suspected injections in summaries; verbatim quoting available on request.
The retriever is a leaf agent callable at any depth, including by other subagents.
Tool descriptions for the routed web tools instruct callers to describe exactly what they need and to treat task prompts as crossing a trust boundary.
Secret scrubbing applies to the arguments of the retriever's network calls before requests fire; scrub failures block loudly.
Every fetched/cloned URL is logged, and retriever session logs are viewable in the TUI and dashboard.
Q1 (git/GitHub access mechanism) is explicitly arbitrated and documented during assessment/planning, before implementation begins — the answer is a deliberate decision with named hazards, not something that falls out of implementation convenience.
A global-only setting controls the default mode; new sessions start in read-only mode by default.
/readonly toggles read-only mode mid-session in the TUI; the dashboard session header overflow menu has a toggle reflecting current state.
Current mode is clearly indicated in TUI and dashboard; leaving read-only mode requires a deliberate user action.
In read-only mode, write and edit are unavailable to the session and every subagent spawned while it is active.
bash in read-only mode uses an include list of allowed commands; shell metacharacters are rejected.
Residual risks (summary integrity, task-prompt leakage, repo-code execution via include list, retriever credential inheritance) are documented in user-facing docs.
Out of Scope / Future
Lightweight classifier on retriever output — possible follow-up issue, deliberately excluded here. The boundary itself is the control; classifiers are adversarially attackable and risk becoming security theater.
Per-domain approval ratchet for outbound fetches (approve new domains on first use) — candidate hardening if task-prompt leakage proves significant.
Auto-engagement of read-only mode upon untrusted-content ingestion — mooted by always-on quarantine; if revisited, use warn-first with a human ratchet.
Technical Notes
Tool registry/factories: packages/coding-agent/src/core/tools/index.ts — createReadOnlyToolDefinitions() already exists (read/grep/find/ls) as a starting point for read-only mode.
Subagent spawn and tool filtering: packages/coding-agent/src/core/tools/subagent.ts — filterSubagentTools(), tools passed via --tools CLI arg to child processes. The retriever needs a dedicated agent definition and a mechanism for subagents to spawn only it (children currently can't spawn subagents at all).
Slash command registration: packages/coding-agent/src/core/slash-commands.ts; dispatch in packages/coding-agent/src/modes/interactive/interactive-mode.ts.
Dashboard: overflow menu in packages/dashboard/src/client/screens/session.tsx; per-session RPC pattern follows setThinking (api.ts → Express route in server.ts → h.client.*).
Settings: packages/coding-agent/src/core/settings-manager.ts — the default-mode setting and the direct-fetch escape hatch should be global-only so project settings can't weaken them (mirrors the nested-context trust boundary policy).
Current web_fetch is GET-only, blocks private/internal hosts, and redirects GitHub URLs to gh/clone guidance (packages/coding-agent/src/core/tools/web.ts).
Mid-session tool-set swap invalidates the provider prompt cache — accepted.
Original Request
Design Direction
From follow-up discussion (verbatim):
On the relationship to the sentinel concept in #437 (verbatim):
On the scope of the quarantine (verbatim, from post-creation discussion):
On the maturity of the git/GitHub part of the design (verbatim, from post-creation discussion):
Summary
This issue covers two complementary but technically independent layers:
Quarantined external-content retrieval (always on, all modes) —
web_searchandweb_fetchfundamentally change nature: they no longer fetch raw content into the caller's context. Instead they route through a specialized "external content retriever" subagent that has network access but no filesystem read and no mutation tools, and returns distilled, injection-aware summaries. Raw untrusted content never enters any process that holds local file access or mutation tools. This applies in every mode, not just read-only — injection pays off precisely when mutation tools are present, so quarantining only in read-only mode would protect the mode that least needs it.Read-only session mode (the toggle) — an additional accident-reduction layer: new sessions start read-only (no
write/edit;bashrestricted to an include list), toggleable at any point via/readonlyin the TUI and a dashboard overflow-menu toggle, with leaving read-only mode always a deliberate user action. The natural workflow this supports: research and exploration happen safely in read-only mode, and edits happen in the tail end of a session after vetted research has been completed.Supersedes #459 (manual read-only toggle — absorbed into this design) and #437 (external-content sentinel — the quarantine boundary replaces the classifier concept; see Design Direction above).
Security Invariant
The design goal is that no single process ever simultaneously holds:
The parent session holds (1) — and, in edit mode, mutation tools — but never sees (2) and has no direct (3); it receives only distilled summaries. The retriever holds (2) and (3) but has no (1) and no mutation tools, so it has nothing worth exfiltrating beyond its task prompt. This is the dual-LLM / privilege-separation pattern (Simon Willison 2023; CaMeL, arXiv:2503.18813) adapted to dreb's process-per-subagent architecture, and it structurally breaks the "lethal trifecta" instead of trying to detect attacks.
Read-only mode strengthens the invariant further by removing mutation tools from the parent, but the quarantine does not depend on it.
Threat Model
In scope (strongly mitigated):
rm -rf, scrambling working directories "for the lulz") — injected instructions never reach a process holding mutation tools; in read-only mode, no mutation tools exist at all.Known residual risks (must be documented, not claimed as solved):
npm test,make,pytest), those execute repo-controlled code and become mutation/exfil-by-proxy.gh/gitwith the user's credentials, a hijacked retriever could attempt authenticated actions. This is unresolved — see Open Design Questions.Proposed Architecture
1. External-content retriever (the quarantine boundary — all modes)
web_searchandweb_fetchkeep their tool names but transparently delegate to a specialized leaf subagent in all modes. This is the fundamental nature change: these tools now mean "delegated, quarantined retrieval," everywhere.gh/git) is not a settled decision.web_search/web_fetchare updated so callers describe precisely what information they need extracted, and understand the task prompt crosses a trust boundary.2. Read-only mode (independent toggle layer)
/readonlyslash command toggles mid-session in the TUI; dashboard session-header overflow menu gets a matching toggle showing current state.writeandeditare removed from the tool set of the session and of every subagent spawned while it is active. (Subagents already running when the mode toggles are grandfathered — they're separate processes and can't be recalled.)3. Git/GitHub content — direction only, mechanism NOT arbitrated
web_fetchbehavior of bouncing GitHub URLs toghCLI guidance is incompatible with this design (it assumes the caller has bash access, which the quarantine removes) and must be revisited.gh pr viewallowlists,git cloneto /tmp, etc.) as candidate sketches, not decisions.4. Bash include list (read-only mode only)
bashremains but inverts from the existing forbidden-command exclude list to an include list of allowed commands.git status; rm -rf x). Single simple commands matched on argv.5. Outbound argument scrubbing
Open Design Questions (must be arbitrated at assessment/planning time)
Q1: How does the retriever access git/GitHub content? (Flagged by the issue author as one of the weakest points of the plan — it has not been seriously arbitrated and must not be treated as decided.)
Candidate mechanisms, none endorsed yet:
gh/gitcommands. Hazards:gh apiis not inherently read-only (--method POST,-ffields make arbitrary authenticated writes); the retriever inherits the user'sGH_TOKEN/git credentials, so a hijacked retriever with any bash surface could attempt authenticated mutations; argv-level allowlisting must assume a hostile model, not a cooperative one;git cloneflags matter (--recurse-submodulesfetches attacker-controlled URLs,ext::transports, credential helpers).github_fetchtool with a fixed schema of read-only operations: view PR, view issue, read file at ref, list comments) implemented over the GitHub API, with no general shell at all. Stronger isolation (no argv parsing to get wrong, credentials can be scoped server-side per-call) at the cost of building and maintaining a new tool surface.web_fetchonly for public GitHub content, accepting the known reliability problems that caused the current bounce-to-ghguidance (rendered HTML chrome on/blob/pages, no auth for private repos), and no private-repo access at all.git.Sub-questions: Does the retriever get credentials at all, or a downscoped token? Is clone-to-/tmp done via shell or a dedicated tool with fixed flags? What is the full enumeration of git/GitHub operations the retriever legitimately needs?
Acceptance Criteria
web_searchandweb_fetchroute to the retriever subagent in all modes; no agent process fetches external content directly into its own context by default.web_search,web_fetch, plus whatever git/GitHub mechanism the planning phase decides (Q1)./readonlytoggles read-only mode mid-session in the TUI; the dashboard session header overflow menu has a toggle reflecting current state.writeandeditare unavailable to the session and every subagent spawned while it is active.bashin read-only mode uses an include list of allowed commands; shell metacharacters are rejected.Out of Scope / Future
Technical Notes
packages/coding-agent/src/core/tools/index.ts—createReadOnlyToolDefinitions()already exists (read/grep/find/ls) as a starting point for read-only mode.packages/coding-agent/src/core/tools/subagent.ts—filterSubagentTools(), tools passed via--toolsCLI arg to child processes. The retriever needs a dedicated agent definition and a mechanism for subagents to spawn only it (children currently can't spawn subagents at all).packages/coding-agent/src/core/slash-commands.ts; dispatch inpackages/coding-agent/src/modes/interactive/interactive-mode.ts.packages/dashboard/src/client/screens/session.tsx; per-session RPC pattern followssetThinking(api.ts→ Express route inserver.ts→h.client.*).packages/coding-agent/src/core/settings-manager.ts— the default-mode setting and the direct-fetch escape hatch should be global-only so project settings can't weaken them (mirrors the nested-context trust boundary policy).web_fetchis GET-only, blocks private/internal hosts, and redirects GitHub URLs togh/clone guidance (packages/coding-agent/src/core/tools/web.ts).