mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-09-20 00:48:12 +00:00
fix(qq): 输出工具不再受"当前会话身份"限制(修「可信 QQ 会话身份不完整」误拒)
现场(用户线上,驻留子联调回执原文):
被**子的中断**唤醒的一轮里,父 agent 调用 output_send__qq(meta 带齐 user_id)被拒:
「QQ 权限策略拒绝工具 output_send__qq:可信 QQ 会话身份不完整;请不要改用其他会话 ID 重试」
根因:`sessionToolArgsAllowed` 对**所有**工具都先要求"本轮能精确匹配可信 OneBot 事件"。
`onInputAuthContext` 在来源是 QQ 但匹配不到可信事件时会降权成
`auth = qqAuthContext{active: true}`(无 peer、非 owner)⇒ `currentPeer == 0`
⇒ 连**输出**也一并被拒 ✗。
但输出是 agent 的**主动调用**:发到哪个会话由它自己给的 meta(group_id / user_id)决定,
`handleChannelOutput` 已经强制要求该字段存在(缺了给明确报错)。再要求"当前会话身份"
是多余的门,而且会把合法发送一起挡掉 —— 设计上收到输入后可以往任意(已授权)通道
发任意多次。
改法:`output_send__qq` 在身份判据**之前**直接放行;「只能访问当前会话」这类限制
保留给**读取类**工具(get_history / mark_read / get_message)—— 那才真的不能跨会话读。
判据 `TestDowngradedAuthStillAllowsQQOutput`:
降权态下输出放行、读取类仍被当前会话限制挡住。
扰动验证:去掉放行分支 ⇒ 该判据报出与现场**一字不差**的那句拒绝。
线上实测:CLI 发起的轮次里 output_send__qq 返回 ok,插件日志 handleChannelOutput 确认送达。
This commit is contained in:
@ -2,7 +2,7 @@
|
|||||||
"name": "qq",
|
"name": "qq",
|
||||||
"name_zh": "QQ消息",
|
"name_zh": "QQ消息",
|
||||||
"name_en": "qq",
|
"name_en": "qq",
|
||||||
"version": "1.4.0",
|
"version": "1.4.1",
|
||||||
"description": "QQ 消息收发插件,通过 NapCat 协议桥接",
|
"description": "QQ 消息收发插件,通过 NapCat 协议桥接",
|
||||||
"author": "HomeAgent",
|
"author": "HomeAgent",
|
||||||
"entry": "plugin.so",
|
"entry": "plugin.so",
|
||||||
|
|||||||
@ -925,6 +925,19 @@ func (p *Plugin) sessionToolArgsAllowed(name string, args map[string]interface{}
|
|||||||
if !auth.active || auth.owner {
|
if !auth.active || auth.owner {
|
||||||
return true, ""
|
return true, ""
|
||||||
}
|
}
|
||||||
|
// 输出工具**不受"当前会话"身份限制**(先于身份判据返回)。
|
||||||
|
//
|
||||||
|
// 为什么:输出是 agent 的**主动调用**,发到哪个会话由它自己给的 meta
|
||||||
|
// (group_id / user_id)决定 —— handleChannelOutput 会强制要求该字段存在,
|
||||||
|
// 缺了会得到明确的报错。这里再要求"本轮能精确匹配可信 OneBot 事件"是多余的门,
|
||||||
|
// 而且会把合法发送一起拒掉:现场(被子的中断唤醒的一轮)父带齐 meta 也发不出去,
|
||||||
|
// 报「可信 QQ 会话身份不完整」。
|
||||||
|
// 「只能访问当前会话」这类限制只对**读取类**工具(get_history / mark_read /
|
||||||
|
// get_message)成立 —— 那才是真的不能跨会话读。
|
||||||
|
if name == "output_send__"+p.name {
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|
||||||
currentPeer := auth.userID
|
currentPeer := auth.userID
|
||||||
if auth.isGroup {
|
if auth.isGroup {
|
||||||
currentPeer = auth.groupID
|
currentPeer = auth.groupID
|
||||||
|
|||||||
@ -202,3 +202,36 @@ func TestZeroLimitsMeanUnlimited(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 降权(本轮无法精确匹配可信 OneBot 事件 ⇒ auth={active:true}、无 peer、非 owner)时,
|
||||||
|
// **输出仍必须放行**:发到哪个会话由 agent 自己给的 meta 决定,
|
||||||
|
// 不该被「当前会话身份」挡住。现场:被子的中断唤醒的一轮里,父带齐 meta 也发不出去
|
||||||
|
// (报「可信 QQ 会话身份不完整」)。
|
||||||
|
//
|
||||||
|
// 反之,**读取类**工具在降权时仍受当前会话限制 —— 那才是真的不能跨会话读。
|
||||||
|
func TestDowngradedAuthStillAllowsQQOutput(t *testing.T) {
|
||||||
|
p := newPermissionTestPlugin(t)
|
||||||
|
p.auth = qqAuthContext{active: true}
|
||||||
|
p.privateToolAllowlist = []string{"output_send__qq", "qq_get_history"}
|
||||||
|
p.groupToolAllowlists = map[int64][]string{0: {"output_send__qq", "qq_get_history"}}
|
||||||
|
|
||||||
|
ctx := toolCallContext("output_send__qq", map[string]interface{}{
|
||||||
|
"payload": "带齐 meta 的主动发送",
|
||||||
|
"type": "text",
|
||||||
|
"meta": `{"user_id":2198972886}`,
|
||||||
|
})
|
||||||
|
if err := p.beforeToolcall(ctx); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ctx.Response != nil {
|
||||||
|
t.Fatalf("降权时输出被拒: %s", *ctx.Response)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx2 := toolCallContext("qq_get_history", map[string]interface{}{"group_id": 1027993713})
|
||||||
|
if err := p.beforeToolcall(ctx2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ctx2.Response == nil || !strings.Contains(*ctx2.Response, "可信 QQ 会话身份不完整") {
|
||||||
|
t.Fatalf("读取类工具在降权时应被当前会话限制挡住: %#v", ctx2.Response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user