All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 47s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 47s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m3s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 45s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 51s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m6s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 50s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 45s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 59s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 51s
156 lines
3.6 KiB
Go
156 lines
3.6 KiB
Go
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
|
||
}
|
||
|
||
// DefaultSandbox 返回默认沙箱配置(最小权限)
|
||
func DefaultSandbox() *Sandbox {
|
||
return &Sandbox{
|
||
AllowedPaths: []string{},
|
||
WritablePaths: []string{},
|
||
DeniedPaths: defaultDeniedPaths(),
|
||
AllowNetwork: false,
|
||
MaxReadSize: 10 * 1024 * 1024, // 10MB
|
||
MaxWriteSize: 1 * 1024 * 1024, // 1MB
|
||
}
|
||
}
|
||
|
||
// 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
|
||
}
|