Files
GoTunnel/pkg/plugin/script/sandbox.go
Flik 42e11e0aca
Some checks failed
Build Multi-Platform Binaries / build-frontend (push) Failing after 19s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Has been skipped
feat(plugin): 实现插件安全验证和审计日志功能
- 添加插件签名验证机制,支持远程证书吊销列表
- 增加插件安装时的安全检查和签名验证
- 实现插件版本存储的HMAC完整性校验
- 添加插件审计日志记录插件安装和验证事件
- 增加JS插件沙箱安全限制配置
- 添加插件商店API的签名URL字段支持
- 实现安全配置的自动刷新机制
2025-12-30 22:06:33 +08:00

162 lines
3.8 KiB
Go
Raw Permalink 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 script
import (
"fmt"
"os"
"path/filepath"
"strings"
)
// Sandbox 插件沙箱配置
type Sandbox struct {
// 允许访问的路径列表(绝对路径)
AllowedPaths []string
// 允许写入的路径列表(必须是 AllowedPaths 的子集)
WritablePaths []string
// 禁止访问的路径(黑名单,优先级高于白名单)
DeniedPaths []string
// 是否允许网络访问
AllowNetwork bool
// 最大文件读取大小 (bytes)
MaxReadSize int64
// 最大文件写入大小 (bytes)
MaxWriteSize int64
// 最大内存使用量 (bytes)0 表示不限制
MaxMemory int64
// 最大调用栈深度
MaxStackDepth int
}
// DefaultSandbox 返回默认沙箱配置(最小权限)
func DefaultSandbox() *Sandbox {
return &Sandbox{
AllowedPaths: []string{},
WritablePaths: []string{},
DeniedPaths: defaultDeniedPaths(),
AllowNetwork: false,
MaxReadSize: 10 * 1024 * 1024, // 10MB
MaxWriteSize: 1 * 1024 * 1024, // 1MB
MaxMemory: 64 * 1024 * 1024, // 64MB
MaxStackDepth: 1000, // 最大调用栈深度
}
}
// defaultDeniedPaths 返回默认禁止访问的路径
func defaultDeniedPaths() []string {
home, _ := os.UserHomeDir()
denied := []string{
"/etc/passwd",
"/etc/shadow",
"/etc/sudoers",
"/root",
"/.ssh",
"/.gnupg",
"/.aws",
"/.kube",
"/proc",
"/sys",
}
if home != "" {
denied = append(denied,
filepath.Join(home, ".ssh"),
filepath.Join(home, ".gnupg"),
filepath.Join(home, ".aws"),
filepath.Join(home, ".kube"),
filepath.Join(home, ".config"),
filepath.Join(home, ".local"),
)
}
return denied
}
// ValidateReadPath 验证读取路径是否允许
func (s *Sandbox) ValidateReadPath(path string) error {
return s.validatePath(path, false)
}
// ValidateWritePath 验证写入路径是否允许
func (s *Sandbox) ValidateWritePath(path string) error {
return s.validatePath(path, true)
}
func (s *Sandbox) validatePath(path string, write bool) error {
// 清理路径,防止路径遍历攻击
cleanPath, err := s.cleanPath(path)
if err != nil {
return err
}
// 检查黑名单(优先级最高)
if s.isDenied(cleanPath) {
return fmt.Errorf("access denied: path is in denied list")
}
// 检查白名单
allowedList := s.AllowedPaths
if write {
allowedList = s.WritablePaths
}
if len(allowedList) == 0 {
return fmt.Errorf("access denied: no paths allowed")
}
if !s.isAllowed(cleanPath, allowedList) {
if write {
return fmt.Errorf("access denied: path not in writable list")
}
return fmt.Errorf("access denied: path not in allowed list")
}
return nil
}
// cleanPath 清理并验证路径
func (s *Sandbox) cleanPath(path string) (string, error) {
// 转换为绝对路径
absPath, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("invalid path: %w", err)
}
// 清理路径(解析 .. 和 .
cleanPath := filepath.Clean(absPath)
// 检查符号链接(防止通过符号链接绕过限制)
realPath, err := filepath.EvalSymlinks(cleanPath)
if err != nil {
// 文件可能不存在,使用清理后的路径
if !os.IsNotExist(err) {
return "", fmt.Errorf("invalid path: %w", err)
}
realPath = cleanPath
}
// 再次检查路径遍历
if strings.Contains(realPath, "..") {
return "", fmt.Errorf("path traversal detected")
}
return realPath, nil
}
// isDenied 检查路径是否在黑名单中
func (s *Sandbox) isDenied(path string) bool {
for _, denied := range s.DeniedPaths {
if strings.HasPrefix(path, denied) || path == denied {
return true
}
}
return false
}
// isAllowed 检查路径是否在白名单中
func (s *Sandbox) isAllowed(path string, allowedList []string) bool {
for _, allowed := range allowedList {
if strings.HasPrefix(path, allowed) || path == allowed {
return true
}
}
return false
}