mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-28 01:03:50 +08:00
171 lines
7.0 KiB
Go
171 lines
7.0 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/Autumn-27/artex/intercept"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const completeFindingReviewReport = `# 同一权限缺陷
|
|
## 概述
|
|
同一对象权限检查缺失。
|
|
## 影响
|
|
已验证读取其他账号的订单。
|
|
## 前置条件
|
|
已存在的低权会话和订单编号。
|
|
## 复现步骤
|
|
对照自己的订单,再查看其他账号订单,响应证明同一检查缺失。
|
|
## 证据
|
|
原报告的保存响应及命令输出,未重新探测。
|
|
## PoC
|
|
完整命令在原始证据中,此测试使用隔离材料。
|
|
## 根因分析
|
|
同一订单 handler 未验证订单所有者。
|
|
## 修复建议
|
|
统一验证请求者与订单所有者,并回归对象权限检查。
|
|
`
|
|
|
|
func TestUnifiedFindingReportRejectsIncompleteAndTruncated(t *testing.T) {
|
|
if err := validateUnifiedFindingReport(completeFindingReviewReport); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, s := range []string{strings.Split(completeFindingReviewReport, "## 根因分析")[0], completeFindingReviewReport + "\n```bash\nunclosed", strings.Replace(completeFindingReviewReport, "统一验证请求者与订单所有者,并回归对象权限检查。", "", 1)} {
|
|
if err := validateUnifiedFindingReport(s); err == nil {
|
|
t.Fatal("incomplete report accepted")
|
|
}
|
|
}
|
|
// Section labels appearing inside a script are not real report sections.
|
|
if err := validateUnifiedFindingReport("## 概述\ntext\n```bash\n" + completeFindingReviewReport + "\n```"); err == nil {
|
|
t.Fatal("script comments counted as sections")
|
|
}
|
|
}
|
|
|
|
func TestFindingCaseReviewCompletionRequiresReadsAndFreshReport(t *testing.T) {
|
|
s, task, ids := testCaseServer(t)
|
|
runs, err := s.m.pg.CreateFindingCaseReviews(t.Context(), map[int64][]int64{task: ids})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
conv := runs[0].ConversationID
|
|
defer s.m.pg.Exec(`DELETE FROM conversations WHERE id=$1`, conv)
|
|
if _, err := s.m.pg.Exec(`UPDATE finding_case_review_runs SET state='running' WHERE conversation_id=$1`, conv); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx := intercept.WithConvID(context.Background(), conv)
|
|
a := findingReviewConclusion{Summary: "根据对象权限检查的具体证据判断"}
|
|
for _, id := range ids {
|
|
a.Decisions = append(a.Decisions, findingReviewDecision{FindingID: id, Verdict: "merged", Reason: "同一 handler", RootCauseEvidence: "各记录的 baseline/proof 指向同一对象所有者校验缺失"})
|
|
}
|
|
if err := s.saveFindingReviewConclusion(ctx, conv, a); err == nil {
|
|
t.Fatal("unread findings accepted")
|
|
}
|
|
for _, id := range ids {
|
|
if _, err := s.performFindingCaseTool(ctx, "get_finding_record", findingCaseRequest{FindingID: json.RawMessage(fmt.Sprint(id))}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
cid, err := s.m.pg.MergeFindingCase(ctx, task, ids, "orders", "same concrete check", "reporter")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.saveFindingReviewConclusion(ctx, conv, a); err == nil {
|
|
t.Fatal("missing unified report accepted")
|
|
}
|
|
c, _ := s.m.pg.GetFindingCase(cid)
|
|
if err := s.m.pg.UpdateFindingCaseReport(ctx, cid, c.Version, "orders", completeFindingReviewReport, "high", "verified impact"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.saveFindingReviewConclusion(ctx, conv, a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.validateFindingReviewCompletion(conv); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.checkCaseReviewScope(ctx, "update_finding_case_report", findingCaseRequest{CaseID: json.RawMessage(fmt.Sprint(cid))}); err == nil {
|
|
t.Fatal("sealed review can still mutate")
|
|
}
|
|
if _, err := s.m.pg.Exec(`UPDATE findings SET evidence='new proof' WHERE id=$1`, ids[0]); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.validateFindingReviewCompletion(conv); err == nil {
|
|
t.Fatal("stale group reported completed")
|
|
}
|
|
}
|
|
|
|
func TestFindingCaseReviewProtectsOriginalToolWrites(t *testing.T) {
|
|
s, f, _ := trafficEvidenceServer(t)
|
|
original, err := s.m.pg.GetFinding(f.FindingID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
runs, err := s.m.pg.CreateFindingCaseReviews(t.Context(), map[int64][]int64{*original.TaskID: {original.ID}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
conv := runs[0].ConversationID
|
|
defer s.m.pg.Exec(`DELETE FROM conversations WHERE id=$1`, conv)
|
|
ctx := intercept.WithConvID(context.Background(), conv)
|
|
workflowCall(t, ctx, s.toolUpdateFindingReport(), map[string]any{"finding_id": f.NodeID, "report": "OVERWRITE", "evidence_version": original.EvidenceVersion}, true)
|
|
if err := s.agentFindingTrafficAccess(ctx, f.FindingID, true); err == nil {
|
|
t.Fatal("historical review can change evidence")
|
|
}
|
|
after, _ := s.m.pg.GetFinding(original.ID)
|
|
if after.Report != original.Report || after.EvidenceVersion != original.EvidenceVersion || after.Status != original.Status || after.Severity != original.Severity {
|
|
t.Fatal("original changed")
|
|
}
|
|
// Normal reporting is still able to write its own standalone report.
|
|
workflowCall(t, context.Background(), s.toolUpdateFindingReport(), map[string]any{"finding_id": f.NodeID, "report": "normal report", "evidence_version": original.EvidenceVersion}, false)
|
|
}
|
|
|
|
func TestFindingCaseReviewSuggestionNeedsSavedSuggestion(t *testing.T) {
|
|
s, task, ids := testCaseServer(t)
|
|
runs, err := s.m.pg.CreateFindingCaseReviews(t.Context(), map[int64][]int64{task: ids[:2]})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
conv := runs[0].ConversationID
|
|
defer s.m.pg.Exec(`DELETE FROM conversations WHERE id=$1`, conv)
|
|
s.m.pg.Exec(`UPDATE finding_case_review_runs SET state='running' WHERE conversation_id=$1`, conv)
|
|
ctx := intercept.WithConvID(context.Background(), conv)
|
|
a := findingReviewConclusion{Summary: "需人工确认共同组件"}
|
|
for _, id := range ids[:2] {
|
|
s.performFindingCaseTool(ctx, "get_finding_record", findingCaseRequest{FindingID: json.RawMessage(fmt.Sprint(id))})
|
|
a.Decisions = append(a.Decisions, findingReviewDecision{FindingID: id, Verdict: "suggested", Reason: "同一边界但根因未确认", RootCauseEvidence: "原报告指向对象权限边界,缺少共同组件证据"})
|
|
}
|
|
if err := s.saveFindingReviewConclusion(ctx, conv, a); err == nil {
|
|
t.Fatal("unpersisted suggestion accepted")
|
|
}
|
|
if _, err := s.m.pg.SuggestFindingCase(ctx, task, ids[0], ids[1], "orders", "common component unknown"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.saveFindingReviewConclusion(ctx, conv, a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestFindingCaseReviewCandidatesStayWithinSelection(t *testing.T) {
|
|
s, task, ids := testCaseServer(t)
|
|
runs, err := s.m.pg.CreateFindingCaseReviews(t.Context(), map[int64][]int64{task: ids[:2]})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
conv := runs[0].ConversationID
|
|
defer s.m.pg.Exec(`DELETE FROM conversations WHERE id=$1`, conv)
|
|
ctx := intercept.WithConvID(context.Background(), conv)
|
|
a := findingCaseRequest{FindingID: json.RawMessage(fmt.Sprint(ids[0]))}
|
|
v, err := s.performFindingCaseTool(ctx, "search_finding_duplicates", a)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v.(map[string]any)["total"].(int) != 1 {
|
|
t.Fatal("unselected candidates leaked", v)
|
|
}
|
|
v, err = s.performFindingCaseTool(context.Background(), "search_finding_duplicates", a)
|
|
if err != nil || v.(map[string]any)["total"].(int) != 2 {
|
|
t.Fatal("normal candidate search changed", v, err)
|
|
}
|
|
}
|