feat(#1058): disallow built-in Bash; remove allowedBashPatterns; prompt sweep
This commit is contained in:
parent
013e8740bd
commit
88f22065b0
5 changed files with 16 additions and 87 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
"autoMemoryEnabled": false,
|
||||
"effortLevel": "medium",
|
||||
"permissions": {
|
||||
"deny": ["WebFetch", "WebSearch", "Task", "TodoWrite"]
|
||||
"deny": ["Bash", "WebFetch", "WebSearch", "Task", "TodoWrite"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
<!-- /role:agent -->
|
||||
|
||||
**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 <branch>`, `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/<org>/<repo>.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 <sub>` runs triage queries (`unassigned`, `no-reviewer --reviewer NAME`, `stale-branches [--days N]`, `assignments [--user NAME]`). Default repo comes from `HIVE_FORGE_REPO`; pass `-r <repo>` (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 <branch> [--base main] [--body "..." | --body-file <path>] [--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 <path>] [--assignee <user>]`. `--body-file -` means stdin, so a HEREDOC body works naturally: `hive-forge comment <num> --body-file - <<EOF ... EOF`. To attach a file: `hive-forge attach-issue <number> <file>` / `hive-forge attach-comment <comment-id> <file>` — both print the `browser_download_url`. Key ops: `hive-forge diff <pr>` prints the unified diff; `hive-forge subscription [--watch|--ignore|--unwatch]` manages repo watch state. Note: forge notifications are delivered via the internal message daemon.
|
||||
<!-- role:agent -->
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
|||
}
|
||||
|
||||
/// 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<String> = 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.<key>` block in the rendered claude config + a
|
||||
|
|
@ -2164,20 +2142,6 @@ fn default_allowed_tools() -> Vec<String> {
|
|||
/// 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<String> {
|
||||
let Ok(raw) = std::fs::read_to_string(BASH_ALLOW_PATH) else {
|
||||
return Vec::new();
|
||||
};
|
||||
serde_json::from_str::<Vec<String>>(&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").
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue