harness - BashToolStrategy 安全审计策略
当前 Bash 审计策略类是 BashToolStrategy,它实现 Agent 模块的 HITLStrategy:
public class BashToolStrategy implements HITLStrategy {
@Override
public String evaluate(ReActTrace trace, Map<String, Object> args) {
// 返回 null:放行
// 返回非空字符串:进入 HITL,字符串作为审核原因
}
}
Harness 默认把该策略绑定到 bash。但 HITL 默认关闭;要使它对 Harness Agent 生效,还需启用 hitlEnabled,并在 Agent 工具配置中包含 hitl:
HarnessEngine engine = HarnessEngine.of("work", ".soloncode/")
.sessionProvider(sessionProvider)
.toolsAdd("bash", "hitl")
.hitlEnabled(true)
.build();
1、真实评估流程
BashToolStrategy.evaluate() 当前分两步:
- 按注册顺序直接检查内置 matcher;命中后立即返回放行或拦截结果。
- 未直接命中时,把权限上下文中的规则与内置规则交给
PermissionEngine评估。
PermissionEngine 的排序规则是:
priority越大越优先;- 同一优先级下,
DENY > ALLOW > ASK; - 工具名
*匹配所有工具,其他工具名采用忽略大小写的精确匹配; - 参数规则可使用 glob pattern 或自定义 matcher。
需要特别注意默认值:
PermissionEngine.evaluate(toolName, args, context)三参数重载未命中时默认返回ASK;BashToolStrategy调用的是带defaultDecision的四参数重载,而它自身的defaultDecision默认为PermissionDecision.ALLOW。
因此,当前 Bash 策略未命中任何规则时默认放行,不能把它描述为“所有非只读命令均要求人工确认”。如果需要默认询问,可显式配置:
BashToolStrategy bashStrategy = new BashToolStrategy()
.defaultDecision(PermissionDecision.ASK);
2、内置 Bash 规则
源码中的内置规则如下,并非所谓“七层安全体系”。
2.1 空命令
command 为空时直接放行,即 evaluate() 返回 null。这与单独调用 BashCommandClassifier.classify("") 得到 DENY 的行为不同;BashToolStrategy 没有直接使用 classify() 的空命令分支。
2.2 字符串风险模式
命令字符串包含以下任一片段时拒绝:
- 反引号
` $(/dev/
提示为“检测到潜在的命令注入或设备重定向风险。”。这是字符串包含判断,不是完整的 Shell 语法分析。
2.3 系统特权命令
当前正则只检查:
sudo, su, chown, chmod, chgrp, passwd, visudo
命中后返回包含原命令的“检测到系统特权指令”提示。当前源码没有把 kill、pkill、systemctl、service 等进程或服务命令加入此规则。
2.4 路径回溯
命令字符串包含 ../ 或 ..\ 时拒绝。该规则是字面字符串检查,不是对规范化文件路径执行真实边界解析。
2.5 敏感路径
命令字符串包含以下任一片段时拒绝:
/etc/
/var/
/root/
~/.ssh/
~/.bashrc
~/.zshrc
同样,这些是字符串包含规则。
2.6 只读命令分类
BashCommandClassifier 维护只读/搜索命令集合,包括:
- 搜索:
grep、egrep、fgrep、rg、ag、ack、find、fd、locate - 目录:
ls、dir、tree、exa - 文件查看:
cat、bat、less、more、head、tail - 信息与统计:
wc、file、which、whereis、whence、type、stat、du、df - 输出与文本处理:
echo、printf、diff、comm、sort、uniq、cut、tr、awk、sed - 格式化:
jq、yq、xmllint - 系统信息:
pwd、env、printenv、id、whoami、hostname、uname、date、cal - Git 只读双词命令:
git log、git show、git diff、git status、git branch、git tag、git remote、git rev-parse、git ls-files、git blame、git shortlog
这些命令仍先经过前述风险规则。例如 cat /etc/passwd 会先命中敏感路径规则,不能因为 cat 在只读集合中就放行。
只读分类还有两个例外:
sed -i、sed --in-place等原地修改形式不算只读;git branch -d、git branch -D、git branch --delete不算只读。
普通输出重定向 >、>> 会使命令不再被归类为只读;当前实现对 2> 做了特殊排除,但这仍不是完整的 Shell 重定向解析。
2.7 管道与不完整命令
包含 | 时,分类器按管道分段;只有每一段都能被识别为只读命令,整个命令才按只读放行。这不是一份独立的“管道安全工具白名单”。
命令尾部是以下符号时,会按“不完整命令”拒绝:
| && || ;
该检查只针对尾部,不代表完整拦截 ;、& 或其他组合命令。
3、当前没有声明的内置硬拦截
不要把以下能力归因于当前 BashToolStrategy:
- 不存在包管理器或环境变更命令的统一硬拦截;
- 不存在
curl、wget、ssh等网络命令的统一硬拦截; - 不存在
rm -rf、mv的专门内置硬拦截; - 不存在完整的多命令组合解析与统一拒绝;
BashCommandClassifier虽定义了 Git 写命令集合和isGitWriteCommand(),但当前BashToolStrategy没有调用它。
这些命令如果没有命中其他内置规则或额外权限规则,会落到 Bash 策略的默认决策;当前默认是 ALLOW。
4、通过 PermissionRule 添加约束
额外规则应通过 PermissionRule / PermissionContext 配置,而不是假定内置策略已经覆盖。
4.1 glob 规则
HarnessEngine engine = HarnessEngine.of("work", ".soloncode/")
.sessionProvider(sessionProvider)
.toolsAdd("bash", "hitl")
.hitlEnabled(true)
.build();
engine.addPermissionRule(
PermissionRule.deny("bash", "rm -rf *").priority(10));
engine.addPermissionRule(
PermissionRule.ask("bash", "git push *").priority(10));
engine.addPermissionRule(
PermissionRule.allow("bash", "git log *").priority(10));
pattern 会针对参数中第一个可用字符串字段匹配,字段顺序为:
command, file_path, path, content, url, link
* 和 ? 按 glob 转换,且使用整串匹配;例如要匹配任意位置的关键词,需要在 pattern 中显式加入 *。
PermissionRule.priority(int)返回新规则,调用结果必须使用。对于自定义 matcher,应直接使用带 priority 参数的PermissionRule.of(...)创建;不要再调用priority(...),因为当前实现会丢失 matcher 与 prompt。
4.2 自定义 matcher
PermissionRule denyNetwork = PermissionRule.of(
"bash",
PermissionBehavior.DENY,
(toolName, args) -> {
Object value = args == null ? null : args.get("command");
if (!(value instanceof String)) {
return false;
}
String command = ((String) value).trim();
return command.startsWith("curl ")
|| command.startsWith("wget ")
|| command.startsWith("ssh ");
},
"网络命令被当前业务策略拒绝。",
200);
engine.addPermissionRule(denyNetwork);
内置 matcher 在第一阶段直接命中时会立即返回。若业务要求完全不同的评估顺序或覆盖语义,更适合注册自定义 HITLStrategy,而不是依赖普通规则覆盖内置直接匹配。
5、自定义 HITLStrategy
HITLStrategy auditStrategy = new HITLStrategy() {
@Override
public String evaluate(ReActTrace trace, Map<String, Object> args) {
Object value = args == null ? null : args.get("command");
String command = value instanceof String ? (String) value : "";
if (command.contains("rm -rf")) {
return "检测到递归删除操作:" + command;
}
if (command.startsWith("curl ") || command.startsWith("wget ")) {
return "网络访问需要人工确认:" + command;
}
return null;
}
};
HITLInterceptor hitl = new HITLInterceptor()
.onTool("bash", auditStrategy);
HarnessEngine engine = HarnessEngine.of("work", ".soloncode/")
.sessionProvider(sessionProvider)
.toolsAdd("bash", "hitl")
.hitlInterceptor(hitl)
.hitlEnabled(true)
.build();
替换 HITLInterceptor 会同时替换 Harness 默认的 Bash 策略和默认 onApproved 记忆回调;需要的行为应自行重新注册。
6、运行时开关
engine.setHitlEnabled(true);
engine.setHitlEnabled(false);
该方法会同步更新 Harness 配置和拦截器的 enabled 状态。它只控制 HITL 是否启用,不会改变 BashToolStrategy 的规则集合或“未命中默认 ALLOW”的行为。