fix: 修复合并后审查发现的 6 个中/高问题
#102 高:证据正文下载在全局 advisory lock 事务内 io.Copy 给 HTTP 客户端, verifyFile 又要整读一遍算 SHA-256 —— 一次大文件下载或慢客户端就会挂住 全进程的证据写入。改为事务内只解析绑定元数据(Store.Binding),出锁后再校验 与串流;blob 内容寻址且 GC 有 24h 宽限,已打开的 fd 也不受 unlink 影响。 #102 高:traffic_evidence_snapshots 的 url/req_head/resp_head 直接来自目标 原始报文,未清洗就写 PG TEXT。目标返回非 UTF-8 头(GBK filename)或 NUL 会让 RecordFindingTx 整个事务回滚,已确认的漏洞完全不落库。新增 TrafficEvidenceSnapshot.Normalize(),在算 ID 之前套用仓库既有的 utf8Clean, 保证哈希与入库字节一致。 #102 中:自动绑定默认关闭,reporter 便不会传 evidence_version, SetFindingReportVersionByNodeID 按 legacy 语义写 -1,报告永久显示「待更新」 且 UI 无处清除;老库还因 reporter_agent_seed_v1 守卫拿不到新触发文案。 触发消息改为无条件先读 get_finding_traffic(只读工具,不依赖捕获开关)并回传 版本,并加一次性迁移刷新老库中未被用户改动的默认文案。 #102 中:开关关闭时 report_finding 只要收到 traffic_refs/evidence_hint_id 就在写入前报错,模型幻觉出多余字段即丢掉一个真实漏洞。改为忽略这两个参数 照常登记,由既有的 evidence_status=not_bound 说明未绑定。 #101 中:删掉 RuleID!=0 判断后,每次模型放行都写一行带完整审计快照的 intercept_pending(单行最坏 24×8KiB 上下文 + 32KiB 输入)。开启兜底裁判的 渗透任务会灌入数百 MB,并经 task_archives 的 SELECT * 进入归档。放行行改为 只留决策元数据与执行结果,丢掉重放快照;ask/deny 路径不变。 #103 中:FindingRetestForConversation 出错时在收尾 defer 注册前就 return, 复测行永远停在 pending —— 漏洞列表常驻「复测中」,后续发起复测被去重拦下, 只有重启才能清。新增 FailPendingRetestForConversation 按会话 ID 收尾。
This commit is contained in:
1 parent
e8efce605c
commit
72a52e9202
10 files changed
+197
-55
No files matched your search
@@ -166,6 +166,15 @@ func auditFor(ctx context.Context, dec Decision, input []byte, status string) *d
|
||||
if a.Correlation != "exact" {
|
||||
a.ExecutionStatus = "unknown"
|
||||
}
|
||||
// A straight rule/model allow is a routine event, not an approval a human
|
||||
// reviewed: with the fallback judge on, a pentest task emits thousands of
|
||||
// them. Keeping the full snapshot would write 24×8KiB of context plus a
|
||||
// 32KiB prompt per row into intercept_pending — hundreds of MB, which the
|
||||
// task archive (SELECT *) then carries along. Decision metadata and the
|
||||
// execution result still land; only the bulky replay snapshot is dropped.
|
||||
// The ask path builds its audit in HandleAsk and is unaffected.
|
||||
a.UserMessage, a.UserTruncated = "", false
|
||||
a.Context, a.ContextTruncated = nil, false
|
||||
}
|
||||
if status == "denied" {
|
||||
a.EffectiveAction, a.ExecutionStatus = "deny", "not_executed"
|
||||
|
||||
+30
-1
@@ -15,7 +15,9 @@ func TestTraceExactCorrelationAndSnapshot(t *testing.T) {
|
||||
trace.Start("call-a", "Write", []byte(`{"path":"a","n":12345678901234567890}`))
|
||||
trace.Append(db.InterceptContextEntry{Kind: "text", Text: "later context"})
|
||||
callCtx := WithCall(ctx, "Write", []byte(`{ "n":12345678901234567890, "path":"a" }`))
|
||||
a := auditFor(callCtx, Decision{Action: "allow"}, nil, "allowed")
|
||||
// "pending" (the ask path) keeps the full snapshot; see TestAuditAllowDropsSnapshot
|
||||
// for why a routine allow does not.
|
||||
a := auditFor(callCtx, Decision{Action: "ask"}, nil, "pending")
|
||||
if a.Correlation != "exact" || a.ToolUseID != "call-a" || len(a.Context) != 1 || a.Context[0].Text != "prior message" {
|
||||
t.Fatalf("wrong snapshot: %+v", a)
|
||||
}
|
||||
@@ -32,6 +34,33 @@ func TestTraceExactCorrelationAndSnapshot(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A rule/model allow is logged for auditability but must not carry the replay
|
||||
// snapshot: with the fallback judge on those rows are emitted per tool call, and
|
||||
// keeping 24×8KiB of context plus a 32KiB prompt each would put hundreds of MB
|
||||
// into intercept_pending (and from there into the task archive). Decision
|
||||
// metadata and correlation must survive so the execution result still binds.
|
||||
func TestAuditAllowDropsSnapshot(t *testing.T) {
|
||||
ctx, trace := WithTrace(context.Background(), "a long user prompt", []db.InterceptContextEntry{{Kind: "user", Text: "prior message"}})
|
||||
input := []byte(`{"path":"a"}`)
|
||||
trace.Start("call-a", "Write", input)
|
||||
a := auditFor(WithCall(ctx, "Write", input), Decision{Action: "allow", RuleName: "auto"}, input, "allowed")
|
||||
if a.UserMessage != "" || a.UserTruncated || a.Context != nil || a.ContextTruncated {
|
||||
t.Fatalf("allow kept the replay snapshot: %+v", a)
|
||||
}
|
||||
if a.Correlation != "exact" || a.ToolUseID != "call-a" || a.EffectiveAction != "allow" ||
|
||||
a.ExecutionStatus != "awaiting_result" || a.RuleName != "auto" || a.InputDigest == "" {
|
||||
t.Fatalf("allow lost decision metadata: %+v", a)
|
||||
}
|
||||
|
||||
// A denial is rare and worth the full context, so it keeps its snapshot.
|
||||
denied := []byte(`{"path":"b"}`)
|
||||
trace.Start("call-b", "Write", denied)
|
||||
d := auditFor(WithCall(ctx, "Write", denied), Decision{Action: "deny"}, denied, "denied")
|
||||
if len(d.Context) == 0 || d.Context[0].Text != "prior message" || d.UserMessage == "" {
|
||||
t.Fatalf("deny lost the snapshot: %+v", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceIdenticalParallelCallsAreNeverGuessed(t *testing.T) {
|
||||
ctx, trace := WithTrace(context.Background(), "test", nil)
|
||||
input := []byte(`{"command":"pwd"}`)
|
||||
|
||||
Reference in new issue
Block a user