181 lines
6.5 KiB
Go
181 lines
6.5 KiB
Go
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:]
|
||
}
|