ca38dcd0c9
agent 可对数据库执行只读查询。三重防护:
- 静态校验:仅单条 SELECT/WITH,词边界拒 insert/update/delete/drop/alter/create/
truncate/grant 等写/DDL 关键字(不误伤 created_at 之类列名)。
- 只读事务(sql.TxOptions{ReadOnly:true}):Postgres 引擎级强制只读,硬兜底。
- 行数(100)+超时(10s)+单元格(200 rune)上限。
连接:SQL_QUERY_DSN 优先(生产应指向专用只读库/账号),未设回退服务已解析的平台 PG DSN
(经 NewGateway 传入 g.pgDSN,不再裸读 env)。独立小连接池(8)。
测试:单测 validateReadOnlySQL(放行 SELECT/WITH/列名含 created;拒写/DDL/多语句)。
live 自主 agent 实测:查 sundynix_task=157 行、sundynix_model=2 行。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
88 lines
2.2 KiB
Go
88 lines
2.2 KiB
Go
package mcp
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestEvalArith(t *testing.T) {
|
|
cases := map[string]float64{
|
|
"1+2*3": 7,
|
|
"(1+2)*3": 9,
|
|
"2^3^2": 512, // 右结合
|
|
"-3+5": 2,
|
|
"10/4": 2.5,
|
|
"10%3": 1,
|
|
"2*(3+4)-5": 9,
|
|
"-(2+3)*2": -10,
|
|
"3.5e1+0.5": 35.5,
|
|
}
|
|
for in, want := range cases {
|
|
got, err := evalArith(in)
|
|
if err != nil {
|
|
t.Errorf("evalArith(%q) 报错: %v", in, err)
|
|
continue
|
|
}
|
|
if math.Abs(got-want) > 1e-9 {
|
|
t.Errorf("evalArith(%q)=%v want %v", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEvalArithErrors(t *testing.T) {
|
|
for _, in := range []string{"1/0", "1+", "(1+2", "1+2)", "rm -rf /", "import os", ""} {
|
|
if _, err := evalArith(in); err == nil {
|
|
t.Errorf("evalArith(%q) 应报错但没有", in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHTMLToText(t *testing.T) {
|
|
in := `<html><head><title>x</title></head><body><script>alert(1)</script>
|
|
<h1>标题</h1><p>第一段&符号</p><style>.a{}</style><div>第二段</div></body></html>`
|
|
got := htmlToText(in)
|
|
if strings.Contains(got, "alert") || strings.Contains(got, ".a{}") {
|
|
t.Fatalf("脚本/样式未剔除: %q", got)
|
|
}
|
|
if !strings.Contains(got, "标题") || !strings.Contains(got, "第一段&符号") || !strings.Contains(got, "第二段") {
|
|
t.Fatalf("正文/实体提取不正确: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestDDGUnwrap(t *testing.T) {
|
|
got := ddgUnwrap("//duckduckgo.com/l/?uddg=https%3A%2F%2Fexample.com%2Fa&rut=x")
|
|
if got != "https://example.com/a" {
|
|
t.Fatalf("ddgUnwrap 解码错误: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestValidateReadOnlySQL(t *testing.T) {
|
|
okCases := []string{
|
|
"SELECT 1",
|
|
"select count(*) from sundynix_task",
|
|
"WITH x AS (SELECT 1) SELECT * FROM x",
|
|
" select * from t where created_at > now() ", // created/update 作为列名一部分不应误伤
|
|
}
|
|
for _, q := range okCases {
|
|
if _, ok := validateReadOnlySQL(q); !ok {
|
|
t.Errorf("应通过: %q", q)
|
|
}
|
|
}
|
|
badCases := []string{
|
|
"insert into t values (1)",
|
|
"update t set a=1",
|
|
"delete from t",
|
|
"drop table t",
|
|
"select 1; drop table t",
|
|
"truncate t",
|
|
"SELECT 1; SELECT 2",
|
|
"grant all on t to x",
|
|
}
|
|
for _, q := range badCases {
|
|
if _, ok := validateReadOnlySQL(q); ok {
|
|
t.Errorf("应拒绝: %q", q)
|
|
}
|
|
}
|
|
}
|