diff --git a/DATA/SYSTEM/ANALYSIS/KnowledgeKeywords.md b/DATA/SYSTEM/ANALYSIS/KnowledgeKeywords.md index 929d460..463fb35 100644 --- a/DATA/SYSTEM/ANALYSIS/KnowledgeKeywords.md +++ b/DATA/SYSTEM/ANALYSIS/KnowledgeKeywords.md @@ -13,3 +13,4 @@ 只返回 schema 要求的结构化输出。 +期望输出格式:一个 JSON 对象,包含一个 key 为 "keywords",value 为字符串数组,例如 {"keywords": ["hostname.example.com", "192.168.1.1", "Suspicious Login"]}。不要返回裸数组。 diff --git a/DATA/SYSTEM/ANALYSIS/KnowledgeKeywords_EN.md b/DATA/SYSTEM/ANALYSIS/KnowledgeKeywords_EN.md index 25b2539..9fd9c06 100644 --- a/DATA/SYSTEM/ANALYSIS/KnowledgeKeywords_EN.md +++ b/DATA/SYSTEM/ANALYSIS/KnowledgeKeywords_EN.md @@ -13,3 +13,5 @@ Keyword selection rules: Return only the structured output required by the schema. +Expected output format: a JSON object with a single key "keywords" whose value is an array of strings, e.g. {"keywords": ["hostname.example.com", "192.168.1.1", "Suspicious Login"]}. Do not return a bare array. + diff --git a/DATA/SYSTEM/ANALYSIS/System.md b/DATA/SYSTEM/ANALYSIS/System.md index 60debd0..97d4db4 100644 --- a/DATA/SYSTEM/ANALYSIS/System.md +++ b/DATA/SYSTEM/ANALYSIS/System.md @@ -10,11 +10,12 @@ Schema 的调查报告。 输入格式: -HumanMessage 是一个紧凑 JSON 对象,顶层只有两个字段:`knowledge` 和 `case`。 +HumanMessage 是一个紧凑 JSON 对象,顶层有三个字段:`knowledge`、`case` 和 `discussions`。 - `case` 是当前需要研判的主要对象。 - `knowledge.records` 是分析前检索到的内部知识补充。每条记录可能包含 `id`、`row_id`、`title`、`source`、`tags`、`expires_at` 和 `body`。`body` 字段可能包含 Markdown 内容;这些 Markdown 只属于该条知识记录本身。 - `knowledge.keywords` 是根据当前 Case 生成、用于检索这些知识记录的搜索关键词。 +- `discussions` 是分析员在案件上的评论和回复列表。每条包含 `message`(评论文本)、`created_at`、`created_by`(作者)、`reply_to_author`、`mentions`(被提及的用户列表)和 `attachments`(附件列表,含 `filename`、`ext`、`filesize`、`download_url`)。将 discussions 作为补充上下文——可能包含分析员假设、手动标注的 IOC、误报理由或结构化字段中未捕获的操作备注。 Knowledge 可能包含 Case 中不可直接看到的内部上下文,例如资产角色、负责人、业务重要性、测试 IP、蜜罐、白名单、已知良性行为、策略、SOP 或响应处置建议。当 Knowledge 有助于解释 Case 或会改变研判结论时,应使用相关 Knowledge;不要把无关 Knowledge 强行写入报告。 @@ -26,7 +27,7 @@ Knowledge 可能包含 Case 中不可直接看到的内部上下文,例如资 4. 综合使用整个 Case 上下文,包括 `alerts`、`artifacts`、`enrichments`、`tickets`、时间字段、状态字段、文本描述、处置记录以及相关内部 Knowledge。 5. 多条告警可能只是同一行为的重复观测。去重后再判断,不要把重复观测误写成多个独立攻击步骤。 6. 没有执行成功、权限获得、持久化建立、横向移动成功或数据访问成功的证据时,不要夸大为“已攻陷”。 -7. `severity`、`impact`、`priority`、`confidence`、`tactic`、`technique`、`sub_technique`、`remediation` +7. `severity`、`impact`、`priority`、`confidence` 和 `remediation` 的已有字段只作为参考;*你需要根据案件整体证据重新评估*。 8. 报告应优先服务分析和响应,不追求“字段写满”。某个维度没有足够证据时,可以输出空列表或更克制的结论。 diff --git a/DATA/SYSTEM/ANALYSIS/System_EN.md b/DATA/SYSTEM/ANALYSIS/System_EN.md index eaafd0c..8011b0b 100644 --- a/DATA/SYSTEM/ANALYSIS/System_EN.md +++ b/DATA/SYSTEM/ANALYSIS/System_EN.md @@ -26,7 +26,7 @@ Analysis principles: 4. Synthesize the full Case context, including `alerts`, `artifacts`, `enrichments`, `tickets`, timestamp fields, status fields, textual descriptions, remediation records, and relevant internal Knowledge. 5. Multiple alerts may be repeated observations of the same behavior. Deduplicate before judging — do not treat repeated observations as independent attack steps. 6. Do not exaggerate to "fully compromised" when there is no evidence of successful execution, privilege gain, persistence, successful lateral movement, or data access. -7. Existing field values for `severity`, `impact`, `priority`, `confidence`, `tactic`, `technique`, `sub_technique`, and `remediation` are for reference only. *You must re-evaluate based on the overall case evidence.* +7. Existing field values for `severity`, `impact`, `priority`, `confidence`, and `remediation` are for reference only. *You must re-evaluate based on the overall case evidence.* 8. The report should prioritize serving analysis and response — do not aim to fill every field. When a dimension lacks sufficient evidence, output an empty list or a more restrained conclusion. Think in the following analysis order: diff --git a/PLUGINS/SIRP/analysis.py b/PLUGINS/SIRP/analysis.py index d1bfe71..79209e3 100644 --- a/PLUGINS/SIRP/analysis.py +++ b/PLUGINS/SIRP/analysis.py @@ -23,73 +23,58 @@ MAX_KNOWLEDGE_RECORDS = 10 class AffectedAsset(BaseModel): - asset_type: str = Field( - description="Type of asset affected or directly operated by the attacker, e.g. Host, IP, User, Mailbox, File, Cloud Resource. 受影响或被攻击者直接操作的资产类型,例如 Host、IP、User、Mailbox、File、Cloud Resource。") - asset_value: str = Field( - description="Specific identifier of the asset, e.g. hostname, IP, username, email address, file path, cloud resource ARN. 资产的具体标识,例如主机名、IP、用户名、邮箱地址、文件路径、云资源 ARN。") + asset_type: str = Field(description="Type of asset, e.g. Host, IP, User, Mailbox, File, Cloud Resource. 资产类型。") + asset_value: str = Field(description="Specific identifier, e.g. hostname, IP, username, email, file path. 资产具体标识。") class EvidenceFinding(BaseModel): - title: str = Field( - description="Title of the key finding, e.g. Suspicious login followed by mailbox forwarding rule modification, Lateral movement detected on Host A. 关键发现标题,例如 可疑登录成功后修改邮箱转发规则、主机A出现横向移动痕迹。") - finding_type: str = Field( - description="Type of finding, e.g. Identity, Host, Process, Network, Email, Cloud, Policy, Ticket, Other. 发现类型,例如 Identity、Host、Process、Network、Email、Cloud、Policy、Ticket、Other。") - subject: str = Field( - description="The subject of this finding, e.g. an account, host, IP, URL, policy name, or alert cluster. 该发现围绕的主体,例如某账号、主机、IP、URL、策略名或告警簇。") - evidence: str = Field( - description="Summary of core evidence supporting this finding; include traceable fields, objects, or phenomena where possible. 支撑该发现的核心证据摘要,尽量写出可追溯的字段、对象或现象。") - conclusion: str = Field( - description="Conclusion drawn from the evidence, explaining what it means in the context of this case. 基于该证据得出的结论,说明它在本案中意味着什么。") + title: str = Field(description="Short title of the finding. 发现标题。") + finding_type: str = Field(description="Category, e.g. Identity, Host, Process, Network, Email, Cloud, Policy. 发现类型。") + subject: str = Field(description="Core subject, e.g. account, host, IP, URL, policy name. 发现主体。") + evidence: str = Field(description="Core evidence summary; include traceable fields or objects. 核心证据摘要。") + conclusion: str = Field(description="What this finding means for the case judgment. 对案件判断的意义。") class AttackChainStep(BaseModel): - attack_stage: AttackStage = Field(description="MITRE ATT&CK attack stage. MITRE ATT&CK 攻击阶段。") + attack_stage: AttackStage = Field(description="MITRE ATT&CK stage. 攻击阶段。") description: str = Field( - description="What happened at this stage, how the attacker achieved it, and the supporting evidence. 该阶段发生了什么、攻击者如何实现、证据依据是什么。") + description="What happened, how it was achieved, and supporting evidence. 阶段描述与证据。") class TimelineEvent(BaseModel): - timestamp: str = Field( - description="Time the event occurred; use relative or approximate time if exact time cannot be determined. 事件发生时间;若无法精确确定,可填相对时间或近似时间。") - attack_behavior: str = Field(description="Key behavior, operation, or detection phenomenon at this point in time. 该时间点发生的关键行为、操作或检测现象。") - evidence_field: str = Field( - description="Key log field, raw excerpt, or correlated evidence supporting this conclusion. 支撑该结论的关键日志字段、原文片段或关联证据。") + timestamp: str = Field(description="Time of event; relative or approximate if exact time unavailable. 事件时间。") + attack_behavior: str = Field(description="Key behavior or detection at this time point. 关键行为或检测现象。") + evidence_field: str = Field(description="Key log field, raw excerpt, or correlated evidence. 关键日志字段或原文片段。") class IndicatorOfCompromise(BaseModel): - indicator_type: str = Field(description="IOC type;IOC 类型。") - value: str = Field(description="The specific value of the IOC. IOC 的具体值。") - context: str = Field( - description="Context of this IOC in the case, e.g. used as download URL, C2, dropped file, lateral movement command. 该 IOC 在本案中的上下文,例如作为下载地址、C2、落地文件、横向移动命令等。") + indicator_type: str = Field(description="IOC type. IOC 类型。") + value: str = Field(description="The IOC value. IOC 具体值。") + context: str = Field(description="Role in this case, e.g. download URL, C2, dropped file. 本案中的作用。") class Remediation(BaseModel): - action_type: str = Field( - description="Type of remediation action, e.g. Isolate host, Disable account, Block URL, Delete file, Fix configuration. 处置动作类型,例如隔离主机、禁用账号、阻断 URL、删除文件、修复配置。") - description: str = Field(description="Specific and directly actionable remediation or hardening recommendation. 可直接执行的处置或加固建议,要求具体。") - priority: CasePriority = Field(description="Execution priority of this remediation action itself. 该处置动作自身的执行优先级。") + action_type: str = Field(description="Action type, e.g. Isolate host, Disable account, Block URL. 处置动作类型。") + description: str = Field(description="Specific, directly actionable recommendation. 具体可执行的建议。") + priority: CasePriority = Field(description="Execution priority of this action. 执行优先级。") class InvestigationReport(BaseModel): model_config = ConfigDict(use_enum_values=False) - verdict: CaseVerdict = Field( - description="AI's final determination of the case nature, e.g. True Positive, Suspicious, False Positive, Insufficient Data. AI 对案件最终性质的判断,例如 True Positive、Suspicious、False Positive、Insufficient Data。") - severity: Severity = Field(description="Severity level of the incident as assessed by AI. AI 评估的事件严重程度。") - impact: Impact = Field(description="Impact level of the incident as assessed by AI. AI 评估的事件影响等级。") - priority: CasePriority = Field(description="Response priority as assessed by AI. AI 评估的响应优先级。") - confidence: Confidence = Field(description="Confidence level of the assessment as determined by AI. AI 评估的事件置信度。") - digest: str = Field(description="Comprehensive summary of the incident. 事件综合摘要。") - affected_assets: List[AffectedAsset] = Field(description="List of affected assets. 受影响资产列表。") - evidence_findings: List[EvidenceFinding] = Field( - description="List of key evidence findings supporting the case conclusion. 支撑案件结论的关键证据发现列表。") - attack_chain: List[AttackChainStep] = Field(description="Attack chain steps reconstructed from evidence. 基于证据重建的攻击链步骤。") - attack_timeline: List[TimelineEvent] = Field(description="Chronologically ordered list of key events. 按时间顺序排列的关键事件时间线。") - ioc_indicators: List[IndicatorOfCompromise] = Field( - description="IOC list for investigation, blocking, hunting, or ongoing monitoring. 可用于排查、封禁、搜索或持续监控的 IOC 列表。") - remediations: List[Remediation] = Field(description="Remediation and hardening recommendations for analysts. 面向分析员的处置与加固建议。") - unknowns: List[str] = Field( - description="List of unconfirmed points that require additional evidence or further investigation. 当前仍无法确认、需要补证或需要进一步排查的不确定点列表。") + verdict: CaseVerdict = Field(description="Final case determination, e.g. True Positive, Suspicious, False Positive. 案件最终性质判定。") + severity: Severity = Field(description="Incident severity. 事件严重程度。") + impact: Impact = Field(description="Incident impact scope. 事件影响范围。") + priority: CasePriority = Field(description="Response priority. 响应优先级。") + confidence: Confidence = Field(description="Assessment confidence. 评估置信度。") + digest: str = Field(description="Conclusive summary of the case. 案件综合摘要。") + affected_assets: List[AffectedAsset] = Field(description="Directly affected or operated assets. 受影响资产列表。") + evidence_findings: List[EvidenceFinding] = Field(description="Key findings supporting the conclusion. 关键证据发现列表。") + attack_chain: List[AttackChainStep] = Field(description="Confirmed behavioral chain. 已确认的行为链。") + attack_timeline: List[TimelineEvent] = Field(description="Key events in chronological order. 关键事件时间线。") + ioc_indicators: List[IndicatorOfCompromise] = Field(description="IOCs for investigation or monitoring. 可用于排查或监控的 IOC 列表。") + remediations: List[Remediation] = Field(description="Remediation recommendations. 处置与加固建议。") + unknowns: List[str] = Field(description="Unconfirmed items requiring additional evidence. 需要补证的不确定点。") class KnowledgeSearchKeywords(BaseModel): @@ -133,11 +118,18 @@ def extract_knowledge_keywords(case_json: str) -> List[str]: try: system_prompt = KNOWLEDGE_KEYWORDS_PROMPT_PATH.read_text(encoding="utf-8") llm = LLMAPI().get_model(tag="structured_output").with_structured_output(KnowledgeSearchKeywords) - result: KnowledgeSearchKeywords = llm.invoke([ + result = llm.invoke([ SystemMessage(content=system_prompt), HumanMessage(content=case_json), ]) - knowledge_keywords = normalize_knowledge_keywords(result.keywords) + if isinstance(result, KnowledgeSearchKeywords): + raw_keywords = result.keywords + elif isinstance(result, list): + raw_keywords = result + else: + logger.warning(f"Unexpected keywords result type: {type(result)}") + return [] + knowledge_keywords = normalize_knowledge_keywords(raw_keywords) return knowledge_keywords except Exception as e: logger.exception(e)