package permission // 规则解析和管理. // // 负责将配置中的权限规则字符串(如 "Bash(prefix:npm)")解析为 Rule 结构体, // 以及从 Settings 加载完整的规则集合. // // 规则格式: // - "ToolName" - 匹配该工具的所有调用 // - "ToolName(prefix:xxx)" - 匹配以 xxx 开头的命令(Bash 工具) // - "ToolName(/path/**)" - 匹配文件路径 glob(Edit/Write/Read 工具) // - "ToolName(domain:xxx)" - 匹配域名(WebFetch 工具) // - "*" - 匹配所有工具 import ( "strings" ) // ContentType 是规则内容的匹配类型. type ContentType string const ( ContentNone ContentType = "" // 无内容条件,匹配该工具的所有调用 ContentPrefix ContentType = "prefix" // 前缀匹配(Bash 命令) ContentPath ContentType = "path" // 路径 glob 匹配(文件操作工具) ContentDomain ContentType = "domain" // 域名匹配(WebFetch 工具) ) // ParsedContent 是解析后的规则内容. type ParsedContent struct { Type ContentType // 内容类型 Value string // 匹配值(前缀字符串,glob 模式,域名) } // ParseContent 解析规则的 Content 字段. // // 示例: // - "" → {Type: ContentNone} // - "prefix:npm" → {Type: ContentPrefix, Value: "npm"} // - "/src/**" → {Type: ContentPath, Value: "/src/**"} // - "domain:example.com" → {Type: ContentDomain, Value: "example.com"} func ParseContent(content string) ParsedContent { if content == "" { return ParsedContent{Type: ContentNone} } // 前缀匹配:prefix:xxx if strings.HasPrefix(content, "prefix:") { return ParsedContent{ Type: ContentPrefix, Value: strings.TrimPrefix(content, "prefix:"), } } // 域名匹配:domain:xxx if strings.HasPrefix(content, "domain:") { return ParsedContent{ Type: ContentDomain, Value: strings.TrimPrefix(content, "domain:"), } } // 路径 glob 匹配:以 / 或 . 开头,或包含 * 通配符 if strings.HasPrefix(content, "/") || strings.HasPrefix(content, ".") || strings.Contains(content, "*") { return ParsedContent{ Type: ContentPath, Value: content, } } // 无法识别的内容,当作前缀处理 return ParsedContent{ Type: ContentPrefix, Value: content, } } // ParseRule 解析规则字符串为 Rule 结构体. // // 规则格式:ToolName 或 ToolName(content) // // 示例: // - "Bash" → Rule{ToolName: "Bash", Content: ""} // - "Bash(prefix:npm)" → Rule{ToolName: "Bash", Content: "prefix:npm"} // - "Edit(/src/**)" → Rule{ToolName: "Edit", Content: "/src/**"} // - "*" → Rule{ToolName: "*", Content: ""} func ParseRule(ruleStr string, source RuleSource, behavior Decision) Rule { ruleStr = strings.TrimSpace(ruleStr) // 查找括号 parenStart := strings.IndexByte(ruleStr, '(') if parenStart < 0 { // 无括号,纯工具名 return Rule{ Source: source, Behavior: behavior, ToolName: ruleStr, Content: "", } } // 有括号,提取工具名和内容 toolName := ruleStr[:parenStart] // 提取括号内容,去掉尾部的 ')' content := ruleStr[parenStart+1:] if strings.HasSuffix(content, ")") { content = content[:len(content)-1] } return Rule{ Source: source, Behavior: behavior, ToolName: toolName, Content: content, } } // LoadRulesFromSettings 从权限设置中加载规则列表. // // AllowedTools 中的条目解析为 allow 规则, // DeniedTools 中的条目解析为 deny 规则. // source 参数指定规则的来源优先级. func LoadRulesFromSettings(allowed []string, denied []string, source RuleSource) []Rule { rules := make([]Rule, 0, len(allowed)+len(denied)) for _, ruleStr := range allowed { rules = append(rules, ParseRule(ruleStr, source, DecisionAllow)) } for _, ruleStr := range denied { rules = append(rules, ParseRule(ruleStr, source, DecisionDeny)) } return rules } // SerializeRule 将规则序列化为字符串. // // 示例: // - Rule{ToolName: "Bash", Content: "prefix:npm"} → "Bash(prefix:npm)" // - Rule{ToolName: "*", Content: ""} → "*" func SerializeRule(rule Rule) string { if rule.Content == "" { return rule.ToolName } return rule.ToolName + "(" + rule.Content + ")" } // SourcePriority 返回规则来源的优先级数值. // 数值越大,优先级越高. func SourcePriority(source RuleSource) int { priorities := map[RuleSource]int{ SourceUser: 0, SourceProject: 1, SourceLocal: 2, SourceFlag: 3, SourcePolicy: 4, SourceCLI: 5, SourceSession: 6, } if p, ok := priorities[source]; ok { return p } return -1 }