安全加固:LLM 会话按 (bot,peer) 隔离+历史上限50条,LLM 入队按 (bot,from_node) 限流(60s内5条),瓦片磁盘缓存单源配额(3000文件/300MB 按mtime淘汰),签到墙读接口限速+全站每日1000条封顶,敏感数据落盘 AES-256-GCM 加密(MESH_SECRET_KEY, 兼容明文迁移),admin 改密需验证当前密码+pwd_version 会话撤销,后端 v1.5.0

This commit is contained in:
2026-08-20 17:13:13 +08:00
parent f21e8337af
commit ba9be5b68b
24 changed files with 619 additions and 51 deletions
+22
View File
@@ -149,6 +149,28 @@ func (l *FailureLimiter) Fail(key string) bool {
return false
}
// Exceeded 记录一次请求,窗口内请求数超过 max 时返回 true(限速,不产生封禁)。
// 用于读接口的轻量频率限制。
func (l *FailureLimiter) Exceeded(key string) bool {
if key == "" {
return false
}
now := l.now()
l.mu.Lock()
defer l.mu.Unlock()
st, ok := l.fails[key]
if !ok || now.Sub(st.windowStart) > l.window {
st = &failState{windowStart: now, count: 1}
l.fails[key] = st
if len(l.fails) > l.maxEntries {
l.purgeLocked(now)
}
return false
}
st.count++
return st.count > l.max
}
// Reset 清除 key 的失败计数。
func (l *FailureLimiter) Reset(key string) {
if key == "" {
+24
View File
@@ -79,3 +79,27 @@ func TestEmptyKeyIgnored(t *testing.T) {
t.Fatal("empty key must not be tracked")
}
}
func TestExceeded(t *testing.T) {
l := newTestLimiter(t, Options{MaxFailures: 3})
now := time.Unix(1700000000, 0)
l.now = func() time.Time { return now }
for i := 0; i < 3; i++ {
if l.Exceeded("ip") {
t.Fatalf("request %d should be allowed", i+1)
}
}
if !l.Exceeded("ip") {
t.Fatal("4th request should be limited")
}
// 窗口过后恢复。
now = now.Add(2 * time.Minute)
if l.Exceeded("ip") {
t.Fatal("request after window should be allowed")
}
// 不同 key 互不影响。
if l.Exceeded("other") {
t.Fatal("other key must not be limited")
}
}