Files
CyberSentinel-AI/ai/result_validator.py

236 lines
9.5 KiB
Python

import json
from typing import Any
from utils.logger import get_logger
logger = get_logger("analyzer")
def _normalize_common_result(result: dict, source_type: str) -> dict | None:
"""规范化分析结果并校验关键字段"""
try:
if not result:
logger.error("分析结果为空")
return None
required_fields: dict[str, type] = {
"summary": str,
"risk_level": str,
"key_points": list,
"technical_details": list,
"affected_components": list,
"is_valuable": bool,
"value_reason": str,
}
if source_type == "cve":
required_fields.update({"cve_id": str, "exploit_status": str})
for field, field_type in required_fields.items():
if field not in result:
logger.error(f"分析结果缺少必需字段: {field}")
return None
if not isinstance(result[field], field_type):
logger.error(
f"字段 {field} 的类型错误,期望 {field_type.__name__},实际为 {type(result[field]).__name__}"
)
return None
valid_risk_levels = {"CRITICAL", "HIGH", "MEDIUM", "LOW"}
risk_level = result["risk_level"].upper()
if risk_level not in valid_risk_levels:
logger.error(f"无效的风险等级: {risk_level},使用默认值 MEDIUM")
risk_level = "MEDIUM"
result["risk_level"] = risk_level
if source_type == "repository" and "security_type" in result:
valid_security_types = {
"POC收集",
"漏洞利用框架",
"安全工具",
"安全研究",
"POC更新",
"漏洞利用",
"安全修复",
"安全功能",
}
security_type = result["security_type"]
if security_type not in valid_security_types:
logger.warning(f"无效的安全类型: {security_type},使用默认值 安全研究")
security_type = "安全研究"
result["security_type"] = security_type
if source_type == "cve":
valid_exploit_status = {"POC可用", "漏洞利用可用", "理论可行", "未知"}
exploit_status = result["exploit_status"]
if exploit_status not in valid_exploit_status:
logger.warning(f"无效的利用状态: {exploit_status},使用默认值 未知")
exploit_status = "未知"
result["exploit_status"] = exploit_status
for field in ["key_points", "technical_details", "affected_components"]:
if not result[field] or not any(item for item in result[field] if item and item != "Unknown"):
logger.error(f"字段 {field} 为空或仅包含无效值")
return None
result["is_valuable"] = bool(result.get("is_valuable", False))
if not result.get("value_reason"):
result["value_reason"] = "未提供价值说明"
if source_type == "repository":
# watched/update 仓库不依赖 relevance_score 判定价值
is_watched = result.get("is_watched", False)
is_update = result.get("is_update", False)
relevance_score = result.get("relevance_score", "LOW")
if relevance_score not in ["HIGH", "MEDIUM", "LOW"]:
relevance_score = "LOW"
result["relevance_score"] = relevance_score
if not is_watched and not is_update and relevance_score == "LOW":
result["is_valuable"] = False
result["value_reason"] = f"与搜索关键词 '{result.get('search_keyword', 'N/A')}' 相关性不足"
return result
except Exception as e:
logger.error(f"验证分析结果时出错: {str(e)}")
return None
def _process_repo_analysis_result(result: dict | str, repo_data: dict, processed_commits: list) -> dict | None:
"""清理并补齐仓库分析结果"""
try:
if not result:
logger.error("分析结果为空")
return None
if isinstance(result, str):
logger.info("解析JSON字符串结果")
result = json.loads(result)
cleaned_result: dict[str, Any] = {}
for key, value in result.items():
if value is None or value == "Unknown":
continue
if isinstance(value, list):
cleaned_list = [item for item in value if item and item != "Unknown"]
if cleaned_list:
cleaned_result[key] = cleaned_list
else:
cleaned_result[key] = value
repo_name = repo_data.get("name", "unknown")
if "/" in repo_name:
repo_name = repo_name.split("/")[-1]
if not cleaned_result.get("short_description"):
description = str(repo_data.get("description", "")).strip()
if description:
short_desc = description[:20] + ("..." if len(description) > 20 else "")
else:
security_type = cleaned_result.get("security_type", "安全工具")
if "POC" in security_type or "poc" in security_type.lower():
short_desc = "漏洞利用验证工具"
elif "漏洞利用" in security_type:
short_desc = "漏洞利用框架"
elif "安全工具" in security_type:
short_desc = "网络安全工具集"
elif "安全研究" in security_type:
short_desc = "安全研究项目"
else:
short_desc = "安全相关项目"
cleaned_result["short_description"] = short_desc
cleaned_result.update(
{
"repository_name": repo_name,
"repository_url": repo_data.get("url"),
"commits_analyzed": len(processed_commits),
"files_changed": sum(len(commit.get("files", [])) for commit in processed_commits),
"is_watched": repo_data.get("is_watched", False),
"is_update": repo_data.get("is_update", False),
"search_keyword": repo_data.get("search_keyword", "N/A"),
}
)
if not cleaned_result.get("summary") and cleaned_result.get("one_line_summary"):
cleaned_result["summary"] = cleaned_result["one_line_summary"]
return cleaned_result
except Exception as e:
logger.error(f"处理分析结果时出错: {str(e)}")
return None
def validate_cve_result(result: dict, cve_data: dict) -> dict | None:
"""验证并增强 CVE 分析结果"""
if not result:
return None
result.update(
{
"cve_id": cve_data.get("cve_id"),
"repository_url": cve_data.get("repository_url"),
"published_date": cve_data.get("published_date"),
"last_modified": cve_data.get("last_modified"),
"stars": cve_data.get("stars", 0),
}
)
normalized_result = _normalize_common_result(result, "cve")
if not normalized_result:
return None
if not normalized_result.get("is_valuable", False):
normalized_result["value_reason"] = "不满足推送条件"
logger.info(f"CVE {normalized_result['cve_id']} 不满足推送条件")
elif normalized_result["risk_level"] not in ["CRITICAL", "HIGH"]:
normalized_result["is_valuable"] = False
normalized_result["value_reason"] = f"风险等级较低 ({normalized_result['risk_level']})"
logger.info(f"CVE {normalized_result['cve_id']} 风险等级较低")
elif normalized_result["exploit_status"] == "未知":
normalized_result["is_valuable"] = False
normalized_result["value_reason"] = "利用状态未知"
logger.info(f"CVE {normalized_result['cve_id']} 利用状态未知")
elif not any(comp and comp != "Unknown" for comp in normalized_result["affected_components"]):
normalized_result["is_valuable"] = False
normalized_result["value_reason"] = "没有明确的受影响组件"
logger.info(f"CVE {normalized_result['cve_id']} 没有明确的受影响组件")
return normalized_result
def validate_repo_result(result: dict, repo_data: dict, processed_commits: list) -> dict | None:
"""验证并规范化仓库分析结果"""
processed_result = _process_repo_analysis_result(result, repo_data, processed_commits)
if not processed_result:
return None
return _normalize_common_result(processed_result, "repository")
def validate_article_result(result: dict) -> dict | None:
"""验证文章分类结果结构"""
try:
if not result or not isinstance(result, dict):
return None
articles = result.get("articles")
if not isinstance(articles, list):
return None
valid_articles = []
for article in articles:
if not isinstance(article, dict):
continue
title = article.get("title")
category = article.get("category")
if isinstance(title, str) and isinstance(category, str) and title.strip() and category.strip():
valid_articles.append({"title": title, "category": category})
if not valid_articles:
return None
return {"articles": valid_articles}
except Exception as e:
logger.error(f"验证文章分类结果失败: {str(e)}")
return None