Documentation
¶
Overview ¶
Package permgate 工具调用许可判定链:谁在什么模式下对什么输入得到 allow/ask/deny。对标 ZCode permission/service.ts 的优先级状态机——顺序是 踩坑固化的,别调换:
requiresUserInteraction → ask(交互型工具天然要用户) alwaysAsk 硬阻断段 → disallowed/project-deny 仍压过「问」(被硬禁的 工具不能退化成「弹窗一点就能跑」) alwaysAsk 会话授权段 → 命中即放行(钥匙是本会话的授权,不看模式) alwaysAsk 只读拦截段 → 只读档的非只读工具直接拒(不给「问」的机会) disallowed → deny project deny → deny 配置白名单 → allow(break-glass 位次,issue #58:运维显式加白是「比模式 更强的信任」,压过只读档与 project ask;硬禁仍在它前面——白名单不是 绕禁的后门) 只读拦截 → deny(只读档的非只读工具直接拒——「禁」压过「问」) yolo → 放行(alwaysAsk 与一切「禁」都在它前面——yolo 跳过的是「问」不是「禁」) project ask → ask 只读模式 → 只读工具放行、其余拦下 project allow → allow 预批钩子 → allow(WebFetch URL 白名单类) 兜底 → 会话授权、只读工具放行,其余 ask
「只读拦截」段是「只读模式」段的前置影子:只读档对非只读工具是「禁」不是 「问」——它必须压过 alwaysAsk 与 project ask 两处「问」,否则只读退化成 「换个姿势问」(issue #24)。它与后段同 RuleID/Reason,审计口径不变; mode 之间互斥,故它与 yolo 段无交集。唯一的例外是配置白名单(#58 的 break-glass):只读档默认禁写,但运维对单个工具的显式加白压得过模式档 (下游不再需要在 permgate 之外私设一层本地 override,授权真相只有一份)。
与 agentkit policy(操作审计门:四模式裁决矩阵)正交:policy 裁「业务操作 该不该做」,permgate 裁「这次工具调用要不要人确认」。
规则内容匹配(对标 matchesRuleContent):精确 / `prefix:*` 前缀(prefix 或 prefix+空白开头——空白含换行,换行同属命令分隔)/ `*` 通配(跨行透明)。 匹配对象从工具输入提取(字段名按 Service.SubjectKeys 清单,缺省 command/url/file_path/path/pattern/patch_text,或字符串输入本身);类型化 输入(map[string]string、struct 及其字段/json tag)同样参与提取(#65/#72: 此前这些形态静默落空,内容级 deny 对类型化调用方全部失配——fail-open)。
⚠️ subject 键清单是 ZCode 的领域词汇惯例(#86):上述 6 键只是缺省值,不是 普遍契约。工具输入 schema 用其他字段名(cmd/script/target …)的宿主**必须** 配置 Service.SubjectKeys 声明自己的匹配对象字段——清单外字段是匹配盲区, 内容级 deny 规则对它们静默失效(规则还在配置里、审计显示「未命中 deny」, 实为死字且零报错,是 deny 规则最坏的失效形态)。为此 deny 档的失配另有 fail-loud 出口:输入是 map/struct 形态却提取不出任何 subject 面时,经 Service.OnConfigWarn 回调显式暴露(allow/ask 失配落 ask 兜底本就保守, 不告警)。工具名等价(Edit 规则匹配 Write 的写面特例)同样外置: Service.ToolEquivalence 钩子,nil = DefaultToolEquivalence(即原特例)。
复合命令(#62/#71):command 形态的匹配对象先按 shell 操作符(`&&` `||` `;` `|` 换行,引号内不拆)拆段,段级命中的量词按规则极性取 fail-closed 侧 ——allow/会话授权要求**每段**都命中(`git status && curl evil` 不得搭 `git status:*` 的车),deny/ask 只要**任一段**命中(复合命令里混进一段 危险命令即拦)。不追求完整 shell 语法:命令替换(`$(…)`、反引号)等真 AST 形态归 tools/bashguard,本包只做 stdlib 操作符切分,拆不动的形态宁可 让 allow 失配(落 ask),不得让操作符后的命令搭车放行。
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DefaultToolEquivalence ¶ added in v0.23.0
DefaultToolEquivalence 写面等价的缺省实现(#86 外置;对标 ZCode 同款特例): Edit 规则匹配 Write 调用——两个工具是同一「写文件」入口的两种形态,针对 Edit 书写的规则对 Write 同样生效。**单向**:Write 规则不匹配 Edit(规则按 其声明的入口书写)。a 为规则声明的工具名,b 为实际调用的工具名;精确相等 不经本函数(matchToolName 先判)。宿主工具命名不同(EditFile/ApplyPatch …) 时经 Service.ToolEquivalence 换上自己的等价语义。
Types ¶
type Capability ¶
type Capability struct {
// ReadOnly 只读工具(读后无副作用)。
ReadOnly bool
// AlwaysAsk 工具自报必须逐次确认(不可被模式/白名单绕过,只可被硬阻断
// 与会话授权短路)。
AlwaysAsk bool
// RequiresUserInteraction 用户交互型工具(AskUserQuestion 类)。
RequiresUserInteraction bool
}
Capability 工具能力自报(注册期声明)。
type Rule ¶
type Rule struct {
// ToolName 工具名;等价工具名按 Service.ToolEquivalence 判定(nil =
// DefaultToolEquivalence:Edit 规则匹配 Write 的写面等价,ZCode 特例)。
ToolName string
// Content 输入内容模式(command/url/路径):空 = 该工具全部调用;
// "git status:*" 前缀(空白边界,命令形态专用);含 * 通配;否则精确等值。
// ⚠ 路径规则用 wildcard 形态("/repo/data/*")而非前缀形态——前缀边界是
// 空白/制表符,对路径子树形同虚设;路径 subject 匹配前会 filepath.Clean
//(`..` 穿越不命中),符号链接围栏归执行器。
Content string
}
Rule 单条许可规则。
type Service ¶
type Service struct {
// AllowedTools 配置级白名单(工具名集合)。break-glass 位次(#58):
// 运维显式加白压过只读档与 project ask,但压不过 disallowed/project deny。
AllowedTools map[string]bool
// DisallowedTools 配置级黑名单(压过 yolo、alwaysAsk 的确认段与白名单)。
DisallowedTools map[string]bool
// ProjectRules 项目/产品级规则集。
ProjectRules Ruleset
// Preapprove 预批钩子(WebFetch URL 白名单类):返回非空 ruleID 即放行。
// 判定位次在项目 allow 之后、配置白名单之后(plan 拦截压得过预批)。
Preapprove func(ctx context.Context, tool string, input any) (ruleID string, ok bool)
// SubjectKeys 工具输入里参与内容匹配的字段名清单(#86)。零值/空 = 内置
// 6 键 command/url/file_path/path/pattern/patch_text(ZCode 领域词汇惯例,
// 向后兼容)。⚠️ 工具输入 schema 用其他字段名(cmd/script/target …)的
// 宿主**必须**配置本清单:清单外字段是匹配盲区,内容级规则对它们静默
// 失配;deny 规则的这类失配经 OnConfigWarn fail-loud。清单按声明顺序
// 提取;形态特判跟随键名本身——名为 command 的键仍按复合命令拆段、
// file_path/path 仍先 filepath.Clean,清单里的自定义键按单段原样匹配
// (命令形态的字段建议沿用内置名 command,或由宿主拆段后匹配)。
SubjectKeys []string
// ToolEquivalence 工具名等价钩子(#86):a 为规则声明的工具名、b 为实际
// 调用的工具名,等价(规则应覆盖该调用)返回 true——精确相等无需钩子。
// nil = DefaultToolEquivalence(Edit 规则匹配 Write 的写面等价,ZCode
// 特例降级为缺省实现)。宿主工具命名不同(EditFile/ApplyPatch …)时
// 声明自己的写面等价,不必为每个变体名重复配规则。钩子是**替换**不是
// 叠加:配置后 ZCode 特例不再自动生效,需要就写进自己的实现里。
ToolEquivalence func(a, b string) bool
// OnConfigWarn 配置疑点告警回调(#86 fail-loud):本包无日志依赖,宿主
// 经它接自己的日志/计数面。当前唯一告警点——deny 内容规则在「输入是
// map/struct 形态但提取不出清单内任何 subject 字段」的调用上无法评估:
// 该规则对此调用形同虚设,而后续档位可能放行。默认 no-op(零值可用)。
// 回调在 Check 路径上同步触发且可能并发,必须轻量并自行保证并发安全;
// 告警频度(同一规则反复触发)由宿主去重。allow/ask 失配落 ask 兜底
// 本就保守,不告警。
OnConfigWarn func(msg string)
// contains filtered or unexported fields
}
Service 许可判定器。零值可用(全部走兜底档)。
并发契约(issue #66):Check/AllowForSession/ResetSession/Update/SessionAllows 之间并发安全——Check 全程只读锁内快照,Update 在锁内整体换配置。导出的 配置字段(AllowedTools/DisallowedTools/ProjectRules/Preapprove/SubjectKeys/ ToolEquivalence/OnConfigWarn)视为**装配期字段**:直接赋值只允许发生在并发 使用开始前;运行期热更新一律经 Update 换**新值**(不得原地改切片元素—— 快照共享底层数组,原地改与并发读仍构成竞争),也不得在 Update 回调里再调 本包方法(非重入锁,会死锁)。
func (*Service) AllowForSession ¶
AllowForSession 会话级授权登记(用户对 ask 选择「本会话总是允许」时调用)。 同 (tool, content) 重复登记折叠(#70):规则表=授权真相——N 次「总是允许」 不该积累 N 条相同规则(线性膨胀稀释审计面)。
func (*Service) Check ¶
func (s *Service) Check(ctx context.Context, tool string, input any, mode Mode, cap Capability) Decision
Check 判定一次工具调用。input 为工具入参(string、map[string]any、 map[string]string 或 struct 等类型化形态——内容字段按 subjectKeys 名单提取)。 分段优先级见包注释——顺序是踩坑固化的,别调换。
func (*Service) ResetSession ¶
func (s *Service) ResetSession()
ResetSession 会话结束清空会话授权(实例消亡语义的显式形态)。
func (*Service) SessionAllows ¶ added in v0.20.0
SessionAllows 本会话已授权清单快照(#70 审计面):锁内深拷贝,调用方可安全 遍历/导出;空清单返回 nil。