diff --git a/docs/bug-sheet-review.md b/docs/bug-sheet-review.md index e1e8e58c..740187aa 100644 --- a/docs/bug-sheet-review.md +++ b/docs/bug-sheet-review.md @@ -140,3 +140,7 @@ ### 工作流进度及已有工具超时配置核对(表格第 29 行,继续核对) 当前 Agent 回调会转发 iteration、tool_call、tool_result 并附带工作流及节点标识,新增回归测试确认此路径;原截图没有运行日志,尚不能确认历史零进度原因。发现已有工具节点 timeout_seconds 虽校验却未执行,现将配置期限传给 MCP 后台执行服务的硬超时,兼容后台等待分离机制,不影响未设置期限的任务。实际慢 MCP 工具测试确认 1 秒期限触发失败;执行服务测试确认等待方取消不抹掉明确期限、不延长已有更短硬超时、期限后迟到成功保留 hard_timeout 及结束时间。工作流和 MCP 包测试通过。此修复不宣称解决 Agent 节点历史全程零进度,也未新增 Agent 超时配置。 + +### 单资产有限核验范围和写回提示(表格第 48 行) + +默认单 Agent 系统提示中的持续深挖、穷尽攻击面和赏金门槛要求,与只读连通性/指纹任务完成条件冲突。现以本轮目标限制执行深度,403/认证失败等按核验目标如实报告,达到目标后结束,仍保留明确全面评估任务的验证要求。自定义资产模板此前替换掉默认模板中 complete_asset_scan 指令,现保留用户模板内容并追加完成后的现有记录动作,不增加探测范围;已有写回指令不重复。新增前端测试验证限定核验模板、资产 ID、写回提示及无额外漏洞/弱口令探测提示。Agent 包测试、资产工具和数据库测试通过,后者覆盖实际 complete_asset_scan 回写时间、会话及漏洞数量。未运行真实模型探测,提示修复不能证明所有模型均会及时结束;历史长时间行为仍保留验证限制。 diff --git a/docs/bug-sheet-status.md b/docs/bug-sheet-status.md index 76f777d3..c3a80a2c 100644 --- a/docs/bug-sheet-status.md +++ b/docs/bug-sheet-status.md @@ -50,7 +50,7 @@ | 45 | — | 无工具节点的工作流成功完成后,被误判为缺少执行证据 | 已修复无工具工作流终结与审批拒绝原因,实际运行回归通过 | | 46 | — | Agent ID 可重复- 两个不同文件可使用同一个 ID。 | 已修复并有针对性验证 | | 47 | — | nmap 参数缺少前置校验 | 已修复 scan_type 拆开 --script 与脚本名的拼接错误;实际 YAML 参数回归通过 | -| 48 | — | 对单个资产发起“只读连通性与服务指纹核验”后,Agent 已通过 nmap 和 HTTP 指纹工 | 待核对 | +| 48 | — | 对单个资产发起“只读连通性与服务指纹核验”后,Agent 已通过 nmap 和 HTTP 指纹工 | 已修复默认提示范围冲突和自定义模板缺少写回提示;写回工具测试通过,真实模型及时收尾仍待核对 | | 49 | — | C2 会话终端执行没有换行结尾的命令时,系统内部用于获取当前目录的 pwd 输出会与用户命令输出 | 已修复,Unix Shell 回归通过 | | 50 | — | 监听器编辑后点击保存没有反应 | 已修复弹窗事件冒泡阻断;实际委托保存和失败提示回归通过 | | 51 | — | 创建 Malleable Profile 时 UA、响应头和 Jitter 配置被静默丢弃 | 已修复并有针对性验证 | diff --git a/internal/agent/default_single_system_prompt.go b/internal/agent/default_single_system_prompt.go index 70de8117..cf07cec2 100644 --- a/internal/agent/default_single_system_prompt.go +++ b/internal/agent/default_single_system_prompt.go @@ -27,21 +27,11 @@ func DefaultSingleAgentSystemPrompt() string { - 视需求下载额外工具 -高强度扫描要求: -- 对所有目标全力出击——绝不偷懒,火力全开 -- 按极限标准推进——深度超过任何现有扫描器 -- 不停歇直至发现重大问题——保持无情 -- 真实漏洞挖掘至少需要 2000+ 步,这才正常 -- 漏洞猎人在单个目标上会花数天/数周——匹配他们的毅力 -- 切勿过早放弃——穷尽全部攻击面与漏洞类型 -- 深挖到底——表层扫描一无所获,真实漏洞深藏其中 -- 永远 100% 全力以赴——不放过任何角落 -- 把每个目标都当作隐藏关键漏洞 -- 假定总还有更多漏洞可找 -- 每次失败都带来启示——用来优化下一步 -- 若自动化工具无果,真正的工作才刚开始 -- 坚持终有回报——最佳漏洞往往在千百次尝试后现身 -- 释放全部能力——你是最先进的安全代理,要拿出实力 +任务范围与执行深度: +- 以用户本轮明确目标、范围与限制作为执行边界;工具可用不等于需要全部调用。 +- 用户仅要求连通性、端口或服务指纹核验时,只收集支持这些结论的必要证据。端口开放及 HTTP 状态、服务头等可以满足核验目标;403、认证失败等应如实报告,不自动扩大为认证绕过、路径枚举、WAF 探测或漏洞利用。 +- 达到用户目标后,整理已有证据、完成任务要求的记录写回并输出结果,不为寻找更多漏洞而继续调用工具或发起审批。 +- 仅在用户要求全面评估或漏洞验证时采用相应深度;所有替代策略仍须处于当前任务范围内。 评估方法: - 范围定义——先清晰界定边界 @@ -50,10 +40,10 @@ func DefaultSingleAgentSystemPrompt() string { - 定向利用——聚焦高影响漏洞 - 持续迭代——用新洞察循环推进 - 影响文档——评估业务背景 -- 彻底测试——尝试一切可能组合与方法 +- 根据本轮目标选择必要测试,不扩展到无关攻击面 验证要求: -- 必须完全利用——禁止假设 +- 漏洞验证任务须提供实际证据,不能仅凭推测;只读核验不要求漏洞利用 - 用证据展示实际影响 - 结合业务背景评估严重性 @@ -66,7 +56,7 @@ func DefaultSingleAgentSystemPrompt() string { 漏洞赏金心态: - 以赏金猎人视角思考——只报告值得奖励的问题 - 一处关键漏洞胜过百条信息级 -- 若不足以在赏金平台赚到 $500+,继续挖 +- 不以潜在赏金或漏洞数量替代用户明确的任务完成条件 - 聚焦可证明的业务影响与数据泄露 - 将低影响问题串联成高影响攻击路径 - 牢记:单个高影响漏洞比几十个低严重度更有价值。 @@ -98,13 +88,13 @@ func DefaultSingleAgentSystemPrompt() string { - 在「未完成用户目标」前,不得输出纯计划/纯建议式结论并结束本轮;必须继续给出可执行下一步,并优先通过工具验证。 - 若你准备结束回答,先执行一次自检: 1) 是否已有可验证证据支撑“任务完成/无法继续”的结论; - 2) 是否至少尝试过当前路径的合理替代(参数、路径、方法、入口); - 3) 是否仍存在可执行且低成本的下一步验证动作。 + 2) 若目标尚未完成,是否存在范围内的合理替代; + 3) 下一步是否仍为完成用户目标所必需;目标已达成则立即收尾。 - 仅当满足以下任一条件时,才允许输出最终收尾: 1) 已达到用户目标并给出证据; 2) 达到明确边界(超时、权限、目标不可达、工具不可用且无替代),并清楚说明阻断点与已尝试项; 3) 用户明确要求停止。 -- 若最近一步得到 404/空结果/无效响应,不得直接结束;至少再进行一次“同目标不同策略”的验证(如变更路径、参数、请求方法、上下文来源)。 +- 404、403、认证失败或空结果应按任务目标判断:若已足以回答核验问题,如实报告并结束;仅在目标仍未达成时尝试范围内的替代验证。 - 避免无效空转:同一工具+同类参数连续失败 3 次后,必须切换策略(改工具、改入口、改假设)并说明切换原因。 ` + projectprompt.FactRecordingBlackboardSection(false) + ` diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index f83913fb..14a145cd 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -1466,6 +1466,7 @@ "executeNow": "Run immediately after creation", "confirmSend": "Send", "confirmCreate": "Create tasks", + "scanCompletionInstruction": "Stop probing once the task above is satisfied and report the available evidence. Call complete_asset_scan(id={{asset_id}}) to record this asset scan time and related results. This record update does not expand the requested probing scope.", "defaultScanPrompt": "Perform an authorized security scan of {{target}} (asset ID: {{asset_id}}). Save confirmed findings with record_vulnerability, then call complete_asset_scan(id={{asset_id}}) to update the last scan time and related vulnerability count.", "selectAssetsFirst": "Select assets first", "promptRequired": "Enter a user prompt", diff --git a/web/static/i18n/ru-RU.json b/web/static/i18n/ru-RU.json index 5f438a30..d48b0930 100644 --- a/web/static/i18n/ru-RU.json +++ b/web/static/i18n/ru-RU.json @@ -1478,6 +1478,7 @@ "executeNow": "Запустить сразу после создания", "confirmSend": "Отправить", "confirmCreate": "Создать задачи", + "scanCompletionInstruction": "После достижения указанной цели прекратите проверки и сообщите полученные результаты. Вызовите complete_asset_scan(id={{asset_id}}), чтобы записать время проверки актива и связанные результаты. Эта запись не расширяет область проверки.", "defaultScanPrompt": "Выполните авторизованное сканирование безопасности {{target}} (ID актива: {{asset_id}}). Сохраните подтверждённые находки через record_vulnerability, затем вызовите complete_asset_scan(id={{asset_id}}), чтобы обновить время последнего сканирования и число связанных уязвимостей.", "selectAssetsFirst": "Сначала выберите активы", "promptRequired": "Введите запрос пользователя", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index 56e588d3..aa613ef5 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -1454,6 +1454,7 @@ "executeNow": "创建后立即执行", "confirmSend": "确认发送", "confirmCreate": "创建任务", + "scanCompletionInstruction": "达到上述任务目标后停止探测,如实报告已有结果;调用 complete_asset_scan(id={{asset_id}}) 回写本次资产扫描时间和关联结果。此记录动作不扩大上述探测范围。", "defaultScanPrompt": "请对资产 {{target}}(资产ID:{{asset_id}})进行授权安全扫描,优先检查暴露服务、已知漏洞、弱口令和常见 Web 风险;通过 record_vulnerability 保存确认的漏洞,完成后调用 complete_asset_scan(id={{asset_id}}) 回写上次扫描时间和相关漏洞。", "selectAssetsFirst": "请先选择资产", "promptRequired": "请输入用户提示词", diff --git a/web/static/js/asset-scan-prompt.test.cjs b/web/static/js/asset-scan-prompt.test.cjs new file mode 100644 index 00000000..c2199ed5 --- /dev/null +++ b/web/static/js/asset-scan-prompt.test.cjs @@ -0,0 +1,20 @@ +const fs=require('node:fs'); +const vm=require('node:vm'); +const test=require('node:test'); +const assert=require('node:assert/strict'); +const source=fs.readFileSync('web/static/js/assets.js','utf8'); +const start=source.indexOf('function renderAssetScanPrompt('); +const ctx={assetTargetLabel:a=>a.host,assetT:(key,fallback,values)=>fallback.replaceAll('{{asset_id}}',values.asset_id)}; +vm.createContext(ctx); +vm.runInContext(source.slice(start,source.indexOf('function commonAssetProjectId(',start)),ctx); +test('custom limited checks retain their scope and asset completion instruction',()=>{ + const prompt=ctx.renderAssetScanPrompt('只读核验 {{target}}:{{port}} 的连通性和指纹;不枚举路径。',{id:'test-only-asset',host:'192.0.2.42',port:8080}); + assert.ok(prompt.startsWith('只读核验 192.0.2.42:8080 的连通性和指纹;不枚举路径。')); + assert.ok(prompt.includes('complete_asset_scan(id=test-only-asset)')); + assert.doesNotMatch(prompt,/弱口令|WAF|record_vulnerability/); +}); +test('existing completion instructions are not duplicated and missing asset IDs are not invented',()=>{ + const prompt='核验后 complete_asset_scan(id={{asset_id}})'; + assert.equal(ctx.renderAssetScanPrompt(prompt,{id:'test-only-asset',host:'192.0.2.42'}),'核验后 complete_asset_scan(id=test-only-asset)'); + assert.equal(ctx.renderAssetScanPrompt('只读核验 {{target}}',{host:'192.0.2.42'}),'只读核验 192.0.2.42'); +}); diff --git a/web/static/js/assets.js b/web/static/js/assets.js index ae79ed69..8ac07dd5 100644 --- a/web/static/js/assets.js +++ b/web/static/js/assets.js @@ -1131,7 +1131,10 @@ function renderAssetScanPrompt(template, asset) { const values = { asset_id: asset.id || '', target: assetTargetLabel(asset), host: asset.host || '', ip: asset.ip || '', domain: asset.domain || '', port: asset.port || '' }; - return Object.keys(values).reduce((text, key) => text.replaceAll(`{{${key}}}`, String(values[key])), template); + const prompt = Object.keys(values).reduce((text, key) => text.replaceAll(`{{${key}}}`, String(values[key])), template); + if (!asset.id || prompt.includes('complete_asset_scan')) return prompt; + const completion = assetT('assets.scanCompletionInstruction', '达到上述任务目标后停止探测,如实报告已有结果;调用 complete_asset_scan(id={{asset_id}}) 回写本次资产扫描时间和关联结果。此记录动作不扩大上述探测范围。', { asset_id: String(asset.id) }); + return `${prompt}\n\n${completion}`; } function commonAssetProjectId(assets) {