diff --git a/internal/app/vulnerability_tools.go b/internal/app/vulnerability_tools.go index 781a9159..6fdb917f 100644 --- a/internal/app/vulnerability_tools.go +++ b/internal/app/vulnerability_tools.go @@ -120,9 +120,19 @@ func formatVulnerabilityDetail(v *database.Vulnerability) string { b.WriteString(v.Description) b.WriteString("\n") } - if v.Proof != "" { - b.WriteString("\n--- 证明(POC) ---\n") - b.WriteString(v.Proof) + if v.Preconditions != "" { + b.WriteString("\n--- 前置条件 ---\n") + b.WriteString(v.Preconditions) + b.WriteString("\n") + } + if v.ReproSteps != "" { + b.WriteString("\n--- 复现步骤 ---\n") + b.WriteString(v.ReproSteps) + b.WriteString("\n") + } + if v.Evidence != "" { + b.WriteString("\n--- 证据 / POC ---\n") + b.WriteString(v.Evidence) b.WriteString("\n") } if v.Impact != "" { @@ -135,9 +145,36 @@ func formatVulnerabilityDetail(v *database.Vulnerability) string { b.WriteString(v.Recommendation) b.WriteString("\n") } + if v.RetestNotes != "" { + b.WriteString("\n--- 复测方式 ---\n") + b.WriteString(v.RetestNotes) + b.WriteString("\n") + } return b.String() } +func missingVulnerabilityReproFields(args map[string]interface{}) []string { + required := []struct { + key string + label string + }{ + {"target", "target(受影响的 URL/IP/服务/接口)"}, + {"vulnerability_type", "vulnerability_type(漏洞类型)"}, + {"description", "description(漏洞摘要与触发点)"}, + {"reproduction_steps", "reproduction_steps(可逐步执行的复现步骤)"}, + {"evidence", "evidence(POC、原始请求/响应、命令输出或截图/日志证据)"}, + {"impact", "impact(确认后的实际影响)"}, + {"recommendation", "recommendation(修复建议)"}, + } + missing := make([]string, 0) + for _, item := range required { + if strings.TrimSpace(strArg(args, item.key)) == "" { + missing = append(missing, item.label) + } + } + return missing +} + func truncateRunes(s string, max int) string { r := []rune(s) if len(r) <= max { @@ -163,18 +200,18 @@ func registerVulnerabilityTools(mcpServer *mcp.Server, db *database.DB, logger * func registerRecordVulnerabilityTool(mcpServer *mcp.Server, db *database.DB, logger *zap.Logger) { tool := mcp.Tool{ Name: builtin.ToolRecordVulnerability, - Description: "记录发现的漏洞详情到漏洞管理系统。边渗透边记录:每验证出一条可复现漏洞(含 POC/影响)后立即调用,勿等会话结束。包括标题、描述、严重程度、类型、目标、证明、影响和建议等。记录前可先 list_vulnerabilities 避免重复。", - ShortDescription: "记录发现的漏洞详情到漏洞管理系统", + Description: "记录发现的漏洞详情到漏洞管理系统。必须按“仅看本记录即可复现”的标准填写:目标、触发点、前置条件、复现步骤、证据/POC、实际影响、修复建议和复测方式。边渗透边记录:每验证出一条可复现漏洞后立即调用,勿等会话结束。记录前可先 list_vulnerabilities 避免重复。", + ShortDescription: "记录可复现的漏洞详情到漏洞管理系统", InputSchema: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "title": map[string]interface{}{ "type": "string", - "description": "漏洞标题(必需)", + "description": "漏洞标题(必需)。建议格式:<资产/接口> 存在 <漏洞类型>,例如“/api/login 存在 SQL 注入”。", }, "description": map[string]interface{}{ "type": "string", - "description": "漏洞详细描述", + "description": "漏洞摘要与触发点(必需):说明哪个功能/参数/入口存在问题、为什么可被利用。不要只写结论。", }, "severity": map[string]interface{}{ "type": "string", @@ -183,26 +220,38 @@ func registerRecordVulnerabilityTool(mcpServer *mcp.Server, db *database.DB, log }, "vulnerability_type": map[string]interface{}{ "type": "string", - "description": "漏洞类型,如:SQL注入、XSS、CSRF、命令注入等", + "description": "漏洞类型,如:SQL注入、XSS、CSRF、命令注入等(必需)", }, "target": map[string]interface{}{ "type": "string", - "description": "受影响的目标(URL、IP地址、服务等)", + "description": "受影响的目标(必需):尽量精确到 URL、IP:端口、服务名、接口路径和参数名。", }, - "proof": map[string]interface{}{ + "preconditions": map[string]interface{}{ "type": "string", - "description": "漏洞证明(POC、截图、请求/响应等)", + "description": "前置条件:登录状态、权限、账号、Header/Cookie、特定数据、网络位置、环境/版本等;无前置条件写“无”。", + }, + "reproduction_steps": map[string]interface{}{ + "type": "string", + "description": "复现步骤(必需):按 1/2/3 编号,写清入口、参数、payload、执行命令、观察点。应让未参与对话的人照做即可复现。", + }, + "evidence": map[string]interface{}{ + "type": "string", + "description": "证据 / POC(必需):原始 HTTP 请求/响应、curl/工具命令、截图文字说明、日志、DNSLog/回连记录、数据库结果、文件路径、时间戳等。优先放最小可验证证据。", }, "impact": map[string]interface{}{ "type": "string", - "description": "漏洞影响说明", + "description": "漏洞影响说明(必需):结合已验证事实说明可造成什么后果,避免泛泛而谈。", }, "recommendation": map[string]interface{}{ "type": "string", - "description": "修复建议", + "description": "修复建议(必需):给出针对该触发点/参数/组件的具体修复和复测建议。", + }, + "retest_notes": map[string]interface{}{ + "type": "string", + "description": "复测方式:修复后如何验证漏洞已关闭,包括应返回的状态码、错误信息或访问控制结果。", }, }, - "required": []string{"title", "severity"}, + "required": []string{"title", "description", "severity", "vulnerability_type", "target", "reproduction_steps", "evidence", "impact", "recommendation"}, }, } @@ -231,6 +280,9 @@ func registerRecordVulnerabilityTool(mcpServer *mcp.Server, db *database.DB, log if !validSeverities[severity] { return textResult(fmt.Sprintf("错误: severity 必须是 critical、high、medium、low 或 info 之一,当前值: %s", severity), true), nil } + if missing := missingVulnerabilityReproFields(args); len(missing) > 0 { + return textResult("错误: 漏洞记录缺少复现所需信息,请补充后再记录:\n- "+strings.Join(missing, "\n- ")+"\n\n最佳实践:漏洞管理中的单条记录应独立包含目标、前置条件、复现步骤、证据/POC、影响和修复/复测方式。", true), nil + } projectID := "" if pid, perr := db.GetConversationProjectID(conversationID); perr == nil { @@ -246,9 +298,12 @@ func registerRecordVulnerabilityTool(mcpServer *mcp.Server, db *database.DB, log Status: "open", Type: strArg(args, "vulnerability_type"), Target: strArg(args, "target"), - Proof: strArg(args, "proof"), + Preconditions: strArg(args, "preconditions"), + ReproSteps: strArg(args, "reproduction_steps"), + Evidence: strArg(args, "evidence"), Impact: strArg(args, "impact"), Recommendation: strArg(args, "recommendation"), + RetestNotes: strArg(args, "retest_notes"), } created, err := db.CreateVulnerability(vuln) @@ -275,8 +330,8 @@ func registerRecordVulnerabilityTool(mcpServer *mcp.Server, db *database.DB, log func registerListVulnerabilitiesTool(mcpServer *mcp.Server, db *database.DB, logger *zap.Logger) { tool := mcp.Tool{ - Name: builtin.ToolListVulnerabilities, - Description: "列出当前授权范围内的漏洞(摘要)。默认:对话已绑定项目时列出该项目下全部漏洞;未绑项目时仅列出当前会话漏洞。可用 scope=conversation 仅看本会话。记录新漏洞前建议先调用以避免重复。", + Name: builtin.ToolListVulnerabilities, + Description: "列出当前授权范围内的漏洞(摘要)。默认:对话已绑定项目时列出该项目下全部漏洞;未绑项目时仅列出当前会话漏洞。可用 scope=conversation 仅看本会话。记录新漏洞前建议先调用以避免重复。", ShortDescription: "列出漏洞(默认当前项目)", InputSchema: map[string]interface{}{ "type": "object",