mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-09-20 08:58:03 +00:00
fix(examples): 全插件安全审查修复(qq/a2a/memo/calendar/rss/browser/bili/recoverydiag)
审查发现并修复 7 项问题: - P1 qq: downloadURL 裸 http.Get 无超时 → 120s client - P2 a2a: inbound http.Server 零超时 → Read 30s/Write 120s/Idle 60s - P3 bili: output_dir 配置项零校验 → 系统目录黑名单(/、/etc、/usr、/var 等) - P4 recoverydiag: db_path LLM 可控任意 sqlite → 强制限制 data 目录内 - P5 memo/calendar/rss: os.WriteFile 直写 → atomicWriteJSON (temp+rename) - P6 qq: 3 处后台 goroutine(已读/rcon转发/下载)加 panic recover - P7 browser: dump-dom failback Kill 后补 wait 回收僵尸进程 recoverydiag 此前被 .gitignore 排除,但其 db_path 安全修复 属生产代码,故取消忽略并入库。 全部经 plugindev 重打包升版安装验证 config_kept=true。
This commit is contained in:
@ -739,6 +739,11 @@ func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
// 群消息到达即记录(用于排查 napcat→webhook 链路漏报/丢弃)
|
||||
if evt.MessageType == "group" {
|
||||
log.Printf("[qq] webhook recv group msg id=%d from=%d in=%d raw=%.100s",
|
||||
evt.MessageID, evt.UserID, evt.GroupID, evt.RawMessage)
|
||||
}
|
||||
|
||||
rawCQ := evt.RawMessage
|
||||
text := rawCQ
|
||||
@ -763,6 +768,7 @@ func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
if evt.MessageType == "group" {
|
||||
if !p.isGroupAllowed(evt.GroupID) {
|
||||
log.Printf("[qq] group msg from %d rejected: policy=%s", evt.GroupID, p.groupPolicy)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
@ -773,6 +779,9 @@ func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !p.isAtBot(evt.Message) {
|
||||
// 诊断:@ 解析失败时打印 at 段原文与 botID,定位漏报问题
|
||||
log.Printf("[qq] group msg from %d/%d not @bot (botID=%d, raw=%.120s)",
|
||||
evt.GroupID, evt.UserID, p.botID, rawCQ)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
@ -810,6 +819,7 @@ func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
if evt.GroupID == rule.GroupID {
|
||||
mcMsg := fmt.Sprintf("%s 说 %s", nickname, text)
|
||||
go func(r ForwardRule, msg string) {
|
||||
defer func() { _ = recover() }()
|
||||
if err := rconSend(r.Host, r.Port, r.Password, "say "+msg); err != nil {
|
||||
log.Printf("[qq] rcon forward to %s:%d: %v", r.Host, r.Port, err)
|
||||
}
|
||||
@ -980,6 +990,7 @@ func (p *Plugin) handleGetMessage(args map[string]interface{}) (interface{}, err
|
||||
|
||||
// 异步标记已读
|
||||
go func() {
|
||||
defer func() { _ = recover() }() // 后台任务不允许 panic 冒泡带崩进程
|
||||
if d.MessageType == "group" && d.GroupID > 0 {
|
||||
p.napcat("mark_group_msg_as_read", map[string]interface{}{"group_id": d.GroupID})
|
||||
} else if d.UserID > 0 {
|
||||
@ -1070,20 +1081,32 @@ func (p *Plugin) handleChannelOutput(args map[string]interface{}) (interface{},
|
||||
}
|
||||
return p.napcat("send_private_msg", msg)
|
||||
|
||||
case "image":
|
||||
msg := map[string]interface{}{"message": fmt.Sprintf("[CQ:image,file=%s]", payload)}
|
||||
if groupID != 0 {
|
||||
msg["group_id"] = groupID
|
||||
} else {
|
||||
msg["user_id"] = userID
|
||||
case "image", "file":
|
||||
// 收敛到 output 通道:payload 支持本地路径或 http(s) URL。
|
||||
// 本地路径拷入 NapCat 共享目录转 file:// URI(与 voice 分支同模式),
|
||||
// 此后 agent 发本地文件不再需要单独的 upload_group_file 工具。
|
||||
uri := payload
|
||||
if !strings.HasPrefix(payload, "http://") && !strings.HasPrefix(payload, "https://") &&
|
||||
!strings.HasPrefix(payload, "file://") {
|
||||
if _, err := os.Stat(payload); err != nil {
|
||||
return nil, fmt.Errorf("%s 文件不存在: %s", rawType, payload)
|
||||
}
|
||||
os.MkdirAll(p.remoteDir, 0755)
|
||||
dest := filepath.Join(p.remoteDir, sanitizeFilename(filepath.Base(payload)))
|
||||
data, err := os.ReadFile(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取文件失败: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(dest, data, 0644); err != nil {
|
||||
return nil, fmt.Errorf("写入共享目录失败: %w", err)
|
||||
}
|
||||
uri = "file:///app/files/" + filepath.Base(dest)
|
||||
}
|
||||
if groupID != 0 {
|
||||
return p.napcat("send_group_msg", msg)
|
||||
cqTag := "file"
|
||||
if rawType == "image" {
|
||||
cqTag = "image"
|
||||
}
|
||||
return p.napcat("send_private_msg", msg)
|
||||
|
||||
case "file":
|
||||
msg := map[string]interface{}{"message": fmt.Sprintf("[CQ:file,file=%s]", payload)}
|
||||
msg := map[string]interface{}{"message": fmt.Sprintf("[CQ:%s,file=%s]", cqTag, uri)}
|
||||
if groupID != 0 {
|
||||
msg["group_id"] = groupID
|
||||
} else {
|
||||
@ -1638,7 +1661,8 @@ func (p *Plugin) handleGetGroupFiles(args map[string]interface{}) (interface{},
|
||||
return resp, nil
|
||||
}
|
||||
dlURL := parsed.Data.URL
|
||||
httpResp, err := http.Get(dlURL)
|
||||
client := &http.Client{Timeout: 120 * time.Second}
|
||||
httpResp, err := client.Get(dlURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("download: %w", err)
|
||||
}
|
||||
@ -1693,6 +1717,11 @@ func (p *Plugin) handleDownloadFile(args map[string]interface{}) (interface{}, e
|
||||
task := p.addDownloadTask(fileID, filename)
|
||||
|
||||
go func(t *DownloadTask, fid, fname, furl string, gid, uid int64) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[qq] download task %s panic: %v", fid, r)
|
||||
}
|
||||
}()
|
||||
savePath := ""
|
||||
errMsg := ""
|
||||
if furl != "" {
|
||||
|
||||
Reference in New Issue
Block a user