Files
Cyrene_v2/internal/capability/restrict/sensitive_paths.go
T
AskaEth 85abb90067
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(security): 敏感清单补齐 design/11 §4 的 7 类(写数据/对外发消息/访问密钥/改配置,结构化判据)
2026-10-05 11:53:17 +08:00

181 lines
6.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package restrict
import "strings"
// 本文件是 design/11 §4 敏感清单里**按文件落点**分类的结构化判据表:同一个
// `file` 调用,落点不同则操作类别不同——写配置件是「改配置与权限」、写数据
// 存储件是「写数据」、写别处是「修改文件」;读密钥件是「访问密钥与隐私」,
// 读别处仍可自主(design/11 §4「非敏感(读文件 / 检索 / 查记忆)可自主」)。
//
// 判据只有**路径字符串**这一个输入(工具名 + 参数),不含任何模型文本
// (design/11 §0 红线:身份与权限永不来自提示词)。
// 配置与权限的落点(改配置与权限)。这些文件承载模型/渠道/工具策略,改动它们
// 等于改动实例的行为与权限边界,因此比普通「修改文件」更敏感:
// - `.env`(含 `.env.*`):实例级开关与凭据,design/04 §4 的「.env.example 同步」对象;
// - `models.json` / `channels.json`:模型路由与渠道凭据(design/04 §7、design/08);
// - `mcp.json` / `plugins.json` / `policy.json` / `permissions.json` / `acl.json`:
// MCP server、插件与工具策略(design/11 §3 策略落盘的相邻载体)。
var configArtifactBases = map[string]bool{
".env": true,
"models.json": true,
"channels.json": true,
"mcp.json": true,
"plugins.json": true,
"policy.json": true,
"permissions.json": true,
"acl.json": true,
}
// configDirSegments 是「目录即配置区」的判据:这些目录下的 `.yaml/.yml/.toml/
// .ini/.conf` 都是实例配置(角色定义、技能定义、用户档),改动同样归入
// 改配置与权限,而不是普通改文件。
var configDirSegments = map[string]bool{
"configs": true,
"config": true,
"personas": true,
"users": true,
"skills": true,
}
// configFileExts 是配置区里作为配置对待的扩展名。
var configFileExts = map[string]bool{
".yaml": true,
".yml": true,
".toml": true,
".ini": true,
".conf": true,
}
// 密钥与隐私的落点(访问密钥与隐私)。这些文件里的内容**不许进日志、不许进
// 模型上下文**(design/04 §4「models.json/channels.json 含 token/key(敏感)→
// 不打印、不进日志」),所以连「读」都算敏感:
// - `.env`(含 `.env.*`):凭据;
// - `models.json` / `channels.json`:供应商与渠道 token/key;
// - 密钥材料:`*.pem` / `*.key` / `*.p12` / `*.pfx` / `*.jks` / `*.keystore` /
// `*.ppk`,以及 SSH 私钥 `id_rsa*` / `id_dsa*` / `id_ecdsa*` / `id_ed25519*`;
// - 凭据库:`.netrc` / `.npmrc` / `.git-credentials` / `credentials*.json` /
// `secrets*.json`,以及 `secrets` / `credentials` 目录下的任何文件。
var secretArtifactBases = map[string]bool{
".env": true,
"models.json": true,
"channels.json": true,
".netrc": true,
".npmrc": true,
".git-credentials": true,
"credentials": true,
"credentials.json": true,
"credential.json": true,
"secrets.json": true,
"secret.json": true,
"passwd": true,
"shadow": true,
}
// secretArtifactPrefixes 按前缀命中的密钥件(如 `id_rsa` / `id_ed25519.pub` 之外
// 的私钥族)——`id_rsa.pub` 是公钥,不含秘密,故单独排除。
var secretArtifactPrefixes = []string{"id_rsa", "id_dsa", "id_ecdsa", "id_ed25519"}
// secretArtifactExts 是密钥材料的扩展名。
var secretArtifactExts = map[string]bool{
".pem": true,
".key": true,
".p12": true,
".pfx": true,
".jks": true,
".keystore": true,
".ppk": true,
}
// secretDirSegments 是「目录本身即密钥区」的判据。
var secretDirSegments = map[string]bool{"secrets": true, "credentials": true}
// dataStoreSuffixes 是数据存储的落点(写=写数据,读=访问密钥与隐私:库里是
// 主人的会话、记忆与审计数据,design/16 §5「敏感:本地 SQLite」)。SQLite 的
// WAL/SHM 同伴文件同属该存储。
var dataStoreSuffixes = []string{".db", ".sqlite", ".sqlite3", ".db-wal", ".db-shm", ".sqlite-wal", ".sqlite-shm"}
// isConfigArtifact 报告路径是否落在配置与权限的载体上(design/11 §4)。
func isConfigArtifact(p string) bool {
segs, base := splitPath(p)
if base == "" {
return false
}
if base == ".env" || strings.HasPrefix(base, ".env.") || configArtifactBases[base] {
return true
}
return hasSegment(segs, configDirSegments) && extOf(base) != "" && configFileExts[extOf(base)]
}
// isSecretArtifact 报告路径是否落在密钥与隐私的载体上(design/11 §4)。
func isSecretArtifact(p string) bool {
segs, base := splitPath(p)
if base == "" {
return false
}
if base == ".env" || strings.HasPrefix(base, ".env.") || secretArtifactBases[base] {
return true
}
if secretArtifactExts[extOf(base)] {
return true
}
for _, prefix := range secretArtifactPrefixes {
if strings.HasPrefix(base, prefix) && !strings.HasSuffix(base, ".pub") {
return true
}
}
return hasSegment(segs, secretDirSegments)
}
// isDataStoreArtifact 报告路径是否是数据存储文件(SQLite 及同伴文件)。
func isDataStoreArtifact(p string) bool {
_, base := splitPath(p)
if base == "" {
return false
}
for _, suffix := range dataStoreSuffixes {
if strings.HasSuffix(base, suffix) {
return true
}
}
return false
}
// splitPath 把路径规范化成小写分段与末段:同时接受 Windows 反斜杠与 POSIX
// 斜杠,丢弃空段(因此 `data//models.json` 与 `/data/models.json` 判成同一类)。
// 这里刻意只做**词法**规范化,与 file 工具的沙箱收敛/文件级互斥同口径:不做
// 符号链接解析,也不假装能识别 8.3 短名等物理同一写法。
func splitPath(p string) ([]string, string) {
s := strings.ToLower(strings.TrimSpace(p))
s = strings.ReplaceAll(s, "\\", "/")
segs := make([]string, 0, 4)
for _, seg := range strings.Split(s, "/") {
if seg != "" {
segs = append(segs, seg)
}
}
if len(segs) == 0 {
return nil, ""
}
return segs, segs[len(segs)-1]
}
// hasSegment 报告分段里是否出现集合中的任一名字(目录判据用)。
func hasSegment(segs []string, set map[string]bool) bool {
for _, seg := range segs {
if set[seg] {
return true
}
}
return false
}
// extOf 返回小写扩展名(含点);无扩展名返回 ""。
func extOf(base string) string {
i := strings.LastIndex(base, ".")
if i <= 0 {
return ""
}
return base[i:]
}