Documentation
¶
Overview ¶
Package bashguard bash 命令静态风险解析:不执行命令,经真 shell AST (mvdan.cc/sh)解析成调用序列,按策略表判定只读性。对标 ZCode bash-command-parser.ts + bash-readonly-policy 的「权限判定不执行命令」。
判定原则(保守到宁可误报):
- 解析失败、含动态词($() / 反引号 / 未展开 $VAR)、含写重定向、 复合控制流(if/for/while/case/函数定义)→ 一律非只读——无法静态证明 安全的就不放行;
- 调用按策略表:命令不在表=未知(非只读);命中危险 flag=非只读; 未知 flag=非只读;值型 flag 吃一个参数后继续;
- 安全包装(env/nice/nohup/time/timeout/stdbuf/xargs)剥壳后判定内核;
- 复合命令(&& || ; |)每一段都必须只读,整体才只读。
三档:只读 / 写 / 不可逆 ¶
只读判定回答「会不会改东西」;**不可逆判定**(Decision.Destructive)回答 「一旦跑就收不回来」——两者正交:rm file 不是只读但可逆,rm -rf / 不可逆。 不可逆判据走**独立的一张表**(Table.Destructive,见 destructive.go): 只读表只放只读命令,而不可逆判据要覆盖 rm / mkfs / dd 这类写命令。 「要不要二次确认」是调用方的策略决定;本包给的是「这条命令不可逆」这个事实。
上限 10K 字符(对标 ZCode:超长命令不进解析器)。超长路径带 Unevaluated 标记 + 头/尾窗 best-effort 不可逆扫描——不静默 fail-open。
缺省只读策略表:保守起步集——不在表=非只读;生产按需经 Table 自定义扩展 (对标 ZCode 的表驱动形态:每命令声明安全 flag 及其值形态)。
Index ¶
Constants ¶
const MaxCommandLen = 10 * 1024
MaxCommandLen 解析上限。
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Decision ¶
type Decision struct {
// ReadOnly 命令可判只读(不执行任何写路径)。
ReadOnly bool
// Reason 非只读原因(人可读,审计用)。
Reason string
// Destructive 该调用含不可逆动作(删数据 / 写设备 / 关机 / 递归改权限 …)。
//
// 与 ReadOnly **正交**:只读与否是另一档。一个命令可以「不是只读、但可逆」
//(rm file),也可以「不是只读、且不可逆」(rm -rf /)。
//
// 语义边界:false 只意味着「没命中已知规则」,**不意味着安全**——
// 是否要二次确认是调用方的策略决定,本包只给「这条命令不可逆」这个事实。
Destructive bool
// DestructiveReason 不可逆依据(人可读,审计用);未命中时为空。
DestructiveReason string
// Unevaluated 命令未进 AST 判定(超长未进解析器 / 解析失败,tools#45)。
//
// 此时 ReadOnly=false 只是保守拒,**Destructive=false 不再意味着「判了
// 不不可逆」**——未评估就是不可判:「判据不可判」与「判了不不可逆」不得
// 压成同一个信号。调用方对未评估输入须按保守侧处理(如照常要二次确认),
// 不得当「已判安全」。超长路径另有头/尾窗 best-effort 扫描兜底(见
// overlongDecision):窗口内命中的不可逆动作照常置 Destructive。
Unevaluated bool
// Invocations 提取的调用序列(观测/风险面)。
//
// ⚠️ 这是**判定路径**的消费序列,不是全量事实:判定在首个非只读调用上
// 提前返回(Analyze 的 break),其后的调用不再评估;包装剥壳后判定的是
// 内核,调用面记录的是包装(sudo -n rm 只见 sudo)。做确认门禁/风险审计
// 请用 AllCalls(见其注释)。
Invocations []Invocation
// AllCalls 全 AST 的 simple command 集合(syntax.Walk,oagentkit/tools#35):
// 管道后半、命令替换 $(…)、子 shell (…)、{ …; } 里的调用**全部可见**,
// 与判定路径是否提前返回无关。
//
// 与 Invocations 的分工:Invocations 服务 ReadOnly/Destructive 判定的
// 归因(为何判非只读);AllCalls 服务「这条命令文本里**出现了什么**」的
// 风险面事实——jumpserver-kit 此前为补这个集合在守卫层自接了一份
// syntax.Walk(判定门禁的绕过全堵上依赖它),重复实现会漂、漂的那份
// 决定门禁拦什么,故上游收编。
//
// 语义边界(调用方须知):
// - 包装不剥壳:sudo -n rm -rf /x 是一条调用(sudo,args 含 rm…)——
// sudo/xargs/env 的内核是**参数**不是语法调用,消费方按需自剥
// (本包自身的不可逆扫描已对未注册调度器做内核兜底,见
// destructive.go 的 destructiveKernel);
// - eval "rm -rf /x" 的 rm 是字符串数据不是语法调用,只见 eval;
// - Dynamic=true 的调用(参数含 $()/反引号/$VAR)同样入列——动态参数
// 本身就是风险信号。
AllCalls []Invocation
}
Decision 判定结果。
type Invocation ¶
Invocation 提取的单次调用(观测面)。
type Policy ¶
type Policy struct {
// ReadOnly 命令本体是否只读。
ReadOnly bool
// SafeFlags 已知安全 flag(含 - 前缀,如 "-n")。长 flag 单写。
SafeFlags map[string]FlagKind
// UnsafeFlags 命中即非只读(显式优先于 SafeFlags)。
UnsafeFlags []string
// Subcommands 子命令策略表(git 形态,可嵌套):子命令不在表=非只读。
Subcommands map[string]Policy
// Wrapper 安全包装(env/timeout 形态):剥壳后按剩余首参判定。
Wrapper bool
// ConsumeBare Wrapper 剥 flag 后先吃掉的前置裸操作数个数(timeout 的时长)。
ConsumeBare int
// NoBareOperands 裸操作数即写形态(git branch <名> 创建 / git stash 裸 push /
// git tag <名> 打标):出现任何 flag 之外的裸操作数 → 非只读。
NoBareOperands bool
// AllowBareOperandsPrefix NoBareOperands 的豁免前缀(date 的 +FORMAT 形态:
// 裸操作数以 "+" 开头是格式串不是写目标)。前缀空串不放行任何操作数。
AllowBareOperandsPrefix []string
// Destructive 该命令恒不可逆(删数据 / 写设备 / 关机 …)。
//
// 只在**不可逆规则表**(Table.Destructive)里有意义——只读表是只读表,
// 不可逆判据单列一张(见 destructive.go 的说明)。
Destructive bool
// DestructiveFlags 命中即不可逆的 flag("-r" / "--recursive")。
// 短 flag 支持联合形态(-rf 里的 r)。
DestructiveFlags []string
// DestructiveArgs 命中即不可逆的参数(**前缀**匹配:"of=" 命中 "of=/dev/sda")。
DestructiveArgs []string
}
Policy 单命令策略。
type RiskTier ¶ added in v0.13.0
type RiskTier string
RiskTier 命令风险档位(单源枚举,tools#48)。
档位体系只此一份:消费方(如自带 MCP 工具风险注册表的宿主)的 per-tool 档位应引用同一组枚举,工具专属的不可逆规则注册进 Table(Destructive 面) 而不是另立一张平行表——两张表各判各的必然漂移:jms-mcp(堡垒机执行 MCP, 不可逆操作是核心安全面)曾锁 tools v0.10.4,错过 v0.11.0 的不可逆档与 v0.12.0 的 AllCalls,其手维护的 20+ 条 per-tool Risk_destructive 注册表与 命令层 bashguard 判定双源漂移,不可逆拦截面出现缝隙。命令提取统一走 Decision.AllCalls(全 AST 调用集,Analyze 已对其做不可逆扫描),不要手写。
粗粒度风险级(low/medium/high,对标 mcpkit ToolHints.RiskLevel)的映射: readonly→low、write→medium、destructive/unevaluated→high(保守侧)。
const ( // RiskReadOnly 只读档:不执行任何写路径。 RiskReadOnly RiskTier = "readonly" // RiskWrite 写档:非只读(写路径或未知命令)但未命中不可逆判据。 // 注意未知命令并未被证明「可逆」,保守调用方可视同待确认。 RiskWrite RiskTier = "write" // RiskDestructive 不可逆档:一旦跑就收不回来——二次确认的判据。 RiskDestructive RiskTier = "destructive" // RiskUnevaluated 未评估档:未进 AST 判定(超长/解析失败)——保守侧, // 调用方按需与不可逆档同处置(要确认)。 RiskUnevaluated RiskTier = "unevaluated" )