diff --git a/internal/handler/openapi.go b/internal/handler/openapi.go index d25d06ae..ba8f4eca 100644 --- a/internal/handler/openapi.go +++ b/internal/handler/openapi.go @@ -506,7 +506,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) { }, "CreateVulnerabilityRequest": map[string]interface{}{ "type": "object", - "required": []string{"conversation_id", "title", "severity"}, + "required": []string{"conversation_id", "title", "description", "severity", "type", "target", "reproduction_steps", "evidence", "impact", "recommendation"}, "properties": map[string]interface{}{ "conversation_id": map[string]interface{}{ "type": "string", @@ -538,10 +538,9 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) { "type": "string", "description": "受影响的目标", }, - "proof": map[string]interface{}{ - "type": "string", - "description": "漏洞证明", - }, + "preconditions": map[string]interface{}{"type": "string", "description": "前置条件"}, + "reproduction_steps": map[string]interface{}{"type": "string", "description": "复现步骤"}, + "evidence": map[string]interface{}{"type": "string", "description": "证据/POC,包含请求响应、命令输出、截图说明、日志等"}, "impact": map[string]interface{}{ "type": "string", "description": "影响", @@ -550,6 +549,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) { "type": "string", "description": "修复建议", }, + "retest_notes": map[string]interface{}{"type": "string", "description": "复测方式"}, }, }, "UpdateVulnerabilityRequest": map[string]interface{}{ @@ -581,10 +581,9 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) { "type": "string", "description": "受影响的目标", }, - "proof": map[string]interface{}{ - "type": "string", - "description": "漏洞证明", - }, + "preconditions": map[string]interface{}{"type": "string", "description": "前置条件"}, + "reproduction_steps": map[string]interface{}{"type": "string", "description": "复现步骤"}, + "evidence": map[string]interface{}{"type": "string", "description": "证据/POC,包含请求响应、命令输出、截图说明、日志等"}, "impact": map[string]interface{}{ "type": "string", "description": "影响", @@ -593,6 +592,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) { "type": "string", "description": "修复建议", }, + "retest_notes": map[string]interface{}{"type": "string", "description": "复测方式"}, }, }, "ListVulnerabilitiesResponse": map[string]interface{}{ @@ -805,18 +805,18 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) { "type": "object", "description": "视觉分析(analyze_image MCP 工具);enabled 且 model 非空时注册工具", "properties": map[string]interface{}{ - "enabled": map[string]interface{}{"type": "boolean", "description": "是否启用 analyze_image"}, - "model": map[string]interface{}{"type": "string", "description": "视觉模型名(必填)", "example": "qwen-vl-max"}, - "api_key": map[string]interface{}{"type": "string", "description": "API Key;留空复用 openai.api_key"}, - "base_url": map[string]interface{}{"type": "string", "description": "Base URL;留空复用 openai.base_url"}, - "provider": map[string]interface{}{"type": "string", "description": "提供商;留空复用 openai.provider"}, - "timeout_seconds": map[string]interface{}{"type": "integer", "description": "VL 调用超时(秒)"}, - "max_image_bytes": map[string]interface{}{"type": "integer", "description": "原始文件大小上限(字节)"}, - "max_dimension": map[string]interface{}{"type": "integer", "description": "长边缩放像素"}, - "jpeg_quality": map[string]interface{}{"type": "integer", "description": "JPEG 质量 60-100"}, - "max_payload_bytes": map[string]interface{}{"type": "integer", "description": "送 API 体积上限(字节)"}, - "skip_preprocess_below_bytes": map[string]interface{}{"type": "integer", "description": "低于该字节且尺寸合规时可原图直传;0=始终压缩"}, - "detail": map[string]interface{}{"type": "string", "enum": []string{"low", "high", "auto"}, "description": "OpenAI 兼容 image detail"}, + "enabled": map[string]interface{}{"type": "boolean", "description": "是否启用 analyze_image"}, + "model": map[string]interface{}{"type": "string", "description": "视觉模型名(必填)", "example": "qwen-vl-max"}, + "api_key": map[string]interface{}{"type": "string", "description": "API Key;留空复用 openai.api_key"}, + "base_url": map[string]interface{}{"type": "string", "description": "Base URL;留空复用 openai.base_url"}, + "provider": map[string]interface{}{"type": "string", "description": "提供商;留空复用 openai.provider"}, + "timeout_seconds": map[string]interface{}{"type": "integer", "description": "VL 调用超时(秒)"}, + "max_image_bytes": map[string]interface{}{"type": "integer", "description": "原始文件大小上限(字节)"}, + "max_dimension": map[string]interface{}{"type": "integer", "description": "长边缩放像素"}, + "jpeg_quality": map[string]interface{}{"type": "integer", "description": "JPEG 质量 60-100"}, + "max_payload_bytes": map[string]interface{}{"type": "integer", "description": "送 API 体积上限(字节)"}, + "skip_preprocess_below_bytes": map[string]interface{}{"type": "integer", "description": "低于该字节且尺寸合规时可原图直传;0=始终压缩"}, + "detail": map[string]interface{}{"type": "string", "enum": []string{"low", "high", "auto"}, "description": "OpenAI 兼容 image detail"}, }, }, "AnalyzeImageToolCall": map[string]interface{}{ @@ -1432,7 +1432,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) { { "name": "id", "in": "path", "required": true, "description": "对话ID", - "schema": map[string]interface{}{"type": "string"}, + "schema": map[string]interface{}{"type": "string"}, }, }, "requestBody": map[string]interface{}{ @@ -2570,7 +2570,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) { "content": map[string]interface{}{ "application/json": map[string]interface{}{ "schema": map[string]interface{}{ - "type": "object", + "type": "object", "required": []string{"source_fact_key", "target_fact_key", "edge_type"}, "properties": map[string]interface{}{ "source_fact_key": map[string]interface{}{"type": "string"}, diff --git a/internal/handler/vulnerability.go b/internal/handler/vulnerability.go index 57d84d0b..a81c88a6 100644 --- a/internal/handler/vulnerability.go +++ b/internal/handler/vulnerability.go @@ -45,9 +45,12 @@ type CreateVulnerabilityRequest struct { Status string `json:"status"` Type string `json:"type"` Target string `json:"target"` - Proof string `json:"proof"` + Preconditions string `json:"preconditions"` + ReproSteps string `json:"reproduction_steps"` + Evidence string `json:"evidence"` Impact string `json:"impact"` Recommendation string `json:"recommendation"` + RetestNotes string `json:"retest_notes"` } // CreateVulnerability 创建漏洞 @@ -69,9 +72,12 @@ func (h *VulnerabilityHandler) CreateVulnerability(c *gin.Context) { Status: req.Status, Type: req.Type, Target: req.Target, - Proof: req.Proof, + Preconditions: req.Preconditions, + ReproSteps: req.ReproSteps, + Evidence: req.Evidence, Impact: req.Impact, Recommendation: req.Recommendation, + RetestNotes: req.RetestNotes, } created, err := h.db.CreateVulnerability(vuln) @@ -118,7 +124,7 @@ func parseVulnerabilityListFilter(c *gin.Context) database.VulnerabilityListFilt q = strings.TrimSpace(c.Query("search")) } return database.VulnerabilityListFilter{ - ProjectID: c.Query("project_id"), + ProjectID: c.Query("project_id"), ID: c.Query("id"), Search: q, ConversationID: c.Query("conversation_id"), @@ -197,17 +203,20 @@ func (h *VulnerabilityHandler) ListVulnerabilities(c *gin.Context) { // UpdateVulnerabilityRequest 更新漏洞请求 type UpdateVulnerabilityRequest struct { ProjectID *string `json:"project_id"` - ConversationTag string `json:"conversation_tag"` - TaskTag string `json:"task_tag"` - Title string `json:"title"` - Description string `json:"description"` - Severity string `json:"severity"` - Status string `json:"status"` - Type string `json:"type"` - Target string `json:"target"` - Proof string `json:"proof"` - Impact string `json:"impact"` - Recommendation string `json:"recommendation"` + ConversationTag *string `json:"conversation_tag"` + TaskTag *string `json:"task_tag"` + Title *string `json:"title"` + Description *string `json:"description"` + Severity *string `json:"severity"` + Status *string `json:"status"` + Type *string `json:"type"` + Target *string `json:"target"` + Preconditions *string `json:"preconditions"` + ReproSteps *string `json:"reproduction_steps"` + Evidence *string `json:"evidence"` + Impact *string `json:"impact"` + Recommendation *string `json:"recommendation"` + RetestNotes *string `json:"retest_notes"` } // UpdateVulnerability 更新漏洞 @@ -231,38 +240,47 @@ func (h *VulnerabilityHandler) UpdateVulnerability(c *gin.Context) { if req.ProjectID != nil { existing.ProjectID = strings.TrimSpace(*req.ProjectID) } - if req.ConversationTag != "" { - existing.ConversationTag = req.ConversationTag + if req.ConversationTag != nil { + existing.ConversationTag = *req.ConversationTag } - if req.TaskTag != "" { - existing.TaskTag = req.TaskTag + if req.TaskTag != nil { + existing.TaskTag = *req.TaskTag } - if req.Title != "" { - existing.Title = req.Title + if req.Title != nil { + existing.Title = *req.Title } - if req.Description != "" { - existing.Description = req.Description + if req.Description != nil { + existing.Description = *req.Description } - if req.Severity != "" { - existing.Severity = req.Severity + if req.Severity != nil { + existing.Severity = *req.Severity } - if req.Status != "" { - existing.Status = req.Status + if req.Status != nil { + existing.Status = *req.Status } - if req.Type != "" { - existing.Type = req.Type + if req.Type != nil { + existing.Type = *req.Type } - if req.Target != "" { - existing.Target = req.Target + if req.Target != nil { + existing.Target = *req.Target } - if req.Proof != "" { - existing.Proof = req.Proof + if req.Preconditions != nil { + existing.Preconditions = *req.Preconditions } - if req.Impact != "" { - existing.Impact = req.Impact + if req.ReproSteps != nil { + existing.ReproSteps = *req.ReproSteps } - if req.Recommendation != "" { - existing.Recommendation = req.Recommendation + if req.Evidence != nil { + existing.Evidence = *req.Evidence + } + if req.Impact != nil { + existing.Impact = *req.Impact + } + if req.Recommendation != nil { + existing.Recommendation = *req.Recommendation + } + if req.RetestNotes != nil { + existing.RetestNotes = *req.RetestNotes } if err := h.db.UpdateVulnerability(id, existing); err != nil { @@ -495,9 +513,19 @@ func appendVulnerabilityMarkdown(b *strings.Builder, v *database.Vulnerability, b.WriteString(v.Description) b.WriteString("\n") } - if v.Proof != "" { - b.WriteString("\n#### 证明(POC)\n\n```\n") - b.WriteString(v.Proof) + if v.Preconditions != "" { + b.WriteString("\n#### 前置条件\n\n") + b.WriteString(v.Preconditions) + b.WriteString("\n") + } + if v.ReproSteps != "" { + b.WriteString("\n#### 复现步骤\n\n") + b.WriteString(v.ReproSteps) + b.WriteString("\n") + } + if v.Evidence != "" { + b.WriteString("\n#### 证据 / POC\n\n```\n") + b.WriteString(v.Evidence) b.WriteString("\n```\n") } if v.Impact != "" { @@ -510,6 +538,11 @@ func appendVulnerabilityMarkdown(b *strings.Builder, v *database.Vulnerability, b.WriteString(v.Recommendation) b.WriteString("\n") } + if v.RetestNotes != "" { + b.WriteString("\n#### 复测方式\n\n") + b.WriteString(v.RetestNotes) + b.WriteString("\n") + } b.WriteString("\n") }