package workbench import ( "fmt" "net" "net/url" "regexp" "strings" "aigateway.local/core/internal/provider" ) // ScanFinding 是一条供应链安全扫描发现。 type ScanFinding struct { Rule string `json:"rule"` Severity string `json:"severity"` // high / medium / low Description string `json:"description"` Match string `json:"match,omitempty"` } // 静态扫描规则:对 skill/mcp 资源定义内容(描述、提示词、工具配置、URL 等) // 做供应链安全检查,发现高危模式时在管理端展示。 var ( secretPatterns = []struct { rule, severity, pattern, description string re *regexp.Regexp }{ {rule: "openai_key", severity: "high", pattern: `sk-[A-Za-z0-9_-]{16,}`, description: "疑似硬编码 OpenAI API Key"}, {rule: "aws_key", severity: "high", pattern: `AKIA[0-9A-Z]{16}`, description: "疑似硬编码 AWS Access Key"}, {rule: "github_token", severity: "high", pattern: `gh[pousr]_[A-Za-z0-9]{20,}`, description: "疑似硬编码 GitHub Token"}, {rule: "stripe_key", severity: "high", pattern: `sk_live_[A-Za-z0-9]{20,}`, description: "疑似硬编码 Stripe 密钥"}, {rule: "generic_secret", severity: "medium", pattern: `(?i)(password|passwd|secret|api[_-]?key|token)\s*[:=]\s*['"][^'"]{8,}['"]`, description: "疑似硬编码凭据赋值"}, {rule: "private_key_block", severity: "high", pattern: `-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----`, description: "包含私钥块"}, } dangerCommandPatterns = []struct { pattern, description string re *regexp.Regexp }{ {pattern: `(?i)(rm\s+-rf\s+/|:\(\)\s*\{[^}]*\}\s*;|mkfs\.|dd\s+if=.*of=/dev/)`, description: "包含危险系统命令"}, {pattern: `(?i)curl\s+[^|;&]*\|\s*(ba)?sh|wget\s+[^|;&]*\|\s*(ba)?sh`, description: "管道执行远程脚本(curl|sh)"}, } injectionPatterns = []struct { pattern, description string re *regexp.Regexp }{ {pattern: `(?i)ignore (all |any )?(previous|prior) instructions`, description: "疑似提示词注入(忽略历史指令)"}, {pattern: `(?i)(reveal|leak|exfiltrate|print)\s+(your|the)\s+(system\s+)?(prompt|instructions|secret)`, description: "疑似提示词注入(诱导泄露系统提示/密钥)"}, {pattern: `(?i)(you are now|act as|pretend to be).{0,40}(no restrictions|unfiltered|jailbreak)`, description: "疑似越狱/解除限制指令"}, } ) func compileStaticPatterns() { for i := range secretPatterns { secretPatterns[i].re = regexp.MustCompile(secretPatterns[i].pattern) } for i := range dangerCommandPatterns { dangerCommandPatterns[i].re = regexp.MustCompile(dangerCommandPatterns[i].pattern) } for i := range injectionPatterns { injectionPatterns[i].re = regexp.MustCompile(injectionPatterns[i].pattern) } } func init() { compileStaticPatterns() } // ScanResource 对资源定义内容执行静态安全扫描。 // content 为拼接的文本(名称、描述、提示词、工具 URL、配置等)。 func ScanResource(content string) []ScanFinding { findings := []ScanFinding{} scanText := func(patterns []struct { pattern, description string re *regexp.Regexp }, severity string) { for _, p := range patterns { if match := p.re.FindString(content); match != "" { findings = append(findings, ScanFinding{ Rule: p.pattern, Severity: severity, Description: p.description, Match: truncateRune(match, 80), }) } } } for _, p := range secretPatterns { if match := p.re.FindString(content); match != "" { findings = append(findings, ScanFinding{ Rule: p.rule, Severity: p.severity, Description: p.description, Match: maskMatch(match), }) } } scanText(dangerCommandPatterns, "high") scanText(injectionPatterns, "medium") // URL 与内网地址检测。 urlPattern := regexp.MustCompile(`https?://[^\s"'<>]+`) for _, raw := range urlPattern.FindAllString(content, -1) { parsed, err := url.Parse(strings.Trim(raw, `.,;)]}'"`)) if err != nil || parsed.Hostname() == "" { continue } if parsed.Scheme != "http" && parsed.Scheme != "https" { findings = append(findings, ScanFinding{Rule: "non_http_scheme", Severity: "high", Description: "包含非 http(s) 协议 URL(可能用于 SSRF/文件读取)", Match: truncateRune(raw, 80)}) continue } if addresses, err := net.LookupIP(parsed.Hostname()); err == nil { for _, ip := range addresses { if !provider.IsPublicAddress(ip) { findings = append(findings, ScanFinding{Rule: "private_endpoint", Severity: "high", Description: "资源引用了内网/保留地址(" + ip.String() + "),可能被用于内网探测", Match: truncateRune(raw, 80)}) break } } } } // base64 混淆检测(>=64 字符的 base64 串)。 base64Pattern := regexp.MustCompile(`[A-Za-z0-9+/]{64,}={0,2}`) if match := base64Pattern.FindString(content); match != "" { findings = append(findings, ScanFinding{Rule: "obfuscated_blob", Severity: "low", Description: "包含疑似 base64 混淆数据", Match: truncateRune(match, 40) + "..."}) } return findings } // HighestSeverity 返回发现中的最高严重级。 func HighestSeverity(findings []ScanFinding) string { order := map[string]int{"high": 0, "medium": 1, "low": 2} best := "" for _, f := range findings { if rank, ok := order[f.Severity]; ok { if best == "" || rank < order[best] { best = f.Severity } } } return best } func maskMatch(value string) string { if len(value) <= 8 { return "***" } return value[:4] + "…" + value[len(value)-4:] } func truncateRune(value string, limit int) string { runes := []rune(value) if len(runes) <= limit { return value } return string(runes[:limit]) + "…" } var _ = fmt.Sprintf