From 99ac96da2fc01585d8428948dbc024b465972855 Mon Sep 17 00:00:00 2001 From: Autumn-27 <53844290+Autumn-27@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:19:04 -0400 Subject: [PATCH] =?UTF-8?q?feat(agent):=20=E4=BC=98=E5=8C=96=20planner/wor?= =?UTF-8?q?ker=20=E6=8F=90=E7=A4=BA=E8=AF=8D=E5=B9=B6=E5=8A=A0=E9=87=8F?= =?UTF-8?q?=E5=8C=96=E9=AA=8C=E6=94=B6=E6=A0=B8=E5=AF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - planner 判目标新增「量化验收核对」:覆盖度/flag 数/权限等可量化目标 未达标时禁止 prove_goal,修复覆盖度要求 100%、实测 40% 却被判完成 - 重写 planner「0 意图」判定:仅在「方向已被现有意图覆盖」或「下一步 依赖在跑 work 的产出」两种情况才 0 意图,避免目标未达成时过早收手 - 强化 worker 否定结论证据门槛:未穷尽手段/证据弱一律标 inferred, 避免轻率的 observed 否定把整条路线焊死 - 加 reseedPlannerPrompt / reseedWorkerPrompt 一次性迁移,旧库追加新 默认版本并切换,旧版保留在历史可恢复 --- CHANGELOG.md | 12 ++++++++ agent/planner.go | 6 +++- agent/worker.go | 2 +- server/orchestration.go | 62 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09e4d36..3ec251f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ ## [Unreleased] +### Agent + +#### 新增的功能 + +- planner 判目标时新增「量化验收核对」:目标含可量化验收条件(资产测试覆盖度达到 X%、拿到 N 个 flag、获得某权限)时,`prove_goal` 前必须核对 `graph_overview` 的实测值(`coverage.pct`、findings 计数等),实测未达标则禁止 `prove_goal`、改为派意图补足差距,不得以「主要部分已完成/大体达成」为由提前标 met。修复了目标要求覆盖度 100%、实测仅 40% 却被判完成的问题。 + +#### 修改的功能 + +- 重写 planner 提示词中「本轮 0 意图」的判定依据:原文将 0 意图描述为「最常见、最重要的原则」,会让规划者在目标未达成、范围内仍有未测面时过早收手。现改为只在两种具体情况下才应产出 0 意图——① 想到的方向都已被 open/running/recent_done 意图覆盖;② 下一步依赖当前正在运行的 work 的产出、而产出尚未出现(应等其跑完、图更新后的下一次唤醒再规划)。并补充反向约束:确有未被覆盖且不依赖在跑 work 的新方向、或目标未达成且仍有未测面时,不要因「0 意图常见」而收手。 +- 强化 worker 提示词中「否定结论的证据门槛」:对「不可注入/端口关闭/无登录入口」等可能让规划者放弃一整条方向的否定结论,要求下结论前先穷尽该意图内的合理手段(换编码/参数/路径/方法);手段未走完或证据偏弱一律标 `confidence=inferred`,避免用轻率的 `observed` 否定把整条路线焊死(任务早期的错误否定尤其会把方向带偏且难以自愈)。 +- 上述 planner / worker 默认提示词变更通过一次性迁移(`reseedPlannerPrompt` / `reseedWorkerPrompt`,各带 settings flag 守卫)追加为新版本并切为当前版本;旧版本保留在版本历史中,自定义过提示词的用户可在版本记录里恢复。 + ### 运维 #### 新增的功能 diff --git a/agent/planner.go b/agent/planner.go index 01c1bb8..88f2013 100644 --- a/agent/planner.go +++ b/agent/planner.go @@ -242,7 +242,10 @@ const plannerDefaultTmpl = `你是一个授权渗透测试系统的"规划者" 任务目标:{{.Goal}} -⚠️ 最重要的原则:**这一轮生成 0 个意图是完全正常、而且是最常见的结果。** 你不是"每次都要产出意图"的机器。绝大多数唤醒,frontier 里已有的意图已经覆盖了所有已知方向,你应当什么都不加、直接结束。重复/换措辞地生成已经存在的意图是严重错误。 +⚠️ **生成 0 个意图是合法、正常的结果,但它有【具体的正当理由】,不是"少派更稳"的默认姿态。** 只在下列情况才该产出 0 个意图: +- **已覆盖**:你想到的方向都已被 open / running / recent_done 意图覆盖——重复或换措辞地再生成已存在的意图是严重错误;或 +- **等待依赖**:下一步依赖【当前正在运行的 work】的产出,而产出还没出来——此时应【等这些 work 跑完、图更新后的下一次唤醒再规划】,现在硬派会让下游 worker 拿不到还不存在的前置而空转。 +反过来:若确有【未被任何意图覆盖、且不依赖在跑 work】的新方向,或【目标尚未达成且范围内仍有未测面】,就【不要】因为"0 意图常见"而收手——该派就派。别把 0 意图当成偷懒的默认。 决策流程(每次唤醒): 1. **完整态势已直接附在本提示下方(就是 graph_overview 的返回,无需再调它)**:task(**原始任务标题+目标**,即根节点)、资产计数、goals 及其状态、open/running/recent_done 意图、sites_without_endpoints、findings(**确认漏洞**数)、facts(**探索事实/结论**数,与漏洞是两类)、recent_facts(最近事实的 {id, summary, confidence?},含"端口关闭/不可注入"等**否定结论**——据此别再为已探明的死路生成意图;但 confidence=inferred 的否定结论只是【推断】、证据弱,别当铁案,若该方向对目标很关键值得派一条复核意图)。**这里的探索节点(goals/意图/facts/findings)都只含本任务的**(绝不会有别的任务的目标);**资产图则全局共享**(多任务同一份,资产计数是全局在范围内的数据,非本任务独有)。需要更深的细节时才按需调:list_facts 列全部事实、list_findings 列全部漏洞、**node_detail(id)** 取某条的完整证据/详情(列表/recent_facts 只给摘要)。 @@ -250,6 +253,7 @@ const plannerDefaultTmpl = `你是一个授权渗透测试系统的"规划者" - sites_without_endpoints / findings——"哪些方向【可能】需要探索"。 - 只有需要某一片的细节时,才**按需**调 list_assets(pull 模式:可用 q 关键字搜索,可叠加 type/company_id/task_id 过滤,分页 limit/offset;或用 id/ids 直接取)、asset_neighbors、list_findings。资产图全局共享,别默认拉全量。资产中可能包含非本次任务涉及到的资产,所以需要主要出现非本次任务相关的资产时忽略这些资产。 2. 判目标(核心职责):graph_overview 的 goals 字段已含目标与状态;对已被某发现/事实证明的未达成目标,调 prove_goal(goal_id,evidence_id,reason) 标记 met。**当你用 prove_goal 标记的这一个恰好是最后一个未完成目标时,系统会自动判定整个任务完成**——收官完全由逐个 prove_goal 驱动,你无需、也没有别的“一键完成”手段。 + ⚠️ **量化验收核对(严禁提前盖章)**:若某目标含【可量化的验收条件】(如"资产测试覆盖度达到 X%"、"拿到 N 个 flag"、"获得某权限"),prove_goal **前【必须】核对本提示上方 graph_overview 里的实测值**(coverage.pct、findings 计数等):实测【未达标】就【禁止】prove_goal,改为派意图补足差距——**【不得】以"主要部分已完成/大体达成/核心目标已拿下"为由提前标 met**。例:目标要求覆盖度 100%、而 graph_overview 实测 coverage.pct=40%,则该目标【未达成】,继续派补测意图,不许 prove_goal。 2.5. **(可选,仅限开局、极轻量)探测理解**:你具备 Bash 等执行能力,它的**唯一正当用途**是——当**图里几乎还没有事实**(recent_facts 基本为空、任务刚开始)、仅凭态势无法把初始意图描述具体时,对目标做**极少量、只读**的探测(如 1–2 次 curl 看首页/指纹),据此产出更精准的**初始意图**。 ⚠️ **牢记你的身份边界:你是"规划者",不是"执行者"。你在这里做的一切都只为【生成/说清意图】,绝不是【在 plan 里把活干了】。** 探测的**唯一合法产物是一句更精准的意图描述**——绝不能是漏洞的发现、验证、利用,也不能是端点/目录/参数的枚举结果。任何"我顺手把这个也测了/确认了"的念头都是越界:那是 worker 在 work 阶段该做的事,你只需把它**写成一个意图派下去**。 **三条硬性边界,务必守住**: diff --git a/agent/worker.go b/agent/worker.go index b6c6e8c..c75c97b 100644 --- a/agent/worker.go +++ b/agent/worker.go @@ -169,7 +169,7 @@ const workerDefaultTmpl = `你是一个授权渗透测试系统的"执行者"(wo 可用工具: - insert_assets:登记新资产(资产图)。**你只传原始信息,key 与父子关联由代码算**:新接口→传完整 url+method(代码自动建 domain→site→endpoint、自动抽 URL 里的 query 参数,body/header 参数放 params);指纹→type=tech,name=技术名,on_url=站点地址,props填{version,category}。多个资产放 assets 数组一次批量登记。属性写在资产自己的 props 上,探索结论不要写这里。 -- record_fact:把探索【事实/结论】写入探索图并连到意图(传 intent_id)。正向/否定结论、观察、判断用它;**一次探索的多个观察汇总成一条事实**(summary 总结一句话 + detail 放细节),不要一个属性一条。真有多条不同结论才用 facts 数组。**只写你真实看到的**:给 evidence(一行关键证据:命令+关键输出,简洁,别粘大段——细节在 detail)、标 confidence(observed 直接看到 / inferred 推断);**否定结论**(不可注入/端口关闭等)尤其要给证据、证据弱就标 inferred,别让错的否定误导规划者放弃方向。 +- record_fact:把探索【事实/结论】写入探索图并连到意图(传 intent_id)。正向/否定结论、观察、判断用它;**一次探索的多个观察汇总成一条事实**(summary 总结一句话 + detail 放细节),不要一个属性一条。真有多条不同结论才用 facts 数组。**只写你真实看到的**:给 evidence(一行关键证据:命令+关键输出,简洁,别粘大段——细节在 detail)、标 confidence(observed 直接看到 / inferred 推断);**否定结论的证据门槛**(不可注入/端口关闭/无登录入口等【可能让规划者放弃一整条方向】的结论):下结论前先确认你已【穷尽这条意图内的合理手段】(换编码/换参数/换路径/换方法);手段没走完、或证据只是"看起来像/大概率如此",一律标 confidence=inferred。**宁可标 inferred 让规划者复核,也别用一个轻率的 observed 否定把一整条路线焊死**——尤其任务早期,一个错误的 observed 否定会把整个任务带偏、且后续很难自己扳回来。 - report_finding:确认漏洞 → 记录(含 PoC,传 intent_id=你领到的意图id)。**只有你在本次运行里真实触发过该漏洞、拿到了可复现的证据(请求/响应或命令输出)才用它。** 严禁把下列当作已确认漏洞上报:仅凭版本号/指纹匹配到某 CVE、仅凭"参数看起来可注入"、仅凭外部漏洞库/更新日志/代码 diff 推断。**不要用查 CVE 库或"对比补丁版本"替代实际触发。** 触发不了但确有嫌疑,就用 record_fact 记一条 confidence=inferred 的事实(描述嫌疑点+为何未能触发),交给规划者派后续意图,别硬记成 finding。 - list_assets(查询资产,非探索节点) / asset_neighbors / list_facts(探索事实) / list_findings(漏洞) / node_detail(探索节点 id,非资产 id):按需查上下文。 diff --git a/server/orchestration.go b/server/orchestration.go index 9057ea7..287eaa0 100644 --- a/server/orchestration.go +++ b/server/orchestration.go @@ -452,6 +452,8 @@ func (s *Server) seedOrchestrationTools() { s.unbindGoalMetDefault() s.reseedGoalsPrompt() // goals 提示词加入「抽操作约束」步 → 旧库追加一版新默认(一次性) s.reseedMainAgentPrompt() // mainagent 提示词加入「目标达成后 add_intent 反问是否建目标」(一次性) + s.reseedPlannerPrompt() // planner 提示词:重写「0 意图」正当理由 + 加量化验收核对(一次性) + s.reseedWorkerPrompt() // worker 提示词:加否定结论证据门槛(一次性) s.seedReporterAgent() // 预置「报告撰写」agent + 工具绑定 + finding 触发器(一次性) // 注:pentest 的默认工具绑定无需迁移——BuiltinToolSeeds 在全新初始化时就把 // list_assets/insert_assets/report_finding/list_findings/list_companies 连同 @@ -571,6 +573,66 @@ func (s *Server) reseedMainAgentPrompt() { log.Printf("[prompts] mainagent 提示词已追加新默认版本(加入目标达成后反问建目标,一次性)") } +// reseedPlannerPrompt 把 planner 提示词刷成【当前代码默认】——默认正文重写了「0 意图」的正当理由 +// (从笼统"最常见"改为"仅在已覆盖/等待在跑 work 依赖时才 0 意图")、并新增「量化验收核对」(覆盖度等 +// 可量化目标未达标禁止 prove_goal)。SeedPromptIfEmpty 首插入only,旧库已有版本收不到,故用版本管理 +// 【追加一个新版本】并切过去(ResetPromptToDefault),旧版本仍保留在历史里,用户若自定义过可从版本记录 +// 找回。settings flag 守卫 → 只做一次。全新库无需处理(SeedPromptIfEmpty 已 seed 最新默认)。与 +// reseedGoalsPrompt 完全同构。 +func (s *Server) reseedPlannerPrompt() { + const flag = "planner_prompt_zerointent_acceptance_v1" + if v, _, _ := s.m.pg.GetSetting(flag); v == "true" { + return + } + defer func() { _ = s.m.pg.SetSetting(flag, "true") }() // 无论成功与否只尝试一次 + a, err := s.m.pg.GetAgentByKey("planner") + if err != nil || a == nil { + return // 全新库尚未建 agent 行时,seedPrompts 会直接 seed 最新默认,无需此迁移 + } + tmpl := agent.BuiltinPromptSeeds()["planner"] + if tmpl == "" { + return + } + // 全新库 seedPrompts 已 seed 最新默认 → 当前版本已等于代码默认,不必再追加重复版本。 + if cur, err := s.m.pg.CurrentPrompt(a.ID); err == nil && cur == tmpl { + return + } + if _, err := s.m.pg.ResetPromptToDefault(a.ID, tmpl); err != nil { + log.Printf("[prompts] planner 提示词重刷为新默认失败: %v", err) + return + } + log.Printf("[prompts] planner 提示词已追加新默认版本(重写0意图理由+加量化验收核对,一次性)") +} + +// reseedWorkerPrompt 把 worker 提示词刷成【当前代码默认】——默认正文强化了「否定结论的证据门槛」 +// (未穷尽手段/证据弱一律标 inferred,别用轻率 observed 否定焊死路线)。SeedPromptIfEmpty 首插入only, +// 旧库已有版本收不到,故用版本管理【追加一个新版本】并切过去,旧版本仍保留在历史里可找回。settings flag +// 守卫 → 只做一次。全新库无需处理。与 reseedGoalsPrompt 完全同构。 +func (s *Server) reseedWorkerPrompt() { + const flag = "worker_prompt_negfact_threshold_v1" + if v, _, _ := s.m.pg.GetSetting(flag); v == "true" { + return + } + defer func() { _ = s.m.pg.SetSetting(flag, "true") }() // 无论成功与否只尝试一次 + a, err := s.m.pg.GetAgentByKey("worker") + if err != nil || a == nil { + return // 全新库尚未建 agent 行时,seedPrompts 会直接 seed 最新默认,无需此迁移 + } + tmpl := agent.BuiltinPromptSeeds()["worker"] + if tmpl == "" { + return + } + // 全新库 seedPrompts 已 seed 最新默认 → 当前版本已等于代码默认,不必再追加重复版本。 + if cur, err := s.m.pg.CurrentPrompt(a.ID); err == nil && cur == tmpl { + return + } + if _, err := s.m.pg.ResetPromptToDefault(a.ID, tmpl); err != nil { + log.Printf("[prompts] worker 提示词重刷为新默认失败: %v", err) + return + } + log.Printf("[prompts] worker 提示词已追加新默认版本(加否定结论证据门槛,一次性)") +} + // seedReporterAgent 预置一个「报告撰写」自定义 agent(builtin=false,可在 UI 编辑/删除): // 绑定 update_finding_report + 任务查询工具,并挂一个「report_finding 被调用即触发」的 // 触发器 —— 每登记一个漏洞就唤起它写详细报告。一次性(settings flag 守卫):用户删掉后不再重建。