0.10.1: 安全与业务逻辑加固、新品牌与部署加固

三轮审查修复(60+ 项),相对远端 main(b536672)的关键变更:
- 安全: 数据面 SSRF 拨号防护(防 DNS rebinding)/上游凭据剥离/登录防枚举
  与锁定态统一/可信代理(X-Forwarded-For)限流加固/会话版本失效机制/
  撤销即时传播/弱密钥拒绝启动/脱敏字节级重写(保签名契约)
- 业务逻辑: 裸 body 上传 panic/bootstrap 审计管线卡死/定价通配符优先级/
  全局工具可见性/调度器停机补跑/TOTP 挑战令牌消费顺序/熔断探针语义/
  >4MB 响应 token 计量/管理员重置密码作废会话 等
- 前端: 新 logo(语枢 AI 网关主题)/Provider 凭据异常警示/删除入口/
  后端错误消息透传/localStorage 敏感数据收敛
- 部署: CREDENTIAL_MASTER_KEY 持久化与弱值拒绝/Provider DELETE 接口/
  nginx 安全头/worker 内存限制
- 新增迁移 000029(key_hash 索引)/000030(usage_daily 币种维度)
This commit is contained in:
2026-08-13 10:50:51 +08:00
parent b536672000
commit 9501751792
136 changed files with 8024 additions and 1476 deletions
+60
View File
@@ -0,0 +1,60 @@
package agentnode
import (
"encoding/json"
"testing"
"time"
)
func routeTestNode(id, status string, enabled bool, capabilities map[string]any) Node {
raw, _ := json.Marshal(capabilities)
return Node{ID: id, Code: id, Status: status, Enabled: enabled, Capabilities: raw, LastHeartbeatAt: func() *time.Time { now := time.Now(); return &now }()}
}
func TestSelectRouteCandidatesFiltersCapabilitiesAndStatus(t *testing.T) {
nodes := []Node{
routeTestNode("online-capable", "online", true, map[string]any{"tool_exec": true, "region": "cn"}),
routeTestNode("online-disabled-capability", "online", true, map[string]any{"tool_exec": false}),
routeTestNode("online-missing-capability", "online", true, map[string]any{"region": "cn"}),
routeTestNode("offline-capable", "offline", true, map[string]any{"tool_exec": true}),
routeTestNode("disabled-capable", "online", false, map[string]any{"tool_exec": true}),
}
selected := selectRouteCandidates(nodes, "request-1", []string{"tool_exec"})
if len(selected) != 1 || selected[0].ID != "online-capable" {
t.Fatalf("unexpected candidates: %#v", selected)
}
}
func TestSelectRouteCandidatesIsDeterministic(t *testing.T) {
nodes := []Node{
routeTestNode("node-a", "online", true, nil),
routeTestNode("node-b", "online", true, nil),
routeTestNode("node-c", "online", true, nil),
}
first := selectRouteCandidates(nodes, "request-42", nil)
second := selectRouteCandidates([]Node{nodes[2], nodes[0], nodes[1]}, "request-42", nil)
if len(first) != len(second) || len(first) == 0 {
t.Fatalf("candidate lengths differ: %d %d", len(first), len(second))
}
for index := range first {
if first[index].ID != second[index].ID {
t.Fatalf("selection order is not stable: first=%v second=%v", first, second)
}
}
if first[0].ID == first[1].ID {
t.Fatal("candidate order contains duplicates")
}
}
func TestNormalizeRoutePreviewInput(t *testing.T) {
input, err := normalizeRoutePreviewInput(RoutePreviewInput{
PoolType: " PUBLIC ", PoolCode: "shared", RequestKey: " request-1 ",
RequiredCapabilities: []string{"tool_exec", " tool_exec ", ""},
})
if err != nil || input.PoolType != "public" || input.RequestKey != "request-1" || len(input.RequiredCapabilities) != 1 {
t.Fatalf("normalized input=%+v err=%v", input, err)
}
if _, err := normalizeRoutePreviewInput(RoutePreviewInput{PoolType: "public", PoolCode: "shared"}); err == nil {
t.Fatal("empty request key must be rejected")
}
}