package repo import ( "context" "testing" "github.com/google/uuid" ) /* `AgentMayReadSession` —— 决定「能不能读这条会话」的那道闸(2026-10-02 补判据)。 # 为什么它此前零覆盖 `grep -rln AgentMayReadSession --include=*_test.go` ⇒ **没有任何测试碰过它**。 一个决定安全边界的函数没有任何判据,于是 2026-09-15 那个 「未声明 scope 就放行」的迁移期妥协一直活着,直到今天被实测打出来。 # 那个妥协的代价(实测数字,见函数头注释) dsh 密钥 + 不带 session_id ⇒ 20/20 封别人的信全部 200(完整正文)。 带上 session_id ⇒ 闸是好的(带自己会话读别人的 = 403)。 ⇒ 缺口就是 `scope == nil` 那一个分支。 */ // ★ 核心:未声明 session_id 必须**拒绝**(旧语义是放行)。 func TestAgentMayReadSessionRejectsUndeclaredScope(t *testing.T) { setupTestDB(t) ctx := context.Background() target := uuid.New() ok, reason, err := AgentMayReadSession(ctx, "dsh", nil, target) if err != nil { t.Fatal(err) } if ok { t.Fatal("★ 未声明 session_id 必须拒绝 —— 实测旧语义下 dsh 不带 session_id " + "读到了 20/20 封别人的信(收件方 pi/homeagent/opencode,跨工作区),全部 200") } // reason 必须是 canReadSession 认识的既有值:新增 reason 而不同步改 // handler 的 switch,会让那个端点返回 500 而不是 403。 if reason != "not-your-session" { t.Fatalf("★ reason 必须是 canReadSession 已处理的 %q(否则 handler 落到 default 分支或 500),实际 %q", "not-your-session", reason) } } // 闸本身没坏:声明了且等于目标就放行。 func TestAgentMayReadSessionAllowsOwnSession(t *testing.T) { setupTestDB(t) ctx := context.Background() id := uuid.New() ok, _, err := AgentMayReadSession(ctx, "dsh", &id, id) if err != nil { t.Fatal(err) } if !ok { t.Fatal("声明的会话等于目标时必须放行(否则修复会把正常读信也堵死)") } } // 声明了但不是目标 ⇒ 拒绝(跨会话隔离,这是「每个 session 是独立用户」的落点)。 func TestAgentMayReadSessionRejectsOtherSession(t *testing.T) { setupTestDB(t) ctx := context.Background() mine, theirs := uuid.New(), uuid.New() ok, reason, err := AgentMayReadSession(ctx, "dsh", &mine, theirs) if err != nil { t.Fatal(err) } if ok { t.Fatal("★ 跨会话读必须拒绝") } if reason != "not-your-session" { t.Fatalf("reason=%q", reason) } } // agentName 不参与判断 —— 这是**刻意**的(2026-09-15 用户裁定: // 「每个 session 概念上是一个独立的『用户』」,不按 agent 身份仲裁)。 // 这一格钉住那个裁定,防止将来有人"顺手"加一层按 agent 的判断。 func TestAgentMayReadSessionIgnoresAgentName(t *testing.T) { setupTestDB(t) ctx := context.Background() mine, theirs := uuid.New(), uuid.New() asDSH, _, _ := AgentMayReadSession(ctx, "dsh", &mine, theirs) asNobody, _, _ := AgentMayReadSession(ctx, "完全不相干的 agent", &mine, theirs) if asDSH != asNobody { t.Fatalf("判定不应随 agentName 改变(2026-09-15 裁定:会话才是私有单位):dsh=%v other=%v", asDSH, asNobody) } if asDSH { t.Fatal("跨会话仍应拒绝") } }