diff --git a/docs/turn-loop.md b/docs/turn-loop.md index 3f86a01b..112f79fe 100644 --- a/docs/turn-loop.md +++ b/docs/turn-loop.md @@ -569,22 +569,13 @@ status hint moved to the wake prompt + UI header. ### Tool whitelist (`mcp::ALLOWED_BUILTIN_TOOLS`) -- Allowed built-ins: `Bash`, `Edit`, `Glob`, `Grep`, `Read`, `Write`. -- Denied by omission: `WebFetch`, `WebSearch`, `Task`, - `NotebookEdit`, `TodoWrite`. +- Allowed built-ins: `Edit`, `Glob`, `Grep`, `Read`, `Write`. +- Denied by omission or `claude-settings.json` deny list: `Bash`, + `WebFetch`, `WebSearch`, `Task`, `NotebookEdit`, `TodoWrite`. - Allowed MCP tools: as listed above per flavor. -By default `Bash` is approved wholesale — any shell command runs -without confirmation. To restrict an agent to specific command -families, set `hyperhive.allowedBashPatterns` in its `agent.nix`: - -```nix -hyperhive.allowedBashPatterns = [ "git *" "ls *" ]; -``` - -The harness reads `/etc/hyperhive/bash-allow.json` and replaces -`Bash` in `--allowedTools` with `Bash(git *)` + `Bash(ls *)` etc. -Commands outside the pattern list require confirmation — which in -`--print` mode means they will not run. An empty list (default) keeps -the current wholesale `Bash` entry. +`Bash` is disallowed — shell execution goes through +`mcp__hyperhive__bash_run` (background tasks with structured output + +task-id tracking) instead of an interactive shell. The `bash_run` / +`bash_status` MCP tools are always in the `--allowedTools` list. diff --git a/hive-ag3nt/prompts/claude-settings.json b/hive-ag3nt/prompts/claude-settings.json index 7fdf4373..edf04d70 100644 --- a/hive-ag3nt/prompts/claude-settings.json +++ b/hive-ag3nt/prompts/claude-settings.json @@ -3,6 +3,6 @@ "autoMemoryEnabled": false, "effortLevel": "medium", "permissions": { - "deny": ["WebFetch", "WebSearch", "Task", "TodoWrite"] + "deny": ["Bash", "WebFetch", "WebSearch", "Task", "TodoWrite"] } } diff --git a/hive-ag3nt/prompts/system.md b/hive-ag3nt/prompts/system.md index d60084bd..fe66b96d 100644 --- a/hive-ag3nt/prompts/system.md +++ b/hive-ag3nt/prompts/system.md @@ -128,7 +128,7 @@ Claude session (OAuth credentials) lives at `/root/.claude/` and persists across **Shared space**: `/shared` is accessible to all agents (read/write). Only put things here you're willing to lose — other agents may delete them. Use for explicit cross-agent communication or shared artifacts when appropriate. -**Code forge**: a private Forgejo at `http://localhost:3000` is available when `/agents/{label}/state/forge-token` exists. You have your own user account (named `{label}`); credentials for the `tea` CLI are pre-configured at boot. Use `tea repos create`, `tea pulls create --base main --head `, `tea pulls list`, `tea issues create`, etc. for any persistent code work — git repos that should outlive a single turn, code you want a peer or the operator to review, anything you'd otherwise jam into `/shared`. Falls back to plain `git`/`curl` if `tea` doesn't fit; the REST API is at `http://localhost:3000/api/v1/` with the same token (`Authorization: token $(cat /agents/{label}/state/forge-token)`). +**Code forge**: a private Forgejo at `http://localhost:3000` is available when `/agents/{label}/state/forge-token` exists. You have your own user account (named `{label}`). Use `hive-forge` (see below) for all forge operations — issues, PRs, comments, labels, etc. For git operations use plain `git` directly against `http://localhost:3000//.git` (credentials are pre-configured). The `hive-forge` CLI helper wraps common Forgejo API operations: `view`, `issue`, `issue-create`, `issue-edit`, `pr`, `pr-create`, `comment`, `comments`, `comment-show`, `comment-edit`, `assign`, `close`, `labels`, `lint`, `list`, `milestone`, `pr-reviews`, `branches`, `tree-sha`, `diff`, `subscription`, `attach-issue`, `attach-comment`. `lint ` runs triage queries (`unassigned`, `no-reviewer --reviewer NAME`, `stale-branches [--days N]`, `assignments [--user NAME]`). Default repo comes from `HIVE_FORGE_REPO`; pass `-r ` (global flag, works before or after the verb) to target a different repo. Every verb takes `--help` for its full signature. To create a PR: `hive-forge pr-create --title "..." --head [--base main] [--body "..." | --body-file ] [--draft] [--push [--remote forge]]` — prints the PR URL. Add `--push` to also `git push` the head branch before the API call (default remote: `forge`); the noisy post-push "Create a pull request" hint is suppressed since we print the canonical URL ourselves. To create an issue: `hive-forge issue-create --title "..." [--body "..." | --body-file ] [--assignee ]`. `--body-file -` means stdin, so a HEREDOC body works naturally: `hive-forge comment --body-file - < ` / `hive-forge attach-comment ` — both print the `browser_download_url`. Key ops: `hive-forge diff ` prints the unified diff; `hive-forge subscription [--watch|--ignore|--unwatch]` manages repo watch state. Note: forge notifications are delivered via the internal message daemon. diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs index 4d79930a..7e50cdf6 100644 --- a/hive-ag3nt/src/mcp.rs +++ b/hive-ag3nt/src/mcp.rs @@ -1898,13 +1898,13 @@ pub const SERVER_NAME: &str = "hyperhive"; /// Built-in claude tools the turn loop enables via `--tools`. Anything not /// in this list literally doesn't exist in the session (claude won't even /// try to call it). Web egress (`WebFetch`/`WebSearch`) and nested agents -/// (`Task`) are intentionally omitted for now; `Bash` is allowed pending a -/// finer-grained allow-list system for shell command patterns. `TodoWrite` +/// (`Task`) are intentionally omitted. `Bash` is disallowed — shell +/// execution goes through `mcp__hyperhive__bash_run` (background tasks +/// with structured output) instead of a raw interactive shell. `TodoWrite` /// is omitted because the todo list lives in claude's in-process session /// state and silently evaporates on /compact or session reset — agents -/// should plan in /state notes instead. Edit later as our trust model -/// evolves. -pub const ALLOWED_BUILTIN_TOOLS: &[&str] = &["Bash", "Edit", "Glob", "Grep", "Read", "Write"]; +/// should plan in /state notes instead. +pub const ALLOWED_BUILTIN_TOOLS: &[&str] = &["Edit", "Glob", "Grep", "Read", "Write"]; /// Which MCP tool surface to advertise via `--allowedTools`. The agent /// list is the strict subset of the manager list, so we just thread the @@ -2032,27 +2032,12 @@ pub fn allowed_mcp_tools(groups: &[hive_sh4re::ToolGroup]) -> Vec { } /// Combined allow-list passed to `--allowedTools` (auto-approve) — covers -/// both the built-ins and the MCP surface. If `hyperhive.allowedBashPatterns` -/// is configured (non-empty list in `/etc/hyperhive/bash-allow.json`), -/// `Bash` is replaced with one `Bash(pattern)` entry per pattern so -/// only vetted command families auto-approve without a blanket shell grant. -/// An empty or missing allow file keeps the current wholesale `Bash` entry. +/// both the built-ins and the MCP surface. #[must_use] pub fn allowed_tools_arg(flavor: Flavor) -> String { let mut all: Vec = ALLOWED_BUILTIN_TOOLS .iter() - .flat_map(|s| { - if *s == "Bash" { - let patterns = load_bash_allow(); - if patterns.is_empty() { - vec!["Bash".to_owned()] - } else { - patterns.into_iter().map(|p| format!("Bash({p})")).collect() - } - } else { - vec![(*s).to_owned()] - } - }) + .map(|s| (*s).to_owned()) .collect(); let groups = effective_tool_groups(flavor); all.extend(allowed_mcp_tools(&groups)); @@ -2073,13 +2058,6 @@ pub fn builtin_tools_arg() -> String { ALLOWED_BUILTIN_TOOLS.join(",") } -/// Where the NixOS module writes the per-agent Bash command allow-list -/// (see `nix/templates/harness-base.nix`). Contains a JSON array of -/// command-pattern strings like `["git *", "ls *"]`. Empty array = -/// wholesale `Bash` approval (the default). Non-empty = one -/// `Bash(pattern)` entry per item in `--allowedTools`. -const BASH_ALLOW_PATH: &str = "/etc/hyperhive/bash-allow.json"; - /// Where the NixOS module writes the per-agent extra-MCP spec (see /// `nix/templates/harness-base.nix`). Each entry becomes an additional /// `mcpServers.` block in the rendered claude config + a @@ -2164,20 +2142,6 @@ fn default_allowed_tools() -> Vec { /// Read + parse the Bash command allow-list. Returns an empty vec when /// the file is missing or unparsable (degrade to wholesale `Bash` /// approval — same as the pre-feature behaviour). -fn load_bash_allow() -> Vec { - let Ok(raw) = std::fs::read_to_string(BASH_ALLOW_PATH) else { - return Vec::new(); - }; - serde_json::from_str::>(&raw).unwrap_or_else(|e| { - tracing::warn!( - path = BASH_ALLOW_PATH, - error = ?e, - "bash-allow list parse failed; falling back to wholesale Bash approval", - ); - Vec::new() - }) -} - /// Read + parse the extra-MCP spec. Returns an empty map when /// the file is missing or unparsable (the agent has none configured, /// or the file is malformed — both cases degrade to "no extra servers"). diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 627e66a9..b4c70e2d 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -129,29 +129,6 @@ in ''; }; - options.hyperhive.allowedBashPatterns = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - example = [ - "git *" - "ls *" - "cat /agents/*/state/*" - ]; - description = '' - Shell command patterns auto-approved for the `Bash` built-in tool. - Empty list (the default) grants wholesale `Bash` approval — - claude can run any shell command without a prompt. Non-empty list - replaces `Bash` in `--allowedTools` with one `Bash(pattern)` entry - per item; only commands matching a pattern are auto-approved; all - others require confirmation (which in `--print` mode means they - will not run). Use to sandbox agents to a known-safe command - vocabulary. - - Patterns use the same glob syntax claude accepts in `Bash(…)`: - `*` matches any string within a word, shell-style. - ''; - }; - options.hyperhive.allowedRecipients = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; @@ -824,9 +801,6 @@ in text = config.hyperhive._bashEnvFragments; }; - environment.etc."hyperhive/bash-allow.json".text = - builtins.toJSON config.hyperhive.allowedBashPatterns; - environment.etc."hyperhive/send-allow.json".text = builtins.toJSON config.hyperhive.allowedRecipients;