add allowedBashPatterns NixOS option for finer-grained Bash tool approval
This commit is contained in:
parent
c05a750409
commit
3c672ed6b2
3 changed files with 88 additions and 10 deletions
|
|
@ -276,11 +276,21 @@ status hint moved to the wake prompt + UI header.
|
||||||
|
|
||||||
### Tool whitelist (`mcp::ALLOWED_BUILTIN_TOOLS`)
|
### Tool whitelist (`mcp::ALLOWED_BUILTIN_TOOLS`)
|
||||||
|
|
||||||
- Allowed built-ins: `Bash`, `Edit`, `Glob`, `Grep`, `Read`,
|
- Allowed built-ins: `Bash`, `Edit`, `Glob`, `Grep`, `Read`, `Write`.
|
||||||
`TodoWrite`, `Write`.
|
|
||||||
- Denied by omission: `WebFetch`, `WebSearch`, `Task`,
|
- Denied by omission: `WebFetch`, `WebSearch`, `Task`,
|
||||||
`NotebookEdit`.
|
`NotebookEdit`, `TodoWrite`.
|
||||||
- Allowed MCP tools: as listed above per flavor.
|
- Allowed MCP tools: as listed above per flavor.
|
||||||
|
|
||||||
`Bash` is on the allow-list pending a finer-grained pattern allow-list
|
By default `Bash` is approved wholesale — any shell command runs
|
||||||
(`Bash(git *)`-style) — see [issue #21](http://localhost:3000/hyperhive/hyperhive/issues/21).
|
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.
|
||||||
|
|
|
||||||
|
|
@ -1269,12 +1269,30 @@ pub fn allowed_mcp_tools(flavor: Flavor) -> Vec<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Combined allow-list passed to `--allowedTools` (auto-approve) — covers
|
/// Combined allow-list passed to `--allowedTools` (auto-approve) — covers
|
||||||
/// both the built-ins and the MCP surface.
|
/// 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.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn allowed_tools_arg(flavor: Flavor) -> String {
|
pub fn allowed_tools_arg(flavor: Flavor) -> String {
|
||||||
let mut all: Vec<String> = ALLOWED_BUILTIN_TOOLS
|
let mut all: Vec<String> = ALLOWED_BUILTIN_TOOLS
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| (*s).to_owned())
|
.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()]
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
all.extend(allowed_mcp_tools(flavor));
|
all.extend(allowed_mcp_tools(flavor));
|
||||||
all.join(",")
|
all.join(",")
|
||||||
|
|
@ -1287,6 +1305,13 @@ pub fn builtin_tools_arg() -> String {
|
||||||
ALLOWED_BUILTIN_TOOLS.join(",")
|
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
|
/// Where the NixOS module writes the per-agent extra-MCP spec (see
|
||||||
/// `nix/templates/harness-base.nix`). Each entry becomes an additional
|
/// `nix/templates/harness-base.nix`). Each entry becomes an additional
|
||||||
/// `mcpServers.<key>` block in the rendered claude config + a
|
/// `mcpServers.<key>` block in the rendered claude config + a
|
||||||
|
|
@ -1357,9 +1382,26 @@ fn default_allowed_tools() -> Vec<String> {
|
||||||
vec!["*".to_owned()]
|
vec!["*".to_owned()]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read + parse the extra-MCP spec. Returns an empty map on missing /
|
/// Read + parse the Bash command allow-list. Returns an empty vec when
|
||||||
/// unparsable file (the agent has none configured, or the file is
|
/// the file is missing or unparsable (degrade to wholesale `Bash`
|
||||||
/// malformed — both cases degrade to "no extra servers").
|
/// 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").
|
||||||
fn load_extra_mcp() -> std::collections::BTreeMap<String, ExtraMcpServer> {
|
fn load_extra_mcp() -> std::collections::BTreeMap<String, ExtraMcpServer> {
|
||||||
let Ok(raw) = std::fs::read_to_string(EXTRA_MCP_PATH) else {
|
let Ok(raw) = std::fs::read_to_string(EXTRA_MCP_PATH) else {
|
||||||
return std::collections::BTreeMap::new();
|
return std::collections::BTreeMap::new();
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,29 @@
|
||||||
# only opts in from its own `agent.nix`.
|
# only opts in from its own `agent.nix`.
|
||||||
imports = [ ./weston-rdp.nix ];
|
imports = [ ./weston-rdp.nix ];
|
||||||
|
|
||||||
|
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 {
|
options.hyperhive.allowedRecipients = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default = [ ];
|
default = [ ];
|
||||||
|
|
@ -170,6 +193,9 @@
|
||||||
config = {
|
config = {
|
||||||
environment.etc."hyperhive/extra-mcp.json".text = builtins.toJSON config.hyperhive.extraMcpServers;
|
environment.etc."hyperhive/extra-mcp.json".text = builtins.toJSON config.hyperhive.extraMcpServers;
|
||||||
|
|
||||||
|
environment.etc."hyperhive/bash-allow.json".text =
|
||||||
|
builtins.toJSON config.hyperhive.allowedBashPatterns;
|
||||||
|
|
||||||
environment.etc."hyperhive/send-allow.json".text =
|
environment.etc."hyperhive/send-allow.json".text =
|
||||||
builtins.toJSON config.hyperhive.allowedRecipients;
|
builtins.toJSON config.hyperhive.allowedRecipients;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue