feat(security): 敏感清单补齐 design/11 §4 的 7 类(写数据/对外发消息/访问密钥/改配置,结构化判据)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

This commit is contained in:
AskaEth committed 2026-10-05 11:53:17 +08:00
1 parent 509dc412eb
commit 85abb90067
6 files changed
+813 -18

No files matched your search

@@ -0,0 +1,180 @@
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:]
}