feat(tools): 真实工具声明文件目标(让文件级互斥真正生效)
新增 plugins/tools/io/file_targets.go:fileTool 与 cropTool 实现 tool.FileTargeter (编译期钉住 var _ tool.FileTargeter,声明集中一处可审计)。 file:read/list/search ⇒ 读;其余(含 delete / 未知 / 缺失 action)⇒ 写(保守侧,宁可多挡一次)。 路径取沙箱解析前的声明值(绝对、clean、已确认在 roots 内,纯词法不触盘);沙箱会拒的路径不声明。 search 的缺省 base 与执行路径共用 f.searchBase(避免声明目录 ≠ 实际走查目录)。 image-crop:仅本地路径声明为读(URL/data URI 不落盘);分类与 load 共用一份。 体检清单:file ✅ / image-crop ✅ / document-read ❌(sidecar 走「临时文件 + rename」原子替换, 无撕读风险;且布局属未导出的第二份真相)/ basic、http、knowledge 等不碰盘; vision·ocr(internal 越界)、terminal(cmd 越界 + 目标不可知)、MCP 外部工具 未做 —— 如实登记为遗留。 测试:单元(action→Op 全表 / 路径归一 / 不声明场景)+ 机制层 6 条(真插件 + 真 Dispatcher + 真占用表, 用闸门工具把并发变确定性事件,不靠 sleep)。 变异 5 条(一切按写 / 未知按读 / 用原始相对串 / crop 不声明 / file 声明被抹掉)。 注:本切片声明正确但生效依赖 ef3dcb6(网关给每条在途路挂互斥身份)。
This commit is contained in:
1 parent
ef3dcb699b
commit
3589f4b14f
7 files changed
+842
-15
No files matched your search
@@ -192,13 +192,39 @@ func encodePNG(img image.Image) ([]byte, error) {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// cropSourceKind 是 image 参数的来源分类:只有本地路径会碰文件系统,URL 与 data URI
|
||||
// 都不落盘。分类只此一份 —— 执行分派(load)与目标声明(file_targets.go 的
|
||||
// cropTool.FileTargets)共用它,两处就不会各自推演前缀规则而漂移。
|
||||
type cropSourceKind int
|
||||
|
||||
const (
|
||||
// cropSourceFile 是本地路径:读取要过 file 工具的沙箱。
|
||||
cropSourceFile cropSourceKind = iota
|
||||
// cropSourceURL 是 http(s) 源:走共享的 SSRF 防护客户端。
|
||||
cropSourceURL
|
||||
// cropSourceData 是 base64/data: URI:内容就在参数里,不碰磁盘。
|
||||
cropSourceData
|
||||
)
|
||||
|
||||
// cropSourceKindOf 分类一份 image 参数。
|
||||
func cropSourceKindOf(src string) cropSourceKind {
|
||||
switch {
|
||||
case strings.HasPrefix(src, "data:"):
|
||||
return cropSourceData
|
||||
case strings.HasPrefix(src, "http://"), strings.HasPrefix(src, "https://"):
|
||||
return cropSourceURL
|
||||
default:
|
||||
return cropSourceFile
|
||||
}
|
||||
}
|
||||
|
||||
// load reads the source image: a local path through the file sandbox, an http(s)
|
||||
// URL through the shared SSRF-guarded client, or a base64/data: URI payload.
|
||||
func (c *cropTool) load(ctx context.Context, src string) ([]byte, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(src, "data:"):
|
||||
switch cropSourceKindOf(src) {
|
||||
case cropSourceData:
|
||||
return decodeCropDataURI(src)
|
||||
case strings.HasPrefix(src, "http://"), strings.HasPrefix(src, "https://"):
|
||||
case cropSourceURL:
|
||||
return c.fetch(ctx, src)
|
||||
default:
|
||||
return c.readFile(src)
|
||||
|
||||
Reference in new issue
Block a user