From 74309a7cef91e7e63645eed6046de63da3c4d4cb Mon Sep 17 00:00:00 2001 From: ciregenz Date: Sun, 19 Jul 2026 12:07:00 -0700 Subject: [PATCH] [eric] agents: read-only session flag for the unattended onboarding audit (Edit/Bash hard-blocked) --- backend/apps/agents/core/models.py | 6 ++++++ backend/apps/agents/manager/AgentLaunch.py | 1 + .../permissions/build_effective_tool_lists.py | 13 +++++++++++++ frontend/src/shared/state/agentsSlice.ts | 2 ++ 4 files changed, 22 insertions(+) diff --git a/backend/apps/agents/core/models.py b/backend/apps/agents/core/models.py index 48bc5bc3..bc93cdff 100644 --- a/backend/apps/agents/core/models.py +++ b/backend/apps/agents/core/models.py @@ -17,6 +17,10 @@ class AgentConfig(BaseModel): workflow_edit_id: Optional[str] = None # App cards the user picked to edit. When exactly one resolves, launch binds the chat's cwd to that app instead of seeding a new "Untitled App". selected_app_output_ids: Optional[list[str]] = None + # Onboarding auto-launches an audit over the user's REAL files with nobody watching, so it runs + # read-only: Read/Grep/Glob + Write (its one report) allowed, Edit/Bash/NotebookEdit hard-blocked + # so "modify or delete an existing file" is unrepresentable, not just discouraged by the prompt. + read_only: bool = False class ApprovalRequest(BaseModel): id: str = Field(default_factory=lambda: uuid4().hex) @@ -84,6 +88,8 @@ class AgentSession(BaseModel): sdk_session_id: Optional[str] = None system_prompt: Optional[str] = None allowed_tools: list[str] = Field(default_factory=list) + # Hard-block the mutation/exec tools for this session (onboarding's unattended audit); see AgentConfig.read_only. + read_only: bool = False max_turns: Optional[int] = None cwd: Optional[str] = None # Resolved at session start so resume reattaches to the same repo even after the user cd's elsewhere. diff --git a/backend/apps/agents/manager/AgentLaunch.py b/backend/apps/agents/manager/AgentLaunch.py index 83c5057d..abf3a806 100644 --- a/backend/apps/agents/manager/AgentLaunch.py +++ b/backend/apps/agents/manager/AgentLaunch.py @@ -110,6 +110,7 @@ class AgentLaunch(AgentManagerProtocol): mode=config.mode, system_prompt=config.system_prompt, allowed_tools=tools, + read_only=config.read_only, max_turns=config.max_turns, cwd=effective_cwd, repo_url=repo_url, diff --git a/backend/apps/agents/manager/permissions/build_effective_tool_lists.py b/backend/apps/agents/manager/permissions/build_effective_tool_lists.py index eb916bcd..7d308f6e 100644 --- a/backend/apps/agents/manager/permissions/build_effective_tool_lists.py +++ b/backend/apps/agents/manager/permissions/build_effective_tool_lists.py @@ -19,6 +19,10 @@ from backend.apps.tools_lib.tools_lib import ( sanitize_server_name as sanitize_server_name, ) +# Mutation/exec tools a read-only session must never reach: Edit (rewrites files), Bash (rm/mv/overwrite), +# NotebookEdit (rewrites notebooks). Write is intentionally NOT here, the audit needs its one report. +READ_ONLY_BLOCKED_TOOLS = ("Edit", "Bash", "NotebookEdit") + @typechecked def build_effective_tool_lists( @@ -108,4 +112,13 @@ def build_effective_tool_lists( # The claude_code preset ships its own bare `Skill` tool that reads ~/.claude/skills directly; always withhold it so skills only ever load through our provider-agnostic mcp__openswarm-skill__Skill (or not at all). if "Skill" not in effective_disallowed: effective_disallowed.append("Skill") + # Read-only session (onboarding's unattended audit over the user's real files): the mutation/exec + # tools are HARD-blocked, not just left out of allowed, so a background agent can never modify or + # delete an existing file. Write stays permitted for its single report. Also drop them from allowed + # in case a preset seeded them, disallowed wins in the SDK but keep the two lists coherent. + if getattr(session, "read_only", False): + for dt in READ_ONLY_BLOCKED_TOOLS: + if dt not in effective_disallowed: + effective_disallowed.append(dt) + effective_allowed = [t for t in effective_allowed if t not in READ_ONLY_BLOCKED_TOOLS] return effective_allowed, effective_disallowed diff --git a/frontend/src/shared/state/agentsSlice.ts b/frontend/src/shared/state/agentsSlice.ts index 18c72f5b..a3628a97 100644 --- a/frontend/src/shared/state/agentsSlice.ts +++ b/frontend/src/shared/state/agentsSlice.ts @@ -137,6 +137,8 @@ export interface AgentConfig { target_directory?: string; dashboard_id?: string; selected_app_output_ids?: string[]; + // Onboarding's unattended audit runs read-only over the user's real files (Edit/Bash hard-blocked). + read_only?: boolean; } export interface HistorySession {