Files
opsxdb/apps/server/internal/db/danger.go
T
logan a9510235bb
CI / Go server (push) Canceled after 0s
CI / Web + core packages (push) Canceled after 0s
CI / Desktop (Tauri) cargo check (push) Canceled after 0s
feat: 18 引擎驱动全家桶 + 驱动管理/离线包 + 设置中心 + 工作台交互打磨
驱动接入(全部本机真实例验收):
- Elasticsearch 原生 SQL API、InfluxDB 原生 HTTP+Flux(官方 JDBC 付费许可,实测弃用)
- ZooKeeper/MongoDB JVM agent(连接时自动下载官方驱动 jar,sha256 钉版本到 ~/.opsxdb/drivers/)
- 达梦 JDBC agent 桥就绪(待厂商 jar+实例);TiDB 真实例验收
- 协议族代码审查放行 PolarDB/TDSQL/OceanBase/Kingbase/GaussDB/Oracle
- 驱动能力模型:按驱动开关对象树分组/右键菜单/行编辑,NoSQL 树拍平(对象直挂库节点)

驱动管理:
- 驱动管理弹框(状态/预装/离线包导入/JDK 检测)+ /api/drivers/* 三接口
- opsxdb-driver 驱动商店仓库(清单/fetch.sh/pack.sh 离线包/import.sh)
- 连接时懒加载:DMG 零 Java 产物,首连自动下载(实测 ZK 九件套 1.35s),离线包自动回退

UI/交互:
- 设置中心(⚙):常规/执行/结果/驱动/快捷键/关于,左导航+右面板(对标 opsxshell)
- 实例级展开箭头(行首 SVG chevron,替代字体回退失真的 Unicode 三角)
- 树内嵌套渲染修复:对象树挂在激活连接下;工具栏窄窗口不断行(按钮 nowrap+整颗换行)
- 侧栏连接行精简(图标+连接名)、SQLite SSH 守卫、Oracle 死参数清理

质量:
- Go 协议层/离线回退单测、E2E 门控测试(ES/ZK/Influx/Mongo/TiDB/JDBC 桥)
- 驱动注册表单一事实来源 connection-types.json,README/BACKLOG/AGENTS 同步
2026-09-22 01:07:46 +08:00

108 lines
3.2 KiB
Go

package db
import (
"regexp"
"strings"
)
// 危险语句规则:词边界匹配,作用于 StripComments 之后的纯代码文本。
// 词表:DROP/TRUNCATE/ALTER/GRANT/REVOKE/CREATE USER|ROLE/RENAME TABLE/
// REPLACE INTO/LOAD DATA/INTO OUTFILE|DUMPFILE/xp_cmdshell/COPY..PROGRAM/EXEC/CALL。
var dangerPatterns = []struct {
re *regexp.Regexp
desc string
}{
{regexp.MustCompile(`\bDROP\b`), "DROP"},
{regexp.MustCompile(`\bTRUNCATE\b`), "TRUNCATE"},
{regexp.MustCompile(`\bALTER\b`), "ALTER"},
{regexp.MustCompile(`\bGRANT\b`), "GRANT"},
{regexp.MustCompile(`\bREVOKE\b`), "REVOKE"},
{regexp.MustCompile(`\bCREATE\s+(USER|ROLE)\b`), "CREATE USER/ROLE"},
{regexp.MustCompile(`\bRENAME\s+TABLE\b`), "RENAME TABLE"},
{regexp.MustCompile(`\bREPLACE\s+INTO\b`), "REPLACE INTO"},
{regexp.MustCompile(`\bLOAD\s+DATA\b`), "LOAD DATA"},
{regexp.MustCompile(`\bINTO\s+(OUTFILE|DUMPFILE)\b`), "INTO OUTFILE/DUMPFILE"},
{regexp.MustCompile(`(?i)\bxp_cmdshell\b`), "xp_cmdshell"},
{regexp.MustCompile(`\bCOPY\b[^;]*\b(FROM|TO)\s+PROGRAM\b`), "COPY FROM/TO PROGRAM"},
}
var (
reWriteNoWhere = regexp.MustCompile(`\b(DELETE|UPDATE)\b`)
reWhere = regexp.MustCompile(`\bWHERE\b`)
reExecCallHead = regexp.MustCompile(`^(EXEC|EXECUTE|CALL)\b`)
)
// CheckDangerousSQL 服务端权威的危险语句判定(前端预检仅为 UX)。
// 逐语句检查;注释/字符串内的关键字不计;MySQL /*! */ 可执行注释内容参与检查;
// WITH 包裹的写操作因全文扫描 CTE 体同样命中。
// 返回命中的危险原因清单(去重)。
func CheckDangerousSQL(driver, sqlText string) []string {
_ = driver // 词表当前各驱动通用,保留参数便于后续按方言细化
seen := map[string]struct{}{}
var hits []string
add := func(desc, stmt string) {
if _, ok := seen[desc]; ok {
return
}
seen[desc] = struct{}{}
hits = append(hits, desc+": "+truncateSQL(stmt, 80))
}
for _, stmt := range SplitStatements(sqlText) {
code := strings.ToUpper(StripComments(stmt))
code = strings.TrimSpace(code)
if code == "" {
continue
}
for _, p := range dangerPatterns {
if p.re.MatchString(code) {
add(p.desc, stmt)
}
}
// DELETE/UPDATE 无 WHERE(含 WITH CTE 体内的写操作)
if reWriteNoWhere.MatchString(code) && !reWhere.MatchString(code) {
kw := reWriteNoWhere.FindString(code)
add(kw+" 无 WHERE", stmt)
}
// 存储过程/动态执行仅匹配语句开头,降低误报
if m := reExecCallHead.FindString(code); m != "" {
add(m, stmt)
}
}
return hits
}
// IsDangerousSQL 兼容旧调用:命中任意危险规则即 true。
func IsDangerousSQL(sql string) bool {
return len(CheckDangerousSQL("", sql)) > 0
}
func truncateSQL(s string, n int) string {
s = strings.Join(strings.Fields(s), " ")
if len(s) <= n {
return s
}
return s[:n] + "…"
}
func stripSQLLead(sql string) string {
s := strings.TrimSpace(sql)
for {
if strings.HasPrefix(s, "--") {
if i := strings.IndexByte(s, '\n'); i >= 0 {
s = strings.TrimSpace(s[i+1:])
continue
}
return ""
}
if strings.HasPrefix(s, "/*") {
if i := strings.Index(s, "*/"); i >= 0 {
s = strings.TrimSpace(s[i+2:])
continue
}
return ""
}
break
}
return s
}