Files
HomeAgent/internal/sdk/security_impl_test.go
JianFeeeee b850feaef2 feat: 段页式记忆流转、coldcontext 重命名与安全治理
记忆层:
- MemoryBlock 承载 FrameTS/PageSeq/PageKind/BlockRole/CallID
- coldcontext 归档与回填按完整页传递,工具调用参数和配对键不丢失
- 召回先估算总 token,超预算时按全局页相似度标记 inactive
- inactive 仅是 context 渲染期状态,不落盘、不进入 coldcontext,Prune 后重置
- Peek/确认/Remove 两阶段召回,失败保留冷上下文副本
- stablemem 记录 last_recalled_at,删块连带清理边

命名:
- internal/memory/document → internal/memory/coldcontext
- Doc/DocEvent/DocHit → ColdDoc/ColdEvent/ColdHit
- ContextToColdContext/RecentColdContexts/FindColdContexts
- 保留既有 JSON 标签、doc_ 文件前缀与 doc_count 状态键,兼容存量数据

安全治理:
- 工具策略门禁、安全配置、事件总线发布者身份与入站脱敏
- 声明式 SecurityAPI 与 QQ owner/guest profile
- 日志保留策略与相关 e2e/回归判据

验证:go build ./...、go vet ./...、go test ./...(62 包);
生产副本 e2e:7013 块迁移后 ID/向量/指纹 0 变化、0 缺失。
2026-10-29 23:53:48 +08:00

128 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package sdk
// SecurityAPI 内核实现的判据。
//
// ## 钉什么
//
// 声明面只有三件事可能出错,且三种都**静默**:
//
// ① Owner 被插件伪造 ⇒ 能声明别人的档位(qq.owner 全放行)
// ② 名字前缀不对 ⇒ 档位登记成功但在别的 owner 名下(永远命中不到)
// ③ 未装配时静默成功 ⇒ 插件以为生效了,实际没登记
//
// 本文件的判据对每一条都要求**响亮失败**。
import (
"strings"
"testing"
internalSecurity "github.com/JianFeeeee/HologramAgent/internal/security"
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
)
// withRuntime 装一个干净的 Runtime 供测试,结束后恢复。
func withRuntime(t *testing.T) *internalSecurity.Runtime {
t.Helper()
prev := internalSecurity.RuntimeGlobal()
rt := internalSecurity.NewRuntime(nil)
internalSecurity.SetRuntimeGlobal(rt)
t.Cleanup(func() { internalSecurity.SetRuntimeGlobal(prev) })
return rt
}
// ① 正常声明:登记到 Runtime,且 Owner 是内核给的名字。
func TestSecurityDeclareProfileRegistersUnderRealOwner(t *testing.T) {
rt := withRuntime(t)
api := NewSecurityAPI("qq")
if err := api.DeclareProfile(pubsdk.SecurityProfile{
Name: "qq.guest",
DenyTools: []string{"cmd_*"},
AllowTools: []string{"qq_*"},
}); err != nil {
t.Fatalf("声明应成功: %v", err)
}
if !contains(rt.Profiles(), "qq.guest") {
t.Fatalf("档位未登记,实际已有: %v", rt.Profiles())
}
}
// ② ★ Owner 不可伪造:插件声明别人的档位必须报错。
//
// 若能声明 `qq.owner` 且给 AllowTools=["*"],等于把 QQ 的 owner 档位
// 改成全放行 —— 非所有者立刻能调用所有工具。
func TestSecurityDeclareProfileRejectsForeignOwner(t *testing.T) {
withRuntime(t)
api := NewSecurityAPI("evil")
err := api.DeclareProfile(pubsdk.SecurityProfile{
Name: "qq.owner",
Owner: "qq", // 冒名
AllowTools: []string{"*"},
})
if err == nil {
t.Fatal("★ 冒名声明必须被拒绝 —— 否则任何插件都能把 qq.owner 改成全放行")
}
if !strings.Contains(err.Error(), "evil") {
t.Errorf("错误信息应指出真实插件名,实际: %v", err)
}
}
// ③ 名字必须带本插件前缀(否则登记了也命中不到)。
func TestSecurityDeclareProfileRequiresOwnerPrefix(t *testing.T) {
withRuntime(t)
api := NewSecurityAPI("qq")
for _, bad := range []string{"guest", "qqguest", "qqq.guest", ""} {
if err := api.DeclareProfile(pubsdk.SecurityProfile{Name: bad}); err == nil {
t.Errorf("名字 %q 应被拒绝(必须形如 qq.xxx)", bad)
}
}
}
// ④ 未装配 security 时必须报错,不能静默成功。
//
// 静默成功的后果:插件以为档位生效,直到注入时带 SecurityProfile
// 才报"未声明档位",此时排查方向已跑到"名字拼错"上。
func TestSecurityDeclareProfileFailsLoudlyWhenUnassembled(t *testing.T) {
prev := internalSecurity.RuntimeGlobal()
internalSecurity.SetRuntimeGlobal(nil)
t.Cleanup(func() { internalSecurity.SetRuntimeGlobal(prev) })
api := NewSecurityAPI("qq")
err := api.DeclareProfile(pubsdk.SecurityProfile{Name: "qq.guest"})
if err == nil {
t.Fatal("★ 安全模块未装配时必须报错 —— 静默成功会让插件误以为策略已生效")
}
if !strings.Contains(err.Error(), "not assembled") {
t.Errorf("错误信息应说明未装配,实际: %v", err)
}
}
// ⑤ 批量声明:任一失败即整体报错,且错误带下标便于定位。
func TestSecurityDeclareProfilesReportsIndex(t *testing.T) {
withRuntime(t)
api := NewSecurityAPI("qq")
err := api.DeclareProfiles([]pubsdk.SecurityProfile{
{Name: "qq.owner"},
{Name: "bogus.name"}, // 第 1 个(0-based)非法
})
if err == nil {
t.Fatal("批量声明中任一失败应整体报错")
}
if !strings.Contains(err.Error(), "profile[1]") {
t.Errorf("错误应带失败下标,实际: %v", err)
}
}
func contains(list []string, want string) bool {
for _, v := range list {
if v == want {
return true
}
}
return false
}