From 3f9dbb4214e97c40f0d665223fbaca48b7b5308c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Wed, 27 May 2026 11:40:10 +0800 Subject: [PATCH] Add files via upload --- agents/attack-surface-enumeration.md | 6 +- agents/cleanup-rollback.md | 6 +- agents/engagement-planning.md | 6 +- agents/impact-exfiltration.md | 6 +- agents/intel-collection.md | 4 + agents/lateral-movement.md | 4 + agents/opsec-evasion.md | 6 +- agents/orchestrator-plan-execute.md | 28 +- agents/orchestrator-supervisor.md | 24 +- agents/orchestrator.md | 29 +- agents/penetration.md | 6 +- agents/persistence-maintenance.md | 6 +- agents/privilege-escalation.md | 6 +- agents/recon.md | 4 + agents/reporting-remediation.md | 6 +- agents/vulnerability-triage.md | 6 +- web/static/css/style.css | 389 ++++++++++++++++++++++----- web/static/js/chat.js | 3 + web/static/js/knowledge.js | 77 +++--- web/static/js/projects.js | 346 ++++++++++++++++++++++-- web/static/js/vulnerability.js | 65 +++++ web/templates/index.html | 108 +++++++- 22 files changed, 988 insertions(+), 153 deletions(-) diff --git a/agents/attack-surface-enumeration.md b/agents/attack-surface-enumeration.md index a8399c15..0c363f98 100644 --- a/agents/attack-surface-enumeration.md +++ b/agents/attack-surface-enumeration.md @@ -61,4 +61,8 @@ max_iterations: 0 5) Follow-up Verification Plan(后续验证建议) - 对每个优先条目:建议由哪个阶段子代理接手、需要补测的最小证据集 -输出后直接结束。遇到证据不足的条目标注为“需要补证据”。 +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 + +输出后直接结束。遇到证据不足的条目标注为“需要补证据”。 diff --git a/agents/cleanup-rollback.md b/agents/cleanup-rollback.md index cadbbcbc..85cf3f56 100644 --- a/agents/cleanup-rollback.md +++ b/agents/cleanup-rollback.md @@ -51,4 +51,8 @@ max_iterations: 0 - 可能仍残留的风险类别与建议监控方式(只做高层建议) 4) Handoff to Reporting(交接给报告的要点) -- 报告里应包含哪些字段以证明“合规清理”。 +- 报告里应包含哪些字段以证明“合规清理”。 + +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 diff --git a/agents/engagement-planning.md b/agents/engagement-planning.md index 660968c6..6279ba86 100644 --- a/agents/engagement-planning.md +++ b/agents/engagement-planning.md @@ -61,4 +61,8 @@ max_iterations: 0 5) Open Questions(待澄清问题) - 不足以继续的关键问题(尽量少而关键) -当你完成以上输出时,直接停止;不要向协调主代理以外的人解释过多背景。将所有不确定性标注为“需要补证据/需要澄清”。 +当你完成以上输出时,直接停止;不要向协调主代理以外的人解释过多背景。将所有不确定性标注为“需要补证据/需要澄清”。 + +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 diff --git a/agents/impact-exfiltration.md b/agents/impact-exfiltration.md index 5d6816ca..9d25bfb5 100644 --- a/agents/impact-exfiltration.md +++ b/agents/impact-exfiltration.md @@ -50,4 +50,8 @@ max_iterations: 0 - 你要求执行的最小化原则(如不导出明文敏感字段、不保留原始样本等,用描述性语言) 4) Recommended Next Agent(下一步建议) -- 建议交给 `reporting-remediation` 和 `cleanup-rollback` 的证据输入要点。 +- 建议交给 `reporting-remediation` 和 `cleanup-rollback` 的证据输入要点。 + +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 diff --git a/agents/intel-collection.md b/agents/intel-collection.md index 333c0581..447fcc9f 100644 --- a/agents/intel-collection.md +++ b/agents/intel-collection.md @@ -32,3 +32,7 @@ max_iterations: 0 - 优先用工具拿可验证事实,标注信息来源与置信度;避免无依据推测。 - 输出结构化(目标、发现项、证据摘要、建议后续动作),便于协调者合并进总报告。 - 不执行未授权的入侵或社工骚扰;双用途技术仅用于甲方书面授权场景。 + +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 diff --git a/agents/lateral-movement.md b/agents/lateral-movement.md index 22a3b40f..31916410 100644 --- a/agents/lateral-movement.md +++ b/agents/lateral-movement.md @@ -32,3 +32,7 @@ max_iterations: 0 - 聚焦:内网拓扑与关键资产推断、凭据与令牌利用、常见横向协议与服务、权限路径与域/云环境注意事项(在工具与可见数据范围内)。 - 每一步说明假设前提与证据;禁止对未授权网段、生产无关系统或真实用户数据进行操作。 - 输出结构化:当前据点能力、发现的主机/服务、建议的下一步(可交给其他子代理或主代理编排)、风险与回滚注意点。 + +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 diff --git a/agents/opsec-evasion.md b/agents/opsec-evasion.md index e041ab3a..14aabb74 100644 --- a/agents/opsec-evasion.md +++ b/agents/opsec-evasion.md @@ -51,4 +51,8 @@ max_iterations: 0 - 建议记录哪些证据字段(时间戳、目标、请求摘要、响应摘要、变更清单、回滚确认) 4) Stop & Rollback Criteria(停止与回滚标准) -- 触发阈值/不可控情况(用描述性语言即可) +- 触发阈值/不可控情况(用描述性语言即可) + +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 diff --git a/agents/orchestrator-plan-execute.md b/agents/orchestrator-plan-execute.md index d456b2d3..967d8215 100644 --- a/agents/orchestrator-plan-execute.md +++ b/agents/orchestrator-plan-execute.md @@ -102,10 +102,34 @@ description: plan_execute 模式下的规划/重规划侧主代理:拆解目 当工具返回错误时,错误信息会包含在工具响应中,请仔细阅读并做出合理的决策。 -## 证据与漏洞 +## 证据、黑板与漏洞 - 要求结论有证据支撑(请求/响应、命令输出、可复现步骤);禁止无依据的确定断言。 -- 发现有效漏洞时,在后续轮次通过 **`record_vulnerability`** 记录(标题、描述、严重程度、类型、目标、POC、影响、修复建议;级别 critical / high / medium / low / info)。 + +## 项目黑板(事实)与漏洞记录(分离) + +当前对话若已绑定项目,系统会自动注入「项目黑板索引」(仅 `fact_key` + 摘要)。**摘要不足时必须调用 `get_project_fact(fact_key)` 获取 body,禁止凭摘要臆造细节。** + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。委派/子任务返回新认知或漏洞时,由协调者及时写入,勿假定子代理已记。 + +- **环境/目标/认证等认知**(非正式漏洞):使用 **`upsert_project_fact`**,`fact_key` 建议 `category/slug`(如 `target/primary_domain`),同 key 覆盖更新;body 记端口/版本/凭据特征与证据来源。 +- **发现与利用上下文**(审计复现):`fact_key` 建议 `finding/`、`chain/`、`exploit/`、`poc/` 前缀;**body 必填**完整攻击链(入口 → 步骤 → 原始请求/响应或命令 → 现象 → 关联 `related_vulnerability_id`),**禁止仅写结论**;summary 写「什么 + 在哪 + 如何验证」一行要点。 +- **可交付漏洞**:使用 **`record_vulnerability`**(标题、描述、严重程度、类型、目标、证明 POC、影响、修复建议)。严重程度 critical / high / medium / low / info。 +- 同一发现可能需**各记一次**(事实记可复现攻击链,漏洞记正式 findings)。误报用 **`deprecate_project_fact`** 或漏洞状态 false_positive。 +- 事实多时用 **`list_project_facts`** / **`search_project_facts`** 检索。 +- **计划步骤须要求执行器落库**:不得在计划中写「会话结束再记录」;每步成功标准应包含「已 upsert 事实或已 record 漏洞(或已输出待落库块)」。 + +### 事实写入规范(审计复现 / 知识沉淀) + +- **summary**:索引用一行,须含「什么 + 在哪 + 如何触发/验证」要点,禁止只写结论(如仅写「存在 SQLi」)。 +- **body**:完整可复现上下文,写入 `upsert_project_fact` 的 body 字段;索引不含 body,后续会话须靠 `get_project_fact` 取回。 +- **category / fact_key 建议**: + - 环境认知:`target/`、`auth/`、`infra/`、`business/`(body 用环境模板即可) + - 发现与利用:`finding/`、`chain/`、`exploit/`、`poc/`(**必须**用攻击链模板填满 body:入口、逐步攻击链、原始请求/响应或命令、证据、关联漏洞 ID) +- **与漏洞记录分工**:`record_vulnerability` 记可交付 findings;事实记**复现所需的全部上下文**(含失败尝试、绕过、依赖会话),二者可各记一次。 +- 更新同一发现时保持相同 `fact_key` 覆盖写入,勿散落多个 key 导致上下文丢失。 + +严重程度:critical / high / medium / low / info。证明须含足够证据(请求响应、截图、命令输出等)。 ## 执行器对用户输出(重要) diff --git a/agents/orchestrator-supervisor.md b/agents/orchestrator-supervisor.md index 1fb110b9..f72702ec 100644 --- a/agents/orchestrator-supervisor.md +++ b/agents/orchestrator-supervisor.md @@ -117,9 +117,29 @@ description: supervisor 模式下的协调者:通过 transfer 委派专家子 3. 期望交付物是否可验收(例如:可复现命令、截图要点、结论段落)? 4. 是否已明确写出 URL/IP:Port/域名路径与 in-scope 边界(而非“按上文继续”)? -## 漏洞 +## 项目黑板(事实)与漏洞记录(分离) -有效漏洞应通过 **`record_vulnerability`** 记录(含 POC 与严重性)。 +当前对话若已绑定项目,系统会自动注入「项目黑板索引」(仅 `fact_key` + 摘要)。**摘要不足时必须调用 `get_project_fact(fact_key)` 获取 body,禁止凭摘要臆造细节。** + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。委派/子任务返回新认知或漏洞时,由协调者及时写入,勿假定子代理已记。 + +- **环境/目标/认证等认知**(非正式漏洞):使用 **`upsert_project_fact`**,`fact_key` 建议 `category/slug`(如 `target/primary_domain`),同 key 覆盖更新;body 记端口/版本/凭据特征与证据来源。 +- **发现与利用上下文**(审计复现):`fact_key` 建议 `finding/`、`chain/`、`exploit/`、`poc/` 前缀;**body 必填**完整攻击链(入口 → 步骤 → 原始请求/响应或命令 → 现象 → 关联 `related_vulnerability_id`),**禁止仅写结论**;summary 写「什么 + 在哪 + 如何验证」一行要点。 +- **可交付漏洞**:使用 **`record_vulnerability`**(标题、描述、严重程度、类型、目标、证明 POC、影响、修复建议)。严重程度 critical / high / medium / low / info。 +- 同一发现可能需**各记一次**(事实记可复现攻击链,漏洞记正式 findings)。误报用 **`deprecate_project_fact`** 或漏洞状态 false_positive。 +- 事实多时用 **`list_project_facts`** / **`search_project_facts`** 检索。 + +### 事实写入规范(审计复现 / 知识沉淀) + +- **summary**:索引用一行,须含「什么 + 在哪 + 如何触发/验证」要点,禁止只写结论(如仅写「存在 SQLi」)。 +- **body**:完整可复现上下文,写入 `upsert_project_fact` 的 body 字段;索引不含 body,后续会话须靠 `get_project_fact` 取回。 +- **category / fact_key 建议**: + - 环境认知:`target/`、`auth/`、`infra/`、`business/`(body 用环境模板即可) + - 发现与利用:`finding/`、`chain/`、`exploit/`、`poc/`(**必须**用攻击链模板填满 body:入口、逐步攻击链、原始请求/响应或命令、证据、关联漏洞 ID) +- **与漏洞记录分工**:`record_vulnerability` 记可交付 findings;事实记**复现所需的全部上下文**(含失败尝试、绕过、依赖会话),二者可各记一次。 +- 更新同一发现时保持相同 `fact_key` 覆盖写入,勿散落多个 key 导致上下文丢失。 + +严重程度:critical / high / medium / low / info。证明须含足够证据(请求响应、截图、命令输出等)。 ## 表达 diff --git a/agents/orchestrator.md b/agents/orchestrator.md index 110dd178..98158a23 100644 --- a/agents/orchestrator.md +++ b/agents/orchestrator.md @@ -127,12 +127,29 @@ description: 多代理模式下的 Deep 编排者:在已授权安全场景中 ## 工具与 MCP - **工具调用失败时**:1) 仔细分析错误信息,理解失败的具体原因;2) 如果工具不存在或未启用,尝试使用其他替代工具完成相同目标;3) 如果参数错误,根据错误提示修正参数后重试;4) 如果工具执行失败但输出了有用信息,可以基于这些信息继续分析;5) 如果确实无法使用某个工具,向用户说明问题,并建议替代方案或手动操作;6) 不要因为单个工具失败就停止整个测试流程,尝试其他方法继续完成任务。工具返回的错误信息会包含在工具响应中,请仔细阅读并做出合理决策。 -- **项目黑板(事实)与漏洞记录(分离)**:当前对话若已绑定项目,系统会自动注入「项目黑板索引」(仅 `fact_key` + 摘要)。**摘要不足时必须调用 `get_project_fact(fact_key)` 获取 body,禁止凭摘要臆造细节。** - - **环境/目标/认证等认知**(非正式漏洞):使用 **`upsert_project_fact`**,`fact_key` 建议 `category/slug`(如 `target/primary_domain`),同 key 覆盖更新;body 记端口/版本/凭据特征与证据来源。 - - **发现与利用上下文**(审计复现):`fact_key` 建议 `finding/`、`chain/`、`exploit/`、`poc/` 前缀;**body 必填**完整攻击链(入口 → 步骤 → 原始请求/响应或命令 → 现象 → 关联 `related_vulnerability_id`),**禁止仅写结论**;summary 写「什么 + 在哪 + 如何验证」一行要点。 - - **可交付漏洞**:使用 **`record_vulnerability`**(标题、描述、严重程度、类型、目标、证明 POC、影响、修复建议)。严重程度 critical / high / medium / low / info。 - - 同一发现可能需**各记一次**(事实记可复现攻击链,漏洞记正式 findings)。误报用 **`deprecate_project_fact`** 或漏洞状态 false_positive。 - - 事实多时用 **`list_project_facts`** / **`search_project_facts`** 检索。 +## 项目黑板(事实)与漏洞记录(分离) + +当前对话若已绑定项目,系统会自动注入「项目黑板索引」(仅 `fact_key` + 摘要)。**摘要不足时必须调用 `get_project_fact(fact_key)` 获取 body,禁止凭摘要臆造细节。** + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。委派/子任务返回新认知或漏洞时,由协调者及时写入,勿假定子代理已记。 + +- **环境/目标/认证等认知**(非正式漏洞):使用 **`upsert_project_fact`**,`fact_key` 建议 `category/slug`(如 `target/primary_domain`),同 key 覆盖更新;body 记端口/版本/凭据特征与证据来源。 +- **发现与利用上下文**(审计复现):`fact_key` 建议 `finding/`、`chain/`、`exploit/`、`poc/` 前缀;**body 必填**完整攻击链(入口 → 步骤 → 原始请求/响应或命令 → 现象 → 关联 `related_vulnerability_id`),**禁止仅写结论**;summary 写「什么 + 在哪 + 如何验证」一行要点。 +- **可交付漏洞**:使用 **`record_vulnerability`**(标题、描述、严重程度、类型、目标、证明 POC、影响、修复建议)。严重程度 critical / high / medium / low / info。 +- 同一发现可能需**各记一次**(事实记可复现攻击链,漏洞记正式 findings)。误报用 **`deprecate_project_fact`** 或漏洞状态 false_positive。 +- 事实多时用 **`list_project_facts`** / **`search_project_facts`** 检索。 + +### 事实写入规范(审计复现 / 知识沉淀) + +- **summary**:索引用一行,须含「什么 + 在哪 + 如何触发/验证」要点,禁止只写结论(如仅写「存在 SQLi」)。 +- **body**:完整可复现上下文,写入 `upsert_project_fact` 的 body 字段;索引不含 body,后续会话须靠 `get_project_fact` 取回。 +- **category / fact_key 建议**: + - 环境认知:`target/`、`auth/`、`infra/`、`business/`(body 用环境模板即可) + - 发现与利用:`finding/`、`chain/`、`exploit/`、`poc/`(**必须**用攻击链模板填满 body:入口、逐步攻击链、原始请求/响应或命令、证据、关联漏洞 ID) +- **与漏洞记录分工**:`record_vulnerability` 记可交付 findings;事实记**复现所需的全部上下文**(含失败尝试、绕过、依赖会话),二者可各记一次。 +- 更新同一发现时保持相同 `fact_key` 覆盖写入,勿散落多个 key 导致上下文丢失。 + +严重程度:critical / high / medium / low / info。证明须含足够证据(请求响应、截图、命令输出等)。 - **编排进度(待办)**:当你的任务包含 3 个或以上步骤,或你准备委派多个子目标并行/串行推进时,优先使用 `write_todos` 来向用户展示“当前在做什么/接下来做什么”。维护约束:同一时刻最多一个条目处于 `in_progress`;完成后立刻标记 `completed`;遇到阻塞就保留为 `in_progress` 并继续推进。 - **强触发建议(提升多 agent 使用率)**:如果你将要进行任何“证据收集/枚举/扫描/验证/复现/整理报告”这类实质执行动作,且不只是单步查询,请优先在第一个工具调用前就用 `write_todos` 建立计划;随后用 `task` 委派至少一个子代理获取结构化证据,而不是自己把全部步骤做完。 - **技能库(Skills)与知识库**:技能包位于服务器 `skills/` 目录(各子目录 `SKILL.md`,遵循 agentskills.io);知识库用于向量检索片段,Skills 为可执行工作流指令。多代理本会话通过内置 **`skill`** 工具渐进加载;子代理同样挂载 skill + 可选本机文件工具时,可在委派说明中提示按需加载。若当前无 skill 工具,需要完整 Skill 工作流时请使用多代理模式或切换为 Eino 编排会话。 diff --git a/agents/penetration.md b/agents/penetration.md index 59da9ff7..4cab37ac 100644 --- a/agents/penetration.md +++ b/agents/penetration.md @@ -31,5 +31,9 @@ max_iterations: 0 - 禁止自行猜测目标、替换为历史目标或擅自发起全量探索。 - 以证据为中心:请求/响应、Payload、命令输出、截图说明等,便于审计与复现。 -- 先确认边界与禁止项(如拒绝 DoS、数据破坏);发现有效漏洞时按协调者要求使用 `record_vulnerability` 等流程(若你的工具集中包含)。 +- 先确认边界与禁止项(如拒绝 DoS、数据破坏)。 - 输出包含:攻击路径摘要、关键步骤、影响评估、修复与缓解建议;语言简洁,便于主代理汇总。 + +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 diff --git a/agents/persistence-maintenance.md b/agents/persistence-maintenance.md index c3b7cd74..1c6e64b0 100644 --- a/agents/persistence-maintenance.md +++ b/agents/persistence-maintenance.md @@ -51,4 +51,8 @@ max_iterations: 0 - 列出需要清理/验证的痕迹类型(配置、会话、日志、服务变更等层级描述即可) 4) Recommended Next Steps(下一步建议) -- 建议由哪个阶段子代理接手,以及需要哪些证据输入。 +- 建议由哪个阶段子代理接手,以及需要哪些证据输入。 + +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 diff --git a/agents/privilege-escalation.md b/agents/privilege-escalation.md index 2ed0887e..2fe7d2f0 100644 --- a/agents/privilege-escalation.md +++ b/agents/privilege-escalation.md @@ -53,4 +53,8 @@ max_iterations: 0 4) Recommended Next Agent(下一步建议) - 明确建议由哪个子代理接手(例如 `lateral-movement` / `persistence-maintenance` / `impact-exfiltration` / `reporting-remediation`) -输出后直接结束。 +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 + +输出后直接结束。 diff --git a/agents/recon.md b/agents/recon.md index 97d8577a..f89b144e 100644 --- a/agents/recon.md +++ b/agents/recon.md @@ -34,3 +34,7 @@ max_iterations: 0 - 若 **`description` / 用户消息 / 上文交接包** 中已给出资产列表、枚举结论或明确写「跳过全量枚举 / 仅做增量 / 从端口扫描或验证开始」,则**不得**为走完整流程而重新执行等价的广域子域爆破或相同参数集的枚举;仅在交接包声明的**缺口**上补充侦察。 - 若子目标实为**漏洞验证、协议利用、权限提升**等而非攻击面扩展,应**极短说明**「当前角色为侦察;建议协调者改派专项代理」并仅提供与侦察相关的最小补充信息,避免擅自把任务扩写成新一轮全盘资产收集。 + +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 diff --git a/agents/reporting-remediation.md b/agents/reporting-remediation.md index 7eca7866..da86be60 100644 --- a/agents/reporting-remediation.md +++ b/agents/reporting-remediation.md @@ -55,4 +55,8 @@ max_iterations: 0 5) Appendix(附录) - 术语、假设、证据清单索引(按证据类型列出即可) -输出后直接结束。 +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 + +输出后直接结束。 diff --git a/agents/vulnerability-triage.md b/agents/vulnerability-triage.md index 27a6f985..a75ba01a 100644 --- a/agents/vulnerability-triage.md +++ b/agents/vulnerability-triage.md @@ -57,4 +57,8 @@ max_iterations: 0 4) Uncertainties & Missing Evidence(不确定性与缺口) - 列出最关键的缺口(尽量少,但要关键) -输出后直接结束。 +## 边渗透边记录 + +- **边渗透边记录(强制节奏)**:勿等会话结束或收尾再批量写入。每**确认**一条新认知(开放端口/服务版本、入口路径、认证态或凭据特征、可利用点或攻击面变化)后,**立即**调用 `upsert_project_fact`(同 fact_key 覆盖更新)。每**验证**出一条可复现漏洞(含 POC/影响)后,**立即**调用 `record_vulnerability`;与事实可各记一次。继续下一步工作前优先落库,避免上下文压缩后细节丢失。未绑项目时说明无法写黑板,仍在本轮保留证据摘要。若工具集中无上述工具,须在交付物末尾给出「待落库」结构化条目(fact_key 建议、summary、body/POC 要点),供协调者**立即**写入。 + +输出后直接结束。 diff --git a/web/static/css/style.css b/web/static/css/style.css index 91051e89..f22f7c3a 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -20996,6 +20996,7 @@ button.chat-files-dropdown-item:hover:not(:disabled) { } #page-projects .page-content.projects-page-layout { display: flex; + align-items: stretch; gap: 16px; min-height: calc(100vh - 128px); padding: 16px 20px 24px; @@ -21004,6 +21005,7 @@ button.chat-files-dropdown-item:hover:not(:disabled) { .projects-sidebar-card { width: 260px; flex-shrink: 0; + align-self: stretch; display: flex; flex-direction: column; background: #ffffff; @@ -21011,7 +21013,7 @@ button.chat-files-dropdown-item:hover:not(:disabled) { border-radius: 14px; box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06); overflow: hidden; - max-height: calc(100vh - 160px); + min-height: 420px; } .projects-sidebar-head { display: flex; @@ -21118,6 +21120,7 @@ button.chat-files-dropdown-item:hover:not(:disabled) { .projects-detail { flex: 1; min-width: 0; + align-self: stretch; display: flex; flex-direction: column; min-height: 420px; @@ -21173,6 +21176,7 @@ button.chat-files-dropdown-item:hover:not(:disabled) { box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06); overflow: hidden; min-height: 420px; + align-self: stretch; } .projects-detail-header { display: flex; @@ -21244,6 +21248,11 @@ button.chat-files-dropdown-item:hover:not(:disabled) { padding: 4px 10px; border-radius: 999px; } +.projects-stat-chip--warn { + color: #92400e; + background: #fef3c7; + border-color: #fcd34d; +} .projects-detail-header-actions { flex-shrink: 0; display: flex; @@ -21283,6 +21292,18 @@ button.chat-files-dropdown-item:hover:not(:disabled) { padding: 16px 24px 24px; overflow: auto; min-height: 0; + display: flex; + flex-direction: column; +} +.projects-panel--settings { + overflow: hidden; + padding: 0; + flex: 1 1 auto; + display: flex; + flex-direction: column; + align-items: stretch; + min-height: 0; + background: #fff; } /* display:flex 会覆盖 [hidden] 默认 display:none,非激活 Tab 会叠在事实黑板下方 */ .projects-panel[hidden] { @@ -21300,6 +21321,185 @@ button.chat-files-dropdown-item:hover:not(:disabled) { border: 1px solid #eef2f7; border-radius: 10px; } +/* —— 事实黑板:说明 + 筛选工具栏 —— */ +.projects-fact-toolbar { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 14px; + padding: 12px 14px; + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 12px; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); +} +.projects-fact-toolbar-hint { + display: flex; + align-items: flex-start; + gap: 8px; + margin: 0; + padding: 8px 10px; + font-size: 0.8125rem; + line-height: 1.5; + color: #475569; + background: #f0f7ff; + border: 1px solid #dbeafe; + border-radius: 8px; +} +.projects-fact-toolbar-hint-icon { + flex-shrink: 0; + margin-top: 2px; + color: #3b82f6; +} +.projects-fact-toolbar-hint strong { + font-weight: 600; + color: #334155; +} +.projects-fact-toolbar-hint code { + padding: 1px 5px; + font-size: 0.75rem; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + color: #1d4ed8; + background: rgba(255, 255, 255, 0.85); + border: 1px solid #bfdbfe; + border-radius: 4px; +} +.projects-fact-toolbar-filters { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + gap: 10px; +} +.projects-fact-filter-field { + display: flex; + flex-direction: column; + gap: 4px; + margin: 0; + min-width: 0; +} +.projects-fact-filter-field--search { + flex: 1 1 200px; + min-width: 160px; + max-width: 360px; + position: relative; +} +.projects-fact-filter-label { + font-size: 0.6875rem; + font-weight: 600; + letter-spacing: 0.02em; + text-transform: uppercase; + color: #94a3b8; + line-height: 1.2; +} +.projects-fact-filter-field input, +.projects-fact-filter-field select { + width: 100%; + min-width: 0; + height: 34px; + padding: 0 10px; + font-size: 0.8125rem; + color: #0f172a; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 8px; + transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease; +} +.projects-fact-filter-field--search input { + padding-left: 34px; + background: #fff; +} +.projects-fact-search-icon { + position: absolute; + left: 10px; + top: 9px; + color: #94a3b8; + pointer-events: none; +} +.projects-fact-filter-field input:focus, +.projects-fact-filter-field select:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12); + background: #fff; +} +.projects-fact-filter-field select { + min-width: 108px; + cursor: pointer; +} +.projects-fact-filter-toggles { + display: inline-flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + padding-bottom: 1px; +} +.projects-fact-toggle { + display: inline-flex; + align-items: center; + margin: 0; + cursor: pointer; + user-select: none; +} +.projects-fact-toggle input { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} +.projects-fact-toggle span { + display: inline-flex; + align-items: center; + height: 34px; + padding: 0 12px; + font-size: 0.8125rem; + font-weight: 500; + color: #64748b; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 8px; + transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease; +} +.projects-fact-toggle:hover span { + border-color: #cbd5e1; + color: #475569; +} +.projects-fact-toggle input:focus-visible + span { + outline: 2px solid #3b82f6; + outline-offset: 2px; +} +.projects-fact-toggle input:checked + span { + color: #1d4ed8; + background: #eff6ff; + border-color: #93c5fd; + box-shadow: 0 1px 2px rgba(37, 99, 235, 0.08); +} +@media (max-width: 720px) { + .projects-fact-filter-field--search { + flex: 1 1 100%; + max-width: none; + } + .projects-fact-filter-toggles { + width: 100%; + } +} +.projects-filter-check { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 0.8125rem; + color: #475569; + white-space: nowrap; + cursor: pointer; + user-select: none; +} +.projects-pin-toggle { + margin-top: 4px; +} .projects-panel-hint { font-size: 0.8125rem; color: #64748b; @@ -21374,33 +21574,28 @@ button.chat-files-dropdown-item:hover:not(:disabled) { color: #b91c1c; background: #fef2f2; } -.projects-panel--settings { - padding: 0; - background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%); - display: flex; - flex-direction: column; -} +/* —— 项目设置:左右分栏 + 底部危险区,无内层滚动 —— */ .projects-settings-layout { width: 100%; - max-width: none; + flex: 1 1 auto; display: flex; flex-direction: column; gap: 0; - flex: 1; min-height: 0; + overflow: auto; + background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%); } .projects-settings-intro { - padding: 18px 24px 14px; + flex-shrink: 0; + padding: 14px 20px 12px; border-bottom: 1px solid rgba(226, 232, 240, 0.8); background: rgba(255, 255, 255, 0.6); - backdrop-filter: blur(8px); } .projects-settings-intro-title { margin: 0; font-size: 0.9375rem; font-weight: 600; color: #0f172a; - letter-spacing: -0.01em; } .projects-settings-intro-hint { margin: 4px 0 0; @@ -21410,35 +21605,37 @@ button.chat-files-dropdown-item:hover:not(:disabled) { } .projects-settings-grid { display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); - gap: 18px; + grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); + grid-template-rows: minmax(min-content, 1fr); + gap: 14px; align-items: stretch; - padding: 20px 24px; + padding: 14px 20px; + flex: 1 1 auto; + min-height: min-content; } .projects-settings-card { display: flex; flex-direction: column; + height: 100%; min-height: 0; background: #ffffff; border: 1px solid #e2e8f0; border-radius: 14px; - box-shadow: 0 1px 3px rgba(15, 23, 42, 0.05), 0 4px 12px rgba(15, 23, 42, 0.03); + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.05); overflow: hidden; - transition: box-shadow 0.2s ease, border-color 0.2s ease; } -.projects-settings-card:hover { - border-color: #cbd5e1; - box-shadow: 0 2px 6px rgba(15, 23, 42, 0.06), 0 8px 20px rgba(15, 23, 42, 0.04); +.projects-settings-card--basic { + min-height: min-content; } .projects-settings-card--scope { - min-height: 300px; + min-height: 0; } .projects-settings-card-head { display: flex; align-items: flex-start; justify-content: space-between; - gap: 16px; - padding: 16px 20px; + gap: 12px; + padding: 12px 16px; border-bottom: 1px solid #f1f5f9; background: #fafbfc; } @@ -21476,16 +21673,14 @@ button.chat-files-dropdown-item:hover:not(:disabled) { margin: 3px 0 0; } .projects-settings-card-body { - padding: 18px 20px 20px; - flex: 1; - min-height: 0; + padding: 14px 16px 16px; } .projects-settings-card-body--fill { + flex: 1 1 auto; display: flex; flex-direction: column; - flex: 1; - min-height: 200px; - gap: 10px; + gap: 8px; + min-height: 0; } .projects-scope-toolbar { display: flex; @@ -21505,25 +21700,29 @@ button.chat-files-dropdown-item:hover:not(:disabled) { color: #0f172a; } .projects-scope-editor { - flex: 1; + flex: 1 1 auto; display: flex; flex-direction: column; - min-height: 0; + min-height: 120px; border-radius: 10px; overflow: hidden; border: 1px solid #1e293b; background: #0f172a; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); } -.projects-scope-textarea { - flex: 1; - min-height: 180px; +.projects-panel--settings .projects-scope-textarea { + flex: 1 1 auto; + display: block; + width: 100%; + box-sizing: border-box; + min-height: 132px; + max-height: none; resize: vertical; border: none !important; border-radius: 0 !important; background: #0f172a !important; color: #e2e8f0 !important; - padding: 14px 16px !important; + padding: 12px 14px !important; font-size: 0.8125rem !important; line-height: 1.6 !important; box-shadow: none !important; @@ -21561,32 +21760,36 @@ button.chat-files-dropdown-item:hover:not(:disabled) { cursor: pointer; } .projects-settings-card--danger { + flex-shrink: 0; flex-direction: row; - align-items: center; + align-items: flex-start; justify-content: space-between; gap: 20px; - margin: 0 24px 16px; - padding: 16px 20px; - background: linear-gradient(135deg, #fffbfb 0%, #fff 50%); + height: auto; + margin: 0 20px 16px; + padding: 18px 20px; + background: linear-gradient(135deg, #fffbfb 0%, #fff 55%); border-color: #fecaca; box-shadow: 0 1px 2px rgba(220, 38, 38, 0.06); } -.projects-settings-card--danger:hover { - border-color: #fca5a5; -} .projects-settings-danger-main { min-width: 0; flex: 1; display: flex; align-items: flex-start; - gap: 12px; + gap: 14px; +} +.projects-settings-card--danger .projects-settings-icon--red { + margin-top: 1px; } .projects-settings-card--danger .projects-settings-card-title { - margin: 0 0 4px; + margin: 0 0 6px; } .projects-settings-card--danger .projects-settings-card-hint { margin: 0; max-width: 560px; + line-height: 1.6; + color: #64748b; } .projects-settings-card-title { font-size: 0.9375rem; @@ -21602,22 +21805,28 @@ button.chat-files-dropdown-item:hover:not(:disabled) { .projects-settings-danger-actions { display: flex; flex-wrap: wrap; - gap: 8px; + align-items: center; + gap: 10px; flex-shrink: 0; + align-self: center; + padding-top: 2px; +} +.projects-settings-danger-actions .btn-small { + min-height: 34px; + padding: 7px 14px; } .projects-settings-footer { + flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 24px; - margin-top: auto; - border-top: 1px solid #e2e8f0; - background: rgba(255, 255, 255, 0.92); - backdrop-filter: blur(10px); - position: sticky; - bottom: 0; - z-index: 2; + margin: 0; + border-top: 1px solid #eef2f7; + background: #fff; + border-radius: 0 0 14px 14px; + box-shadow: 0 -1px 0 rgba(15, 23, 42, 0.04); } .projects-settings-footer-hint { font-size: 0.8125rem; @@ -21651,33 +21860,35 @@ button.chat-files-dropdown-item:hover:not(:disabled) { @media (max-width: 960px) { .projects-settings-grid { grid-template-columns: 1fr; - padding: 16px; + grid-template-rows: auto; + padding: 12px 16px; } - .projects-settings-card--scope { - min-height: 0; - } - .projects-form-row--2 { - grid-template-columns: 1fr; + .projects-settings-card { + height: auto; } .projects-settings-card--danger { flex-direction: column; align-items: stretch; - margin: 0 16px 12px; + margin: 0 16px 10px; + padding: 16px; } .projects-settings-danger-actions { justify-content: flex-end; } + .projects-form-row--2 { + grid-template-columns: 1fr; + } .projects-settings-footer { flex-direction: column; align-items: stretch; - padding: 14px 16px; + padding: 12px 16px; } .projects-settings-footer .btn-primary { justify-content: center; width: 100%; } .projects-settings-intro { - padding: 14px 16px 12px; + padding: 12px 16px 10px; } } .projects-form-field { @@ -21969,6 +22180,56 @@ button.chat-files-dropdown-item:hover:not(:disabled) { body.projects-modal-open { overflow: hidden; } +.fact-detail-prev-wrap { + margin-bottom: 16px; + padding-bottom: 14px; + border-bottom: 1px dashed #e2e8f0; +} +.fact-detail-prev-title, +.fact-detail-current-title { + margin: 0 0 8px; + font-size: 0.8125rem; + font-weight: 600; + color: #64748b; + text-transform: uppercase; + letter-spacing: 0.04em; +} +.fact-detail-body--muted { + opacity: 0.85; + max-height: 200px; +} +.projects-modal-footer--split { + justify-content: space-between; + flex-wrap: wrap; + gap: 10px; +} +.projects-modal-footer-left, +.projects-modal-footer-right { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; +} +.vulnerability-related-facts { + margin-top: 12px; + padding: 10px 12px; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 8px; + font-size: 0.8125rem; +} +.vulnerability-related-facts ul { + margin: 8px 0 0; + padding-left: 18px; +} +.vulnerability-related-facts li { + margin: 4px 0; +} +.vulnerability-related-facts a { + color: #2563eb; + cursor: pointer; + text-decoration: underline; +} .fact-detail-body { max-height: 50vh; overflow: auto; @@ -22051,6 +22312,12 @@ body.projects-modal-open { color: #64748b; background: #f1f5f9; } +.projects-fact-vuln-link { + display: block; + margin-top: 4px; + font-size: 0.6875rem; + color: #7c3aed; +} .vulnerability-filter-field--project select { min-width: 120px; max-width: 160px; diff --git a/web/static/js/chat.js b/web/static/js/chat.js index e495d111..ebf3184c 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -2912,6 +2912,9 @@ async function startNewConversation() { window.currentConversationId = ''; } catch (e) { /* ignore */ } currentConversationGroupId = null; // 新对话不属于任何分组 + if (typeof ensureDefaultActiveProjectForNewChat === 'function') { + ensureDefaultActiveProjectForNewChat().catch(() => {}); + } if (typeof refreshChatProjectSelector === 'function') { refreshChatProjectSelector(); } diff --git a/web/static/js/knowledge.js b/web/static/js/knowledge.js index 3457b4bb..341d5e9e 100644 --- a/web/static/js/knowledge.js +++ b/web/static/js/knowledge.js @@ -2068,69 +2068,78 @@ function showToastNotification(message, type = 'info') { const toast = document.createElement('div'); toast.className = `toast-notification toast-${type}`; - // 根据类型设置颜色 const typeStyles = { success: { - background: '#28a745', - color: '#fff', - icon: '✅' + background: '#f4fbf6', + border: '1px solid #cce8d4', + color: '#3d6654', + iconColor: '#52a06a', + icon: '' }, error: { - background: '#dc3545', - color: '#fff', - icon: '❌' + background: '#fef7f7', + border: '1px solid #f3d0d0', + color: '#8b4444', + iconColor: '#c96a6a', + icon: '' }, info: { - background: '#17a2b8', - color: '#fff', - icon: 'ℹ️' + background: '#f5f9ff', + border: '1px solid #cfe0f5', + color: '#4a6078', + iconColor: '#6b8fbf', + icon: '' }, warning: { - background: '#ffc107', - color: '#000', - icon: '⚠️' + background: '#fffbf3', + border: '1px solid #f0dfc0', + color: '#7a6535', + iconColor: '#c4a04a', + icon: '' } }; - + const style = typeStyles[type] || typeStyles.info; - + toast.style.cssText = ` background: ${style.background}; + border: ${style.border}; color: ${style.color}; - padding: 14px 20px; - border-radius: 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - min-width: 300px; - max-width: 500px; + padding: 10px 14px; + border-radius: 10px; + box-shadow: 0 2px 8px rgba(15, 23, 42, 0.06); + min-width: 220px; + max-width: 420px; pointer-events: auto; - animation: slideInRight 0.3s ease-out; + animation: slideInRight 0.25s ease-out; display: flex; align-items: center; - gap: 12px; - font-size: 0.9375rem; - line-height: 1.5; + gap: 10px; + font-size: 0.875rem; + line-height: 1.45; word-wrap: break-word; + backdrop-filter: blur(8px); `; - + toast.innerHTML = ` - ${style.icon} + ${style.icon} ${escapeHtml(message)} + " onmouseover="this.style.opacity='0.75'" onmouseout="this.style.opacity='0.45'">× `; container.appendChild(toast); @@ -2156,7 +2165,7 @@ if (!document.getElementById('toast-notification-styles')) { style.textContent = ` @keyframes slideInRight { from { - transform: translateX(100%); + transform: translateX(16px); opacity: 0; } to { @@ -2170,7 +2179,7 @@ if (!document.getElementById('toast-notification-styles')) { opacity: 1; } to { - transform: translateX(100%); + transform: translateX(16px); opacity: 0; } } diff --git a/web/static/js/projects.js b/web/static/js/projects.js index ce1c756f..b7e5e0ff 100644 --- a/web/static/js/projects.js +++ b/web/static/js/projects.js @@ -187,6 +187,25 @@ function prefetchProjectsForChat() { ensureProjectsLoaded().catch(() => {}); } +/** 新对话时:保留有效 activeProjectId,否则默认选中第一个进行中的项目 */ +async function ensureDefaultActiveProjectForNewChat() { + try { + await ensureProjectsLoaded(); + const cur = getActiveProjectId(); + if (cur && isActiveChatProjectId(cur)) return cur; + const first = + projectsCache.find((p) => p.pinned && p.status !== 'archived') || + projectsCache.find((p) => p.status !== 'archived'); + if (first) { + setActiveProjectId(first.id); + return first.id; + } + } catch (e) { + console.warn(e); + } + return ''; +} + function getProjectName(id) { return projectNameById[id] || id || ''; } @@ -357,15 +376,39 @@ function updateProjectStatusPill(status) { el.className = 'projects-status-pill ' + (archived ? 'projects-status-pill--archived' : 'projects-status-pill--active'); } -function updateProjectStats(factCount, vulnCount) { +function updateProjectStats(stats) { + const s = stats || {}; const f = document.getElementById('project-stat-facts'); const v = document.getElementById('project-stat-vulns'); - if (f) f.textContent = `${factCount ?? 0} 条事实`; - if (v) v.textContent = `${vulnCount ?? 0} 个漏洞`; + const c = document.getElementById('project-stat-conversations'); + const sparse = document.getElementById('project-stat-sparse'); + const fc = s.fact_count ?? s.factCount ?? 0; + const vc = s.vuln_count ?? s.vulnCount ?? 0; + const cc = s.conversation_count ?? s.conversationCount ?? 0; + const sc = s.sparse_fact_count ?? s.sparseFactCount ?? 0; + if (f) f.textContent = `${fc} 条事实`; + if (v) v.textContent = `${vc} 个漏洞`; + if (c) c.textContent = `${cc} 个对话`; + if (sparse) { + if (sc > 0) { + sparse.hidden = false; + sparse.textContent = `${sc} 待补全`; + } else { + sparse.hidden = true; + } + } } async function selectProject(id) { currentProjectId = id; + const searchEl = document.getElementById('project-facts-search'); + const catEl = document.getElementById('project-facts-filter-category'); + const confEl = document.getElementById('project-facts-filter-confidence'); + const sparseEl = document.getElementById('project-facts-filter-sparse'); + if (searchEl) searchEl.value = ''; + if (catEl) catEl.value = ''; + if (confEl) confEl.value = ''; + if (sparseEl) sparseEl.checked = false; renderProjectsSidebar(); updateProjectsDetailVisibility(); try { @@ -379,6 +422,8 @@ async function selectProject(id) { document.getElementById('project-edit-scope').value = p.scope_json || ''; const statusEl = document.getElementById('project-edit-status'); if (statusEl) statusEl.value = p.status || 'active'; + const pinEl = document.getElementById('project-edit-pinned'); + if (pinEl) pinEl.checked = !!p.pinned; updateProjectStatusPill(p.status || 'active'); const metaEl = document.getElementById('projects-detail-meta'); if (metaEl) metaEl.textContent = `更新于 ${formatProjectTime(p.updated_at)}`; @@ -397,42 +442,78 @@ async function selectProject(id) { } catch (e) { console.warn(e); } - refreshProjectHeaderStats(); + await refreshProjectHeaderStats(); switchProjectTab(currentProjectTab); } function switchProjectTab(tab) { currentProjectTab = tab; - ['facts', 'vulns', 'settings'].forEach((t) => { + ['facts', 'conversations', 'vulns', 'settings'].forEach((t) => { const btn = document.getElementById(`project-tab-${t}`); const panel = document.getElementById(`project-panel-${t}`); if (btn) btn.classList.toggle('is-active', t === tab); if (panel) panel.hidden = t !== tab; }); if (tab === 'facts') loadProjectFacts(); + if (tab === 'conversations') loadProjectConversations(); if (tab === 'vulns') loadProjectVulnerabilities(); } +function buildProjectFactsQueryParams() { + const params = new URLSearchParams(); + params.set('limit', '200'); + const search = document.getElementById('project-facts-search')?.value?.trim(); + const category = document.getElementById('project-facts-filter-category')?.value?.trim(); + const confidence = document.getElementById('project-facts-filter-confidence')?.value?.trim(); + const sparseOnly = document.getElementById('project-facts-filter-sparse')?.checked; + const hideDeprecated = document.getElementById('project-facts-filter-hide-deprecated')?.checked; + if (search) params.set('search', search); + if (category) params.set('category', category); + if (confidence) params.set('confidence', confidence); + if (sparseOnly) params.set('sparse_only', 'true'); + if (hideDeprecated) params.set('exclude_deprecated', 'true'); + return params; +} + +function debouncedLoadProjectFacts() { + if (_projectFactsFilterDebounce) clearTimeout(_projectFactsFilterDebounce); + _projectFactsFilterDebounce = setTimeout(() => { + _projectFactsFilterDebounce = null; + loadProjectFacts(); + }, 280); +} + async function loadProjectFacts() { const tbody = document.getElementById('project-facts-tbody'); if (!tbody || !currentProjectId) return; tbody.innerHTML = '加载中…'; - const res = await apiFetch(`/api/projects/${currentProjectId}/facts?limit=200`); + const qs = buildProjectFactsQueryParams().toString(); + const res = await apiFetch(`/api/projects/${currentProjectId}/facts?${qs}`); if (!res.ok) { tbody.innerHTML = '加载失败'; return; } const facts = await res.json(); if (!facts.length) { - tbody.innerHTML = '暂无事实,点击「添加事实」或由 Agent 自动写入'; + const hasFilter = + document.getElementById('project-facts-search')?.value?.trim() || + document.getElementById('project-facts-filter-category')?.value || + document.getElementById('project-facts-filter-confidence')?.value || + document.getElementById('project-facts-filter-sparse')?.checked; + tbody.innerHTML = `${ + hasFilter ? '无匹配事实,请调整筛选条件' : '暂无事实,点击「添加事实」或由 Agent 自动写入' + }`; refreshProjectHeaderStats(); return; } tbody.innerHTML = facts.map((f) => { const keyEsc = escapeHtml(f.fact_key); const idEsc = escapeHtml(f.id); + const vulnLink = f.related_vulnerability_id + ? `${escapeHtml(f.related_vulnerability_id.slice(0, 8))}…` + : ''; return ` - ${keyEsc} + ${keyEsc}${vulnLink} ${formatCategoryBadge(f.category)} ${escapeHtml(f.summary)} ${formatFactBodyBadge(f)} @@ -447,34 +528,85 @@ async function loadProjectFacts() { async function refreshProjectHeaderStats() { if (!currentProjectId) return; try { - const [factsRes, vulnRes] = await Promise.all([ - apiFetch(`/api/projects/${currentProjectId}/facts?limit=500`), - apiFetch(`/api/vulnerabilities?project_id=${encodeURIComponent(currentProjectId)}&limit=100`), - ]); - let fc = 0; - let vc = 0; - if (factsRes.ok) { - const f = await factsRes.json(); - fc = Array.isArray(f) ? f.length : 0; - } - if (vulnRes.ok) { - const d = await vulnRes.json(); - const items = d.Vulnerabilities || d.vulnerabilities || d.items || []; - vc = items.length; - } - updateProjectStats(fc, vc); + const res = await apiFetch(`/api/projects/${currentProjectId}/stats`); + if (!res.ok) return; + const stats = await res.json(); + updateProjectStats(stats); } catch (e) { console.warn(e); } } +async function loadProjectConversations() { + const tbody = document.getElementById('project-conversations-tbody'); + if (!tbody || !currentProjectId) return; + tbody.innerHTML = '加载中…'; + const res = await apiFetch(`/api/projects/${currentProjectId}/conversations?limit=100`); + if (!res.ok) { + tbody.innerHTML = '加载失败'; + return; + } + const data = await res.json(); + const items = data.conversations || []; + if (!items.length) { + tbody.innerHTML = + '暂无绑定对话;在对话页选择本项目即可关联'; + return; + } + tbody.innerHTML = items + .map((conv) => { + const id = conv.id; + const idEsc = escapeHtml(id); + const title = escapeHtml(conv.title || '未命名对话'); + const updated = formatProjectTime(conv.updatedAt || conv.updated_at, conv.createdAt || conv.created_at); + return ` + ${title} + ${escapeHtml(updated)} + +
+ + +
+ + `; + }) + .join(''); +} + +function openProjectConversation(conversationId) { + if (!conversationId) return; + if (typeof switchPage === 'function') { + switchPage('chat'); + } + setTimeout(() => { + if (typeof loadConversation === 'function') { + loadConversation(conversationId); + } + }, 200); +} + +async function unbindConversationFromProject(conversationId) { + if (!conversationId || !confirm('解除该对话与当前项目的绑定?')) return; + const res = await apiFetch(`/api/conversations/${encodeURIComponent(conversationId)}/project`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ projectId: '' }), + }); + if (!res.ok) return alert('解绑失败'); + loadProjectConversations(); + refreshProjectHeaderStats(); +} + let _factDetailKey = null; +let _factDetailFact = null; +let _projectFactsFilterDebounce = null; async function viewProjectFactBody(factKey) { const res = await apiFetch(`/api/projects/${currentProjectId}/facts?fact_key=${encodeURIComponent(factKey)}`); if (!res.ok) return alert('加载失败'); const f = await res.json(); _factDetailKey = f.fact_key; + _factDetailFact = f; document.getElementById('fact-detail-title').textContent = `[${f.fact_key}]`; const metaParts = [ `分类: ${f.category}`, @@ -483,6 +615,7 @@ async function viewProjectFactBody(factKey) { ]; if (f.related_vulnerability_id) metaParts.push(`关联漏洞: ${f.related_vulnerability_id}`); if (f.source_conversation_id) metaParts.push(`来源对话: ${f.source_conversation_id}`); + if (f.supersedes_fact_id) metaParts.push('含上一版本'); document.getElementById('fact-detail-meta').textContent = metaParts.join(' · '); document.getElementById('fact-detail-body').textContent = f.body || '(无 body)'; const warnEl = document.getElementById('fact-detail-sparse-warn'); @@ -496,6 +629,30 @@ async function viewProjectFactBody(factKey) { warnEl.textContent = ''; } } + const prevWrap = document.getElementById('fact-detail-prev-wrap'); + if (prevWrap) { + prevWrap.hidden = true; + if (f.id && f.supersedes_fact_id) { + try { + const prevRes = await apiFetch( + `/api/projects/${currentProjectId}/facts/${encodeURIComponent(f.id)}/previous-version`, + ); + if (prevRes.ok) { + const prev = await prevRes.json(); + prevWrap.hidden = false; + document.getElementById('fact-detail-prev-meta').textContent = + `归档于 ${formatProjectTime(prev.archived_at)} · 摘要: ${prev.summary || '—'} · 置信度: ${prev.confidence || '—'}`; + document.getElementById('fact-detail-prev-body').textContent = prev.body || '(无 body)'; + } + } catch (e) { + console.warn(e); + } + } + } + const linkBtn = document.getElementById('fact-detail-link-vuln-btn'); + const createBtn = document.getElementById('fact-detail-create-vuln-btn'); + if (linkBtn) linkBtn.hidden = false; + if (createBtn) createBtn.hidden = false; openProjectsOverlay('fact-detail-modal'); } @@ -508,6 +665,99 @@ function editFactFromDetail() { function closeFactDetailModal() { closeProjectsOverlay('fact-detail-modal'); _factDetailKey = null; + _factDetailFact = null; +} + +async function linkFactToExistingVulnerability() { + const f = _factDetailFact; + if (!f || !currentProjectId) return; + const res = await apiFetch(`/api/vulnerabilities?project_id=${encodeURIComponent(currentProjectId)}&limit=50`); + if (!res.ok) return alert('加载漏洞列表失败'); + const data = await res.json(); + const items = data.Vulnerabilities || data.vulnerabilities || data.items || []; + if (!items.length) return alert('本项目暂无漏洞,请先创建或让 Agent 记录漏洞'); + const lines = items.map((v, i) => `${i + 1}. [${v.severity}] ${v.title} (${v.id})`); + const pick = prompt(`输入序号以关联事实「${f.fact_key}」:\n\n${lines.join('\n')}`); + if (pick == null || pick === '') return; + const idx = parseInt(pick, 10) - 1; + if (Number.isNaN(idx) || idx < 0 || idx >= items.length) return alert('序号无效'); + const vulnId = items[idx].id; + const upd = await apiFetch(`/api/projects/${currentProjectId}/facts/${encodeURIComponent(f.id)}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + fact_key: f.fact_key, + category: f.category, + summary: f.summary, + body: f.body || '', + confidence: f.confidence, + related_vulnerability_id: vulnId, + }), + }); + if (!upd.ok) return alert('关联失败'); + alert('已关联漏洞'); + closeFactDetailModal(); + loadProjectFacts(); +} + +async function createVulnerabilityFromCurrentFact() { + const f = _factDetailFact; + if (!f || !currentProjectId) return; + let convId = + (f.source_conversation_id || '').trim() || + (typeof window.currentConversationId === 'string' ? window.currentConversationId.trim() : ''); + if (!convId) { + convId = prompt('创建漏洞需要对话 ID(可与来源会话一致):', '')?.trim() || ''; + } + if (!convId) return alert('已取消:未提供 conversation_id'); + const severity = inferSeverityFromFact(f); + const body = { + conversation_id: convId, + project_id: currentProjectId, + title: (f.summary || f.fact_key).slice(0, 200), + description: `由项目事实 ${f.fact_key} 生成`, + severity, + status: 'open', + type: f.category || 'finding', + target: '', + proof: f.body || '', + impact: '', + recommendation: '', + }; + const res = await apiFetch('/api/vulnerabilities', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + if (!res.ok) { + const err = await res.json().catch(() => ({})); + return alert(err.error || '创建漏洞失败'); + } + const vuln = await res.json(); + await apiFetch(`/api/projects/${currentProjectId}/facts/${encodeURIComponent(f.id)}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + fact_key: f.fact_key, + category: f.category, + summary: f.summary, + body: f.body || '', + confidence: f.confidence, + related_vulnerability_id: vuln.id, + }), + }); + alert(`已创建漏洞并关联:${vuln.title || vuln.id}`); + closeFactDetailModal(); + loadProjectFacts(); + if (currentProjectTab === 'vulns') loadProjectVulnerabilities(); +} + +function inferSeverityFromFact(f) { + const c = (f.category || '').toLowerCase(); + const key = (f.fact_key || '').toLowerCase(); + if (c === 'exploit' || c === 'poc' || key.includes('rce') || key.includes('sqli')) return 'high'; + if (c === 'finding' || c === 'chain') return 'medium'; + return 'medium'; } async function deprecateProjectFactByKey(factKey) { @@ -573,6 +823,7 @@ async function loadProjectVulnerabilities() {
+
`; @@ -587,6 +838,44 @@ function openVulnerabilityDetail(vulnId) { } } +async function viewFactsForVulnerability(vulnId) { + if (!currentProjectId) return; + switchProjectTab('facts'); + const searchEl = document.getElementById('project-facts-search'); + const catEl = document.getElementById('project-facts-filter-category'); + const confEl = document.getElementById('project-facts-filter-confidence'); + const sparseEl = document.getElementById('project-facts-filter-sparse'); + const hideDepEl = document.getElementById('project-facts-filter-hide-deprecated'); + if (searchEl) searchEl.value = ''; + if (catEl) catEl.value = ''; + if (confEl) confEl.value = ''; + if (sparseEl) sparseEl.checked = false; + if (hideDepEl) hideDepEl.checked = true; + const params = new URLSearchParams({ limit: '50', related_vulnerability_id: vulnId }); + const res = await apiFetch(`/api/projects/${currentProjectId}/facts?${params}`); + if (!res.ok) return alert('加载关联事实失败'); + const facts = await res.json(); + if (!facts.length) { + alert('该漏洞暂无关联事实,可在事实详情中「关联漏洞」或「生成漏洞草稿」建立链接'); + loadProjectFacts(); + return; + } + if (facts.length === 1) { + viewProjectFactBody(facts[0].fact_key); + return; + } + const pick = prompt( + `该漏洞关联 ${facts.length} 条事实,输入序号查看:\n${facts.map((f, i) => `${i + 1}. ${f.fact_key}`).join('\n')}`, + ); + if (pick == null || pick === '') { + loadProjectFacts(); + return; + } + const idx = parseInt(pick, 10) - 1; + if (facts[idx]) viewProjectFactBody(facts[idx].fact_key); + else loadProjectFacts(); +} + function openProjectsOverlay(id) { const el = document.getElementById(id); if (!el) return; @@ -683,6 +972,7 @@ async function saveProjectSettings() { description: document.getElementById('project-edit-description').value.trim(), scope_json: scopeRaw, status: document.getElementById('project-edit-status')?.value || 'active', + pinned: !!document.getElementById('project-edit-pinned')?.checked, }; const res = await apiFetch(`/api/projects/${currentProjectId}`, { method: 'PUT', @@ -1143,6 +1433,7 @@ window.toggleChatProjectPanel = toggleChatProjectPanel; window.closeChatProjectPanel = closeChatProjectPanel; window.selectChatProject = selectChatProject; window.prefetchProjectsForChat = prefetchProjectsForChat; +window.ensureDefaultActiveProjectForNewChat = ensureDefaultActiveProjectForNewChat; window.getActiveProjectId = getActiveProjectId; window.getProjectName = getProjectName; window.viewProjectFactBody = viewProjectFactBody; @@ -1153,5 +1444,12 @@ window.restoreProjectFactByKey = restoreProjectFactByKey; window.openVulnerabilitiesForProject = openVulnerabilitiesForProject; window.openVulnerabilityDetail = openVulnerabilityDetail; window.filterProjectsList = filterProjectsList; +window.debouncedLoadProjectFacts = debouncedLoadProjectFacts; +window.linkFactToExistingVulnerability = linkFactToExistingVulnerability; +window.createVulnerabilityFromCurrentFact = createVulnerabilityFromCurrentFact; +window.viewFactsForVulnerability = viewFactsForVulnerability; +window.openProjectConversation = openProjectConversation; +window.unbindConversationFromProject = unbindConversationFromProject; +window.loadProjectConversations = loadProjectConversations; window.rebuildProjectNameMap = rebuildProjectNameMap; window.projectNameById = projectNameById; diff --git a/web/static/js/vulnerability.js b/web/static/js/vulnerability.js index 28b4a371..7b911986 100644 --- a/web/static/js/vulnerability.js +++ b/web/static/js/vulnerability.js @@ -932,6 +932,7 @@ function renderVulnerabilities(vulnerabilities) { ${vuln.proof ? `
${escapeHtml(vulnT('vulnerabilityPage.detailProof'))}:
${escapeHtml(vuln.proof)}
` : ''} ${vuln.impact ? `
${escapeHtml(vulnT('vulnerabilityPage.detailImpact'))}: ${escapeHtml(vuln.impact)}
` : ''} ${vuln.recommendation ? `
${escapeHtml(vulnT('vulnerabilityPage.detailRecommendation'))}: ${escapeHtml(vuln.recommendation)}
` : ''} + `; @@ -1295,12 +1296,76 @@ function toggleVulnerabilityDetails(id) { if (content.style.display === 'none') { content.style.display = 'block'; icon.style.transform = 'rotate(90deg)'; + loadVulnerabilityRelatedFacts(id).catch((e) => console.warn(e)); } else { content.style.display = 'none'; icon.style.transform = 'rotate(0deg)'; } } +/** 展开漏洞详情时加载关联项目事实 */ +async function loadVulnerabilityRelatedFacts(vulnId) { + const el = document.getElementById(`vuln-related-facts-${vulnId}`); + if (!el || el.dataset.loaded === '1') return; + const projectId = (el.dataset.projectId || '').trim(); + if (!projectId) { + el.hidden = true; + el.dataset.loaded = '1'; + return; + } + el.hidden = false; + el.innerHTML = '加载关联事实…'; + try { + const res = await apiFetch( + `/api/projects/${encodeURIComponent(projectId)}/facts?related_vulnerability_id=${encodeURIComponent(vulnId)}&limit=20&exclude_deprecated=true`, + ); + if (!res.ok) throw new Error('fetch failed'); + const facts = await res.json(); + if (!Array.isArray(facts) || !facts.length) { + el.innerHTML = + '关联事实

暂无;可在「项目管理」事实详情中关联或生成漏洞草稿。

'; + el.dataset.loaded = '1'; + return; + } + const items = facts + .map((f) => { + const key = escapeHtml(f.fact_key); + const sum = escapeHtml((f.summary || '').slice(0, 120)); + const pid = escapeHtml(projectId); + const rawKey = escapeHtml(f.fact_key); + return `
  • ${key} — ${sum}
  • `; + }) + .join(''); + el.innerHTML = `关联事实(${facts.length})`; + el.dataset.loaded = '1'; + } catch (e) { + el.innerHTML = '关联事实

    加载失败

    '; + } +} + +function openProjectFactFromVulnerability(projectId, factKey) { + if (!projectId || !factKey) return; + if (typeof switchPage === 'function') { + switchPage('projects'); + } + setTimeout(async () => { + if (typeof window.initProjectsPage === 'function') { + await window.initProjectsPage(); + } + if (typeof window.selectProject === 'function') { + await window.selectProject(projectId); + } + if (typeof window.switchProjectTab === 'function') { + window.switchProjectTab('facts'); + } + if (typeof window.viewProjectFactBody === 'function') { + window.viewProjectFactBody(factKey); + } + }, 350); +} + +window.openProjectFactFromVulnerability = openProjectFactFromVulnerability; + // HTML转义 function escapeHtml(text) { const div = document.createElement('div'); diff --git a/web/templates/index.html b/web/templates/index.html index f2b30484..e40bda7d 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -1453,6 +1453,8 @@
    0 条事实 0 个漏洞 + 0 个对话 +
    @@ -1462,13 +1464,63 @@
    -
    - 索引仅含 key + 摘要(须含「什么+在哪+如何验证」);攻击链/POC 写在 body,Agent 通过 get_project_fact 复现 - +
    +

    + + 索引仅含 key摘要(须含「什么 + 在哪 + 如何验证」);攻击链 / POC 写在 body,Agent 通过 get_project_fact 复现 +

    +
    @@ -1477,6 +1529,17 @@
    +
    +
    + +
    - +
    @@ -1572,14 +1640,14 @@ - + @@ -4046,11 +4114,23 @@
    + +

    当前版本

    
                 
    -