feat(lua): 适配器导入路径冲突校验(R6)——防静默覆盖与路径逃逸

用户要求:导入适配器时必须检查路径冲突,避免静默错误。

之前 UploadAdapter(name, code) 直接拼 adapter_dir/<name>.lua:
- name 含 "../" 或绝对路径会写到目录外
- name 与内置同名会静默覆盖,加载失败只报 "adapter X missing",
  现场与病因分离

实现(internal/lua/adapter_validate.go):
- ValidateAdapterName:字符集 ^[a-zA-Z0-9_-]+$,拒绝 /、\、..、绝对路径、空白
- ValidateAdapterPath:文件必须在 adapter_dir 内
- VM.InstallPluginAdapter:插件装内置名 → ErrAdapterNameReserved 拒
- Core.UploadAdapter:在 WriteFile 之前校验(防路径逃逸)
- Core.RemoveAdapter:也校验名字(防 os.Remove 越界)

设计取舍:保留名禁止只对插件生效,不对运维生效。
- operator 放 openai.lua 覆盖内置 = 文档化的热覆盖机制,放行
- 第三方插件静默换掉内核自带的协议翻译 = 篡改发行物,拒绝
- 先误做成了全拒,Start() 加载内置适配器直接崩,测试抓住后改对
  (TestLoadBundledAdapters 等锁住回归)

测试 12 个:路径穿越/绝对路径/保留段/空白/内置覆盖(允许 vs 插件拒绝)/
目录外加载/错误点名/热重载不回归。

验证:go test -tags luajit ./... 全绿。
This commit is contained in:
JianFeeeee
2026-10-11 09:23:34 +08:00
parent 9d6653a573
commit 958f1f48b5
4 changed files with 411 additions and 2 deletions

View File

@ -8,10 +8,10 @@ import (
"encoding/hex"
"fmt"
"log"
"net/url"
"os"
"path/filepath"
"sort"
"net/url"
"strings"
"sync"
"sync/atomic"
@ -874,6 +874,14 @@ func (c *Core) UploadAdapter(name, code string) error {
if name == "" {
return fmt.Errorf("adapter name required")
}
// Validate name SAFETY before touching the filesystem (R6). A path-traversal
// name ("../openai") must be rejected BEFORE WriteFile, or it would already
// have escaped the adapter dir by the time LoadAdapter re-checks. Built-in
// names are allowed here: an admin uploading openai.lua is the documented
// override, same as placing the file in adapter_dir by hand.
if err := lua.ValidateAdapterName(name, nil); err != nil {
return err
}
if err := os.MkdirAll(c.cfg.AdapterDir, 0755); err != nil {
return err
}
@ -885,6 +893,13 @@ func (c *Core) UploadAdapter(name, code string) error {
}
func (c *Core) RemoveAdapter(name string) error {
// Validate the name so a traversal name cannot os.Remove a file outside the
// adapter dir. A reserved (built-in) name is a legitimate delete target in
// the running VM, so reserved-name rejection is NOT applied here — only the
// path-safety character set is.
if err := lua.ValidateAdapterName(name, nil); err != nil {
return err
}
path := filepath.Join(c.cfg.AdapterDir, name+".lua")
_ = os.Remove(path)
c.vm.RemoveAdapter(name)