mirror of
https://github.com/FunnyWolf/agentic-soc-platform.git
synced 2026-08-22 13:12:56 +02:00
tmp
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
---
|
||||
name: asp-siem-zh
|
||||
description: '用于在 ASP SIEM 中进行日志调查、事件检索、字段探索和结构化分析,适合从模糊线索到精确验证的调查任务。'
|
||||
argument-hint: 'explore schema [index] | search <keyword> from <UTC start> to <UTC end> | adaptive query <index_name> <time range> [filters] [aggregations]'
|
||||
compatibility: connect to asp mcp server
|
||||
metadata:
|
||||
author: Funnywolf
|
||||
version: 0.3.0
|
||||
mcp-server: asp
|
||||
category: cyber security
|
||||
tags: [ SIEM, search, SOC, hunting, investigation ]
|
||||
documentation: https://asp.viperrtp.com/
|
||||
---
|
||||
|
||||
# ASP SIEM
|
||||
|
||||
当用户要在 ASP 中进行 SIEM 调查时,使用这个 skill。重点是帮助用户根据调查目标选择合适的查询方式,并输出对分析有价值的证据。
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 用户要在 ASP SIEM 中查找、确认或补充安全事件证据。
|
||||
- 用户想先了解有哪些索引、字段或可用数据源。
|
||||
- 用户想根据 IOC、告警上下文或关键词搜索相关日志。
|
||||
- 用户想在已知范围内做进一步过滤、统计或结构化分析。
|
||||
- 用户想从 alert、artifact 或 case 继续向 SIEM 侧取证。
|
||||
|
||||
## 运行规则
|
||||
|
||||
- 如果用户请求已经隐含 SIEM 搜索,不要反问用户要选哪种操作。
|
||||
- 只收集所选路径缺失的必要输入。
|
||||
- 当用户不知道正确索引或字段时,使用 `siem_explore_schema`。
|
||||
- `siem_keyword_search` 适合关键词驱动的搜索,既可用于广范围探索,也可用于已知 index 下的快速检索。
|
||||
- `siem_adaptive_query` 适合精确字段过滤、受控聚合和稳定复现,能力上更接近一个结构化 SIEM API。
|
||||
- 如果用户给出相对时间窗口,先调用 `get_current_time`,从返回的本地时间加时区推导出可用的 UTC 范围。
|
||||
- 优化目标是有用证据,而不是最大原始输出。
|
||||
|
||||
|
||||
## 函数说明
|
||||
|
||||
### `siem_keyword_search`
|
||||
|
||||
适用场景:
|
||||
|
||||
- 用户手里有 IP、域名、用户名、主机名、hash、进程名、邮件地址、报错文本或其他关键词,想直接查有没有相关日志。
|
||||
- 用户还不确定日志在哪个 index,想先在较大范围或全局范围搜索。
|
||||
- 用户想像 SIEM 检索界面那样多次调整关键词和时间范围,逐步观察结果分布。
|
||||
- 用户已经知道 index,但当前只需要基于关键词快速找事件,不需要精确字段过滤。
|
||||
|
||||
使用方法:
|
||||
|
||||
- 必填参数是 `keyword`、`time_range_start`、`time_range_end`。
|
||||
- `keyword` 可以是单个字符串,也可以是字符串列表;传列表时表示 AND 匹配。
|
||||
- `index_name` 可选;不传表示全局搜索,传入时表示只在指定源或 index 中搜索。
|
||||
- `time_field` 默认使用 `@timestamp`,只有在已知数据源使用其他时间字段时才改。
|
||||
|
||||
适合输出和解读:
|
||||
|
||||
- 返回的是搜索命中的事件集合,适合观察是否命中、命中分布在哪些 backend 或 index、事件时间是否集中、是否出现新的可疑关键词或字段。
|
||||
- 它既可以作为调查起点,也可以作为调查中的快速二次搜索工具。
|
||||
- 它不要求调用前就知道精确字段名,因此更适合模糊搜索和探索。
|
||||
|
||||
### `siem_adaptive_query`
|
||||
|
||||
适用场景:
|
||||
|
||||
- 用户已经知道或基本确认日志所在的 `index_name`。
|
||||
- 用户已经有较明确的过滤条件,例如某个字段必须等于某值,或某几个字段要做精确匹配。
|
||||
- 用户想做 top-N、分组统计、字段聚合、受控范围查询。
|
||||
- 用户需要一个更稳定、可复现、结构化的查询,而不是继续做自由关键词搜索。
|
||||
|
||||
使用方法:
|
||||
|
||||
- 必填参数是 `index_name`、`time_range_start`、`time_range_end`。
|
||||
- `filters` 是精确字段过滤条件,键是字段名,值可以是单个字符串或字符串列表。
|
||||
- `aggregation_fields` 是可选的聚合字段列表;只有用户明确需要统计或分组时才添加。
|
||||
- `time_field` 默认使用 `@timestamp`,只有在目标数据源不是这个字段时才修改。
|
||||
|
||||
适合输出和解读:
|
||||
|
||||
- 返回的是结构化查询结果,更适合做稳定验证、字段统计和结论支撑。
|
||||
- 它更像提供给模型使用的一个 SIEM API,不要求一定来自 `siem_keyword_search` 的下一步,但通常需要更清晰的上下文。
|
||||
- 当用户已经给出 index、时间范围和大致条件时,可以直接使用,不必强行先走关键词搜索。
|
||||
|
||||
### 两者如何选择
|
||||
|
||||
- 如果线索主要是“关键词”,优先用 `siem_keyword_search`。
|
||||
- 如果线索主要是“已知 index + 已知字段条件”,优先用 `siem_adaptive_query`。
|
||||
- 如果用户目标是找事件、看分布、补线索,优先用 `siem_keyword_search`。
|
||||
- 如果用户目标是精确过滤、聚合统计、稳定复现,优先用 `siem_adaptive_query`。
|
||||
- 如果用户没有说明字段名且 index 也不确定,不要强推 `siem_adaptive_query`。
|
||||
- 如果用户已经说明明确的 index 和 filters,也不要机械地先跑 `siem_keyword_search`。
|
||||
|
||||
## 决策流程
|
||||
|
||||
1. 如果用户问该用哪个索引、有哪些字段,或 SIEM 源如何组织,使用 `siem_explore_schema`。
|
||||
2. 如果用户给出相对时间窗口,调用 `get_current_time`,从返回的本地时间加时区推导出可用 UTC 范围,再继续。
|
||||
|
||||
## SOP
|
||||
|
||||
### 探索 Schema
|
||||
|
||||
1. 如果用户不知道目标源,先调用 `siem_explore_schema()`。
|
||||
2. 如果用户已经知道索引且想要字段结构,调用 `siem_explore_schema(target_index=<index>)`。
|
||||
3. 解析返回的 JSON。
|
||||
4. 总结与调查目标最相关的索引、时间字段候选和高信号字段。
|
||||
5. 推荐下一步查询路径:先全局关键词搜索,或在已明确位置时直接做自适应查询。
|
||||
|
||||
### 使用 `siem_keyword_search`
|
||||
|
||||
1. 先提取已知最强关键词。
|
||||
2. 只有当用户真正要求所有条件都匹配时,才把多个关键词规范化为 AND 集合。
|
||||
3. 要求 UTC 时间戳以 `Z` 结尾。
|
||||
4. 如果用户未指定 `index_name`,可先全局搜索;如果用户已明确数据源,也可以直接限定到该 index。
|
||||
5. 调用 `siem_keyword_search`。
|
||||
6. 解析每条返回的 JSON 字符串,并按用户目标决定输出重点:
|
||||
- 如果用户在找事件,优先展示代表性命中。
|
||||
- 如果用户在判断日志落点,优先展示 backend、index 分布和时间分布。
|
||||
- 如果用户在继续收敛,优先总结下一轮应补充或移除哪些关键词。
|
||||
7. 根据结果进行下一轮判断:
|
||||
- 命中太多时,先收窄时间范围,再补充一到两个高信号关键词。
|
||||
- 命中太少或为空时,先移除一个限制性关键词,或适度扩展时间范围。
|
||||
- 命中集中在少数 index 时,下一轮优先指定 `index_name`。
|
||||
- 当结果已经足够说明日志位置和关键字段时,切换到 `siem_adaptive_query`。
|
||||
|
||||
### 使用 `siem_adaptive_query`
|
||||
|
||||
1. 要求 `index_name`、UTC 时间范围,以及至少一个精确过滤条件或明确聚合目标。
|
||||
2. 把过滤条件规范化为精确字段/值对。
|
||||
3. 只有当用户想要流行度、top-N 统计或分组范围时,才添加 `aggregation_fields`。
|
||||
4. 调用 `siem_adaptive_query`。
|
||||
5. 用分析师语言总结过滤范围、命中情况和任何聚合输出。
|
||||
6. 如果结果不理想,优先判断是 `filters` 太严、字段名不对、时间范围不对,还是其实应回到 `siem_keyword_search` 补充上下文。
|
||||
|
||||
### 优化搜索
|
||||
|
||||
首选优化动作:
|
||||
|
||||
1. 先用 `siem_keyword_search` 在全局或较大范围内建立感觉,不要过早假设 index。
|
||||
2. 在添加很多新关键词前,先收窄时间范围。
|
||||
3. 添加一两个高信号关键词,而不是很多弱关键词。
|
||||
4. 如果查询为空,移除一个限制性关键词,或适度扩大时间窗口。
|
||||
5. 当广泛搜索返回太多无关数据时,优先指定 `index_name` 或补充更强信号。
|
||||
6. 当结果已经显示日志主要集中在某类 index 时,下一轮搜索就应该收敛到这些 index。
|
||||
7. 当用户已经学到足够字段结构和日志落点时,可切换到 `siem_adaptive_query`;但如果用户仍只是想继续搜事件,也可以继续用 `siem_keyword_search`。
|
||||
8. 持续迭代直到结果质量匹配用户目标。
|
||||
|
||||
|
||||
|
||||
## 回复策略
|
||||
|
||||
始终解释搜索的含义,而不只是它返回了什么。
|
||||
|
||||
首选回复结构:
|
||||
|
||||
### 搜索概览
|
||||
|
||||
- 搜索模式:schema 探索、关键词搜索或自适应查询
|
||||
- 关键词集或精确过滤条件
|
||||
- 时间范围
|
||||
- 搜索的索引或 `all`
|
||||
- 如果使用了聚合字段
|
||||
- 用一两句话给出整体解释,说明当前搜索是在探索、收敛还是验证
|
||||
|
||||
|
||||
### 证据要点
|
||||
|
||||
- 对调查重要的关键字段统计。
|
||||
- 只有在增加价值时才给出代表性记录。
|
||||
- 对于 schema 探索,只强调对 hunt 重要的索引和字段。
|
||||
- 对于关键词搜索,说明这是关键词匹配结果,适合继续搜索、看分布、找线索或定位日志来源。
|
||||
- 对于自适应查询,说明这是结构化过滤或聚合结果,适合做验证、统计和稳定复现。
|
||||
|
||||
### 下一步最佳动作
|
||||
|
||||
- 收窄时间范围
|
||||
- 添加一个更强关键词
|
||||
- 移除一个限制性关键词
|
||||
- 根据分布切换到特定 index
|
||||
- 搜索特定索引
|
||||
- 用精确过滤切换到自适应查询
|
||||
- 把有用 SIEM 结果保存为相关 case、alert 或 artifact 上的 enrichment
|
||||
- 停止,因为证据已经足够
|
||||
|
||||
## 澄清规则
|
||||
|
||||
- 如果缺少时间范围,询问时间范围。
|
||||
- 只有当用户没有提供 UTC 且意图时区不清楚时,才询问时区。
|
||||
- 除非已经明显收敛,否则不要一开始就强制用户提供 `index_name`。
|
||||
- 只有当广泛搜索可能浪费、用户已经暗示已知源,或自适应查询是正确工具时,才询问 `index_name`。
|
||||
- 只有当用户想要自适应查询且 schema 仍不清楚时,才询问精确字段名。
|
||||
- 如果用户说"看看这个事件周围",从可用 IOC 和时间框架推导出合理的首次搜索,而不是让他们设计查询。
|
||||
|
||||
## 输出规则
|
||||
|
||||
- 保持简洁。
|
||||
- 默认不要倾倒每条返回记录。
|
||||
- 优先展示最相关的记录和统计。
|
||||
- 当返回多个组时,按 backend 和 index 分组结果。
|
||||
- 对于 schema 探索,呈现候选列表而不是原始字段清单。
|
||||
- 对于关键词搜索,优先输出能指导下一轮搜索的分布和线索,而不是机械罗列日志。
|
||||
- 对于自适应查询,优先输出能支撑结论的过滤命中和聚合结果。
|
||||
- 如果没有找到数据,直接说明并建议最可能有用的调整。
|
||||
|
||||
## 失败处理
|
||||
|
||||
- 无效时间格式:要求 UTC ISO8601 带尾部 `Z`。
|
||||
- 空结果:先扩展时间范围或移除一个关键词;如果过早指定了 index,也考虑回到全局关键词搜索。
|
||||
- 太多命中:先收窄时间范围,再添加信号,必要时切到更可能的 index。
|
||||
- 未知索引或字段选择:在猜测前使用 `siem_explore_schema`。
|
||||
- Backend 或源问题:如果结果指示了,说明哪个 backend 或索引失败。
|
||||
@@ -1,686 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from enum import StrEnum
|
||||
from typing import List, Optional, Any, Union, ClassVar, Annotated
|
||||
|
||||
from pydantic import field_validator, ConfigDict, BaseModel, BeforeValidator, Field, PlainSerializer
|
||||
|
||||
from PLUGINS.SIRP.nocolymodel import AttachmentModel, AttachmentCreateModel
|
||||
|
||||
|
||||
class MessageType(StrEnum):
|
||||
SYSTEM = "SystemMessage"
|
||||
HUMAN = "HumanMessage"
|
||||
TOOL = "ToolMessage"
|
||||
AI = "AIMessage"
|
||||
|
||||
|
||||
class PlaybookType(StrEnum):
|
||||
CASE = "CASE"
|
||||
ALERT = "ALERT"
|
||||
ARTIFACT = "ARTIFACT"
|
||||
|
||||
|
||||
class KnowledgeSource(StrEnum):
|
||||
MANUAL = "Manual"
|
||||
CASE = "Case"
|
||||
|
||||
|
||||
class TicketStatus(StrEnum):
|
||||
UNKNOWN = 'Unknown'
|
||||
NEW = 'New'
|
||||
IN_PROGRESS = 'In Progress'
|
||||
NOTIFIED = 'Notified'
|
||||
ON_HOLD = 'On Hold'
|
||||
RESOLVED = 'Resolved'
|
||||
CLOSED = 'Closed'
|
||||
CANCELED = 'Canceled'
|
||||
REOPENED = 'Reopened'
|
||||
OTHER = 'Other'
|
||||
|
||||
|
||||
class TicketType(StrEnum):
|
||||
OTHER = 'Other'
|
||||
JIRA = 'Jira'
|
||||
SERVICENOW = 'ServiceNow'
|
||||
PAGERDUTY = 'PagerDuty'
|
||||
SLACK = 'Slack'
|
||||
|
||||
|
||||
class ArtifactType(StrEnum):
|
||||
UNKNOWN = 'Unknown'
|
||||
HOSTNAME = 'Hostname'
|
||||
IP_ADDRESS = 'IP Address'
|
||||
MAC_ADDRESS = 'MAC Address'
|
||||
USER_NAME = 'User Name'
|
||||
EMAIL_ADDRESS = 'Email Address'
|
||||
URL_STRING = 'URL String'
|
||||
FILE_NAME = 'File Name'
|
||||
HASH = 'Hash'
|
||||
PROCESS_NAME = 'Process Name'
|
||||
RESOURCE_UID = 'Resource UID'
|
||||
PORT = 'Port'
|
||||
SUBNET = 'Subnet'
|
||||
COMMAND_LINE = 'Command Line'
|
||||
COUNTRY = 'Country'
|
||||
PROCESS_ID = 'Process ID'
|
||||
HTTP_USER_AGENT = 'HTTP User-Agent'
|
||||
CWE = 'CWE'
|
||||
CVE = 'CVE'
|
||||
USER_CREDENTIAL_ID = 'User Credential ID'
|
||||
ENDPOINT = 'Endpoint'
|
||||
USER = 'User'
|
||||
EMAIL = 'Email'
|
||||
UNIFORM_RESOURCE_LOCATOR = 'Uniform Resource Locator'
|
||||
FILE = 'File'
|
||||
PROCESS = 'Process'
|
||||
GEO_LOCATION = 'Geo Location'
|
||||
CONTAINER = 'Container'
|
||||
REGISTRY = 'Registry'
|
||||
FINGERPRINT = 'Fingerprint'
|
||||
GROUP = 'Group'
|
||||
ACCOUNT = 'Account'
|
||||
SCRIPT_CONTENT = 'Script Content'
|
||||
SERIAL_NUMBER = 'Serial Number'
|
||||
RESOURCE = 'Resource'
|
||||
MESSAGE = 'Message'
|
||||
ADVISORY = 'Advisory'
|
||||
FILE_PATH = 'File Path'
|
||||
DEVICE = 'Device'
|
||||
REGISTRY_PATH = "Registry Path"
|
||||
OTHER = 'Other'
|
||||
|
||||
|
||||
class ArtifactRole(StrEnum):
|
||||
UNKNOWN = 'Unknown'
|
||||
TARGET = 'Target'
|
||||
ACTOR = 'Actor'
|
||||
AFFECTED = 'Affected'
|
||||
RELATED = 'Related'
|
||||
OTHER = 'Other'
|
||||
|
||||
|
||||
class ArtifactReputationScore(StrEnum):
|
||||
UNKNOWN = 'Unknown'
|
||||
VERY_SAFE = 'Very Safe'
|
||||
SAFE = 'Safe'
|
||||
PROBABLY_SAFE = 'Probably Safe'
|
||||
LEANS_SAFE = 'Leans Safe'
|
||||
MAY_NOT_BE_SAFE = 'May not be Safe'
|
||||
EXERCISE_CAUTION = 'Exercise Caution'
|
||||
SUSPICIOUS_RISKY = 'Suspicious/Risky'
|
||||
POSSIBLY_MALICIOUS = 'Possibly Malicious'
|
||||
PROBABLY_MALICIOUS = 'Probably Malicious'
|
||||
MALICIOUS = 'Malicious'
|
||||
OTHER = 'Other'
|
||||
|
||||
|
||||
class Severity(StrEnum):
|
||||
UNKNOWN = "Unknown"
|
||||
INFORMATIONAL = "Informational"
|
||||
LOW = "Low"
|
||||
MEDIUM = "Medium"
|
||||
HIGH = "High"
|
||||
CRITICAL = "Critical"
|
||||
FATAL = "Fatal"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class AttackStage(StrEnum):
|
||||
RECONNAISSANCE = "Reconnaissance"
|
||||
RESOURCE_DEVELOPMENT = "Resource Development"
|
||||
INITIAL_ACCESS = "Initial Access"
|
||||
EXECUTION = "Execution"
|
||||
PERSISTENCE = "Persistence"
|
||||
PRIVILEGE_ESCALATION = "Privilege Escalation"
|
||||
DEFENSE_EVASION = "Defense Evasion"
|
||||
CREDENTIAL_ACCESS = "Credential Access"
|
||||
DISCOVERY = "Discovery"
|
||||
LATERAL_MOVEMENT = "Lateral Movement"
|
||||
COLLECTION = "Collection"
|
||||
COMMAND_AND_CONTROL = "Command and Control"
|
||||
EXFILTRATION = "Exfiltration"
|
||||
IMPACT = "Impact"
|
||||
|
||||
|
||||
class Impact(StrEnum):
|
||||
UNKNOWN = "Unknown"
|
||||
LOW = "Low"
|
||||
MEDIUM = "Medium"
|
||||
HIGH = "High"
|
||||
CRITICAL = "Critical"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class Disposition(StrEnum):
|
||||
UNKNOWN = "Unknown"
|
||||
ALLOWED = "Allowed"
|
||||
BLOCKED = "Blocked"
|
||||
QUARANTINED = "Quarantined"
|
||||
ISOLATED = "Isolated"
|
||||
DELETED = "Deleted"
|
||||
DROPPED = "Dropped"
|
||||
CUSTOM_ACTION = "Custom Action"
|
||||
APPROVED = "Approved"
|
||||
RESTORED = "Restored"
|
||||
EXONERATED = "Exonerated"
|
||||
CORRECTED = "Corrected"
|
||||
PARTIALLY_CORRECTED = "Partially Corrected"
|
||||
UNCORRECTED = "Uncorrected"
|
||||
DELAYED = "Delayed"
|
||||
DETECTED = "Detected"
|
||||
NO_ACTION = "No Action"
|
||||
LOGGED = "Logged"
|
||||
TAGGED = "Tagged"
|
||||
ALERT = "Alert"
|
||||
COUNT = "Count"
|
||||
RESET = "Reset"
|
||||
CAPTCHA = "Captcha"
|
||||
CHALLENGE = "Challenge"
|
||||
ACCESS_REVOKED = "Access Revoked"
|
||||
REJECTED = "Rejected"
|
||||
UNAUTHORIZED = "Unauthorized"
|
||||
ERROR = "Error"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class AlertAction(StrEnum):
|
||||
UNKNOWN = "Unknown"
|
||||
ALLOWED = "Allowed"
|
||||
DENIED = "Denied"
|
||||
OBSERVED = "Observed"
|
||||
MODIFIED = "Modified"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class Confidence(StrEnum):
|
||||
UNKNOWN = "Unknown"
|
||||
LOW = "Low"
|
||||
MEDIUM = "Medium"
|
||||
HIGH = "High"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class AlertAnalyticType(StrEnum):
|
||||
UNKNOWN = "Unknown"
|
||||
RULE = "Rule"
|
||||
BEHAVIORAL = "Behavioral"
|
||||
STATISTICAL = "Statistical"
|
||||
LEARNING = "Learning (ML/DL)"
|
||||
FINGERPRINTING = "Fingerprinting"
|
||||
TAGGING = "Tagging"
|
||||
KEYWORD_MATCH = "Keyword Match"
|
||||
REGULAR_EXPRESSIONS = "Regular Expressions"
|
||||
EXACT_DATA_MATCH = "Exact Data Match"
|
||||
PARTIAL_DATA_MATCH = "Partial Data Match"
|
||||
INDEXED_DATA_MATCH = "Indexed Data Match"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class AlertAnalyticState(StrEnum):
|
||||
UNKNOWN = "Unknown"
|
||||
ACTIVE = "Active"
|
||||
SUPPRESSED = "Suppressed"
|
||||
EXPERIMENTAL = "Experimental"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class ProductCategory(StrEnum):
|
||||
DLP = "DLP"
|
||||
EMAIL = "Email"
|
||||
OT = "OT"
|
||||
PROXY = "Proxy"
|
||||
UEBA = "UEBA"
|
||||
TI = "TI"
|
||||
IAM = "IAM"
|
||||
EDR = "EDR"
|
||||
NDR = "NDR"
|
||||
CLOUD = "Cloud"
|
||||
SIEM = "SIEM"
|
||||
WAF = "WAF"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class AlertPolicyType(StrEnum):
|
||||
IDENTITY_POLICY = "Identity Policy"
|
||||
RESOURCE_POLICY = "Resource Policy"
|
||||
SERVICE_CONTROL_POLICY = "Service Control Policy"
|
||||
ACCESS_CONTROL_POLICY = "Access Control Policy"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class AlertRiskLevel(StrEnum):
|
||||
INFO = "Info"
|
||||
LOW = "Low"
|
||||
MEDIUM = "Medium"
|
||||
HIGH = "High"
|
||||
CRITICAL = "Critical"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class AlertStatus(StrEnum):
|
||||
UNKNOWN = "Unknown"
|
||||
NEW = "New"
|
||||
IN_PROGRESS = "In Progress"
|
||||
SUPPRESSED = "Suppressed"
|
||||
RESOLVED = "Resolved"
|
||||
ARCHIVED = "Archived"
|
||||
DELETED = "Deleted"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class CasePriority(StrEnum):
|
||||
UNKNOWN = "Unknown"
|
||||
LOW = "Low"
|
||||
MEDIUM = "Medium"
|
||||
HIGH = "High"
|
||||
CRITICAL = "Critical"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class CaseStatus(StrEnum):
|
||||
NEW = "New"
|
||||
IN_PROGRESS = "In Progress"
|
||||
ON_HOLD = "On Hold"
|
||||
RESOLVED = "Resolved"
|
||||
CLOSED = "Closed"
|
||||
|
||||
|
||||
class CaseVerdict(StrEnum):
|
||||
UNKNOWN = "Unknown"
|
||||
FALSE_POSITIVE = "False Positive"
|
||||
TRUE_POSITIVE = "True Positive"
|
||||
DISREGARD = "Disregard"
|
||||
SUSPICIOUS = "Suspicious"
|
||||
BENIGN = "Benign"
|
||||
TEST = "Test"
|
||||
INSUFFICIENT_DATA = "Insufficient Data"
|
||||
SECURITY_RISK = "Security Risk"
|
||||
MANAGED_EXTERNALLY = "Managed Externally"
|
||||
DUPLICATE = "Duplicate"
|
||||
OTHER = "Other"
|
||||
|
||||
|
||||
class PlaybookJobStatus(StrEnum):
|
||||
SUCCESS = 'Success'
|
||||
FAILED = 'Failed'
|
||||
PENDING = 'Pending'
|
||||
RUNNING = 'Running'
|
||||
|
||||
|
||||
class KnowledgeAction(StrEnum):
|
||||
STORE = 'Store'
|
||||
REMOVE = 'Remove'
|
||||
DONE = 'Done'
|
||||
|
||||
|
||||
class AccountModel(BaseModel):
|
||||
accountId: Optional[str] = Field(default=None, description="User's unique identifier ID (用户的唯一标识ID)")
|
||||
avatar: Optional[str] = Field(default=None, description="User avatar URL (用户头像的URL)")
|
||||
email: Optional[str] = Field(default=None, description="User email address (用户的电子邮件地址)")
|
||||
fullname: Optional[str] = Field(default=None, description="User full name (用户的全名)")
|
||||
jobNumber: Optional[str] = Field(default=None, description="User job number (用户的工号)")
|
||||
mobilePhone: Optional[str] = Field(default=None, description="User mobile phone number (用户的手机号码)")
|
||||
status: Optional[int] = Field(default=None, description="User status, e.g., 1 means active (用户状态, 例如: 1表示正常)")
|
||||
|
||||
|
||||
def validate_datetime(v: Any) -> Any:
|
||||
if not v:
|
||||
return None
|
||||
if isinstance(v, datetime):
|
||||
return v
|
||||
if not isinstance(v, str):
|
||||
return v
|
||||
|
||||
value = v.strip()
|
||||
if not value:
|
||||
return None
|
||||
|
||||
local_tz = datetime.now().astimezone().tzinfo or timezone.utc
|
||||
|
||||
try:
|
||||
dt = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=local_tz)
|
||||
return dt
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return datetime.strptime(value, "%Y-%m-%d %H:%M:%S").replace(tzinfo=local_tz)
|
||||
except ValueError:
|
||||
raise ValueError(f"Unsupported datetime format: {value}")
|
||||
|
||||
|
||||
def serialize_datetime(v: Any) -> Any:
|
||||
if isinstance(v, datetime):
|
||||
local_tz = datetime.now().astimezone().tzinfo or timezone.utc
|
||||
dt = v.replace(tzinfo=local_tz) if v.tzinfo is None else v
|
||||
return dt.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
return v
|
||||
|
||||
|
||||
AutoDatetime = Annotated[
|
||||
Union[datetime, None],
|
||||
BeforeValidator(validate_datetime),
|
||||
PlainSerializer(serialize_datetime, when_used="json")
|
||||
]
|
||||
|
||||
|
||||
def validate_account(v: Any) -> Any:
|
||||
if not v:
|
||||
return ""
|
||||
if isinstance(v, dict):
|
||||
return v.get("fullname")
|
||||
if isinstance(v, list):
|
||||
if len(v) == 0:
|
||||
return ""
|
||||
elif len(v) == 1:
|
||||
if isinstance(v[0], str):
|
||||
return v[0]
|
||||
elif isinstance(v[0], dict):
|
||||
return v[0].get("fullname")
|
||||
else:
|
||||
raise ValueError(f"Unsupported account format: {v}")
|
||||
else:
|
||||
tmp = []
|
||||
for one in v:
|
||||
if isinstance(one, str):
|
||||
tmp.append(one)
|
||||
elif isinstance(one, dict):
|
||||
tmp.append(one.get("fullname"))
|
||||
else:
|
||||
raise ValueError(f"Unsupported account format: {one}")
|
||||
return tmp
|
||||
|
||||
if not isinstance(v, str):
|
||||
return v
|
||||
raise ValueError(f"Unsupported account format: {v}")
|
||||
|
||||
|
||||
AutoAccount = Annotated[
|
||||
str,
|
||||
BeforeValidator(validate_account),
|
||||
]
|
||||
|
||||
|
||||
class BaseSystemModel(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
_AI_EXCLUDE_FIELDS: ClassVar[set[str]] = {"row_owner", "row_createdBy", "row_createdAt", "row_updatedAt", "row_updatedBy"} # 不传递给 AI 的字段
|
||||
|
||||
row_id: Optional[str] = Field(default=None, description="Unique row ID (唯一行 ID)")
|
||||
row_owner: Optional[AutoAccount] = Field(default=None, description="Record owner (记录所有者)")
|
||||
row_createdBy: Optional[AutoAccount] = Field(default=None, description="Creator (创建者)")
|
||||
row_createdAt: Optional[AutoDatetime] = Field(default=None, description="Record created time (记录创建时间)")
|
||||
row_updatedAt: Optional[AutoDatetime] = Field(default=None, description="Record last updated time (记录最后更新时间)")
|
||||
row_updatedBy: Optional[AutoAccount] = Field(default=None, description="Last updated by (最后更新人)")
|
||||
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
super().__init_subclass__(**kwargs)
|
||||
# 将当前类的集合与父类的集合自动合并
|
||||
cls._AI_EXCLUDE_FIELDS = cls._AI_EXCLUDE_FIELDS | BaseSystemModel._AI_EXCLUDE_FIELDS
|
||||
|
||||
@field_validator("row_owner", mode="before")
|
||||
@classmethod
|
||||
def empty_list_to_none(cls, v: Any) -> Any:
|
||||
if isinstance(v, list) and len(v) == 0:
|
||||
return None
|
||||
return v
|
||||
|
||||
def model_dump_for_ai(
|
||||
self,
|
||||
*,
|
||||
exclude_none: bool = True,
|
||||
exclude_unset: bool = True,
|
||||
exclude_default: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
return self.model_dump(
|
||||
exclude=self._AI_EXCLUDE_FIELDS,
|
||||
exclude_none=exclude_none,
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_defaults=exclude_default,
|
||||
by_alias=True
|
||||
)
|
||||
|
||||
def model_dump_json_for_ai(
|
||||
self,
|
||||
*,
|
||||
exclude_none: bool = True,
|
||||
exclude_unset: bool = True,
|
||||
exclude_default: bool = True,
|
||||
) -> str:
|
||||
return self.model_dump_json(
|
||||
exclude=self._AI_EXCLUDE_FIELDS,
|
||||
exclude_none=exclude_none,
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_defaults=exclude_default,
|
||||
by_alias=True
|
||||
)
|
||||
|
||||
|
||||
class MessageModel(BaseSystemModel):
|
||||
playbook: Optional[List[Union[PlaybookModel, str]]] = Field(default="", description="Owning playbook row ID (所属剧本行 ID)")
|
||||
node: Optional[str] = Field(default="", description="Source node name or ID (源节点名称或 ID)")
|
||||
content: Optional[str] = Field(default="", description="Message text content (消息文本内容)")
|
||||
data: Optional[str] = Field(default="", description="Message JSON payload (消息 JSON 负载)")
|
||||
type: Optional[MessageType] = Field(default=None, description="Message role type (消息角色类型)")
|
||||
|
||||
|
||||
class PlaybookModel(BaseSystemModel):
|
||||
id: Optional[str] = Field(default=None, description="Record ID e.g. playbook_000001 (记录 ID e.g. playbook_000001)")
|
||||
source_row_id: Optional[str] = Field(default="", description="Trigger source row ID (触发源行 ID)")
|
||||
source_id: Optional[str] = Field(default="",
|
||||
description="Trigger source record ID e.g. case_00000_1,alert_000001,artifact_000001 (触发源记录 ID e.g. case_00000_1,alert_000001,artifact_000001)")
|
||||
type: Optional[PlaybookType] = Field(default=None, description="Linked object type (关联对象类型)")
|
||||
name: Optional[str] = Field(default="", description="Executed playbook name (执行剧本名称)")
|
||||
user_input: Optional[str] = Field(default="", description="Initial or follow-up user input (初始或后续用户输入)")
|
||||
user: Optional[AutoAccount] = Field(default=None, description="Playbook requester (剧本请求者)")
|
||||
|
||||
job_status: Optional[PlaybookJobStatus] = Field(default=None, description="Background job status (后台任务状态)")
|
||||
job_id: Optional[str] = Field(default="", description="Background job ID (后台任务 ID)")
|
||||
remark: Optional[str] = Field(default="", description="Execution remark (执行备注)")
|
||||
|
||||
# 关联表
|
||||
messages: Optional[List[Union[MessageModel, str]]] = Field(default=None, description="Execution message history (执行消息历史)")
|
||||
|
||||
|
||||
class KnowledgeModel(BaseSystemModel):
|
||||
id: Optional[str] = Field(default=None, description="Record ID e.g. knowledge_000001 (记录 ID e.g. knowledge_000001)")
|
||||
title: Optional[str] = Field(default="", description="Knowledge title (知识标题)")
|
||||
body: Optional[str] = Field(default="", description="Knowledge content (知识内容)")
|
||||
using: Optional[bool] = Field(default=False, description="Currently in use (当前正在使用)")
|
||||
action: Optional[KnowledgeAction] = Field(default=None, description="Knowledge action (知识操作)")
|
||||
source: Optional[KnowledgeSource] = Field(default=None, description="Knowledge source (知识来源)")
|
||||
tags: Optional[List[str]] = Field(default=[], description="Knowledge tags (知识标签)", json_schema_extra={"type": 2})
|
||||
|
||||
|
||||
class EnrichmentModel(BaseSystemModel):
|
||||
_AI_EXCLUDE_FIELDS: ClassVar[set[str]] = set()
|
||||
id: Optional[str] = Field(default=None, description="Record ID e.g. enrichment_000001 (记录 ID e.g. enrichment_000001)")
|
||||
name: Optional[str] = Field(default="", description="Enrichment name (富化名称)")
|
||||
type: Optional[str] = Field(default="Other", description="Enrichment type (富化类型)", json_schema_extra={"type": 2})
|
||||
provider: Optional[str] = Field(default="Other", description="Enrichment provider (富化提供商)", json_schema_extra={"type": 2})
|
||||
value: Optional[str] = Field(default="", description="Enrichment value (富化值)")
|
||||
src_url: Optional[str] = Field(default="", description="Enrichment source URL (富化来源 URL)")
|
||||
desc: Optional[str] = Field(default="", description="Enrichment summary (富化摘要)")
|
||||
data: Optional[str] = Field(default="", description="Detailed enrichment JSON (详细富化 JSON)")
|
||||
|
||||
|
||||
class TicketModel(BaseSystemModel):
|
||||
_AI_EXCLUDE_FIELDS: ClassVar[set[str]] = {'case'}
|
||||
|
||||
id: Optional[str] = Field(default=None, description="Record ID e.g. ticket_000001 (记录 ID e.g. ticket_000001)")
|
||||
status: Optional[TicketStatus] = Field(
|
||||
default=None, description="External ticket status (外部工单状态)")
|
||||
type: Optional[TicketType] = Field(default=None, description="External ticket type (外部工单类型)",
|
||||
json_schema_extra={"type": 2})
|
||||
title: Optional[str] = Field(default="", description="Ticket title (工单标题)")
|
||||
uid: Optional[str] = Field(default="", description="External ticket ID (外部工单 ID)")
|
||||
src_url: Optional[str] = Field(default="", description="External ticket URL (外部工单 URL)")
|
||||
|
||||
# 反向关联,无需手动处理
|
||||
case: Optional[List[Union[CaseModel, str]]] = Field(default=None, description="Linked case row_id (关联案例行 ID)")
|
||||
|
||||
|
||||
class ArtifactModel(BaseSystemModel):
|
||||
"""Stores entity information extracted from alerts, the minimum investigatable unit"""
|
||||
_AI_EXCLUDE_FIELDS: ClassVar[set[str]] = {'alert'}
|
||||
|
||||
id: Optional[str] = Field(default=None, description="Record ID e.g. artifact_000001 (记录 ID e.g. artifact_000001)")
|
||||
name: Optional[str] = Field(default="", description="Artifact name (实体名称)")
|
||||
type: Optional[ArtifactType] = Field(default=None, description="Artifact type (实体类型)")
|
||||
role: Optional[ArtifactRole] = Field(default=None, description="Artifact role in event (实体在事件中的角色)")
|
||||
value: Optional[str] = Field(default="", description="Artifact value (实体值)")
|
||||
|
||||
owner: Optional[str] = Field(default="", description="Owning system or user (所属系统或用户)")
|
||||
reputation_provider: Optional[str] = Field(default="", description="Threat intel provider (威胁情报提供商)", json_schema_extra={"type": 2})
|
||||
reputation_score: Optional[ArtifactReputationScore] = Field(default=None, description="Artifact reputation (实体信誉)")
|
||||
|
||||
# 反向关联,无需手动处理
|
||||
alert: Optional[List[Union[AlertModel, str]]] = Field(default=None, description="Linked alert row_id (关联告警行 ID)")
|
||||
|
||||
# 关联表
|
||||
enrichments: Optional[List[Union[EnrichmentModel, str]]] = Field(default=None,
|
||||
description="Enrichments information (富化信息)") # None 时表示无需处理,[] 时表示要将 link 清空
|
||||
|
||||
|
||||
class AlertModel(BaseSystemModel):
|
||||
_AI_EXCLUDE_FIELDS: ClassVar[set[str]] = {'raw_data', 'severity_ai', 'confidence_ai', "impact_ai", "comment_ai", "case"}
|
||||
# 系统自动生成字段
|
||||
id: Optional[str] = Field(default=None,
|
||||
description="Record ID e.g. alert_000001, auto-generated, no manual input needed (记录 ID e.g. alert_000001, 系统自动生成,无需手动赋值)")
|
||||
|
||||
# 创建记录填写字段
|
||||
title: Optional[str] = Field(default="", description="Alert title (告警标题)")
|
||||
severity: Optional[Severity] = Field(default=Severity.UNKNOWN, description="Source-defined severity (告警来源定义的严重程度)")
|
||||
confidence: Optional[Confidence] = Field(default=Confidence.UNKNOWN, description="True-positive confidence (真阳性置信度)")
|
||||
impact: Optional[Impact] = Field(default=Impact.UNKNOWN, description="Potential impact (告警潜在影响)")
|
||||
disposition: Optional[Disposition] = Field(default=Disposition.UNKNOWN, description="Source disposition (告警源处置结果)")
|
||||
action: Optional[AlertAction] = Field(default=AlertAction.UNKNOWN, description="Observed action (告警源的动作)")
|
||||
|
||||
labels: Optional[List[str]] = Field(default=[], description="Alert labels (告警标签)", json_schema_extra={"type": 2})
|
||||
desc: Optional[str] = Field(default="", description="Alert description (告警描述)")
|
||||
|
||||
first_seen_time: Optional[AutoDatetime] = Field(default=None, description="First observed time (首次观测时间)")
|
||||
last_seen_time: Optional[AutoDatetime] = Field(default=None, description="Last observed time (最后观测时间)")
|
||||
|
||||
rule_id: Optional[str] = Field(default="", description="SIEM rule ID (SIEM 规则 ID)")
|
||||
rule_name: Optional[str] = Field(default="", description="SIEM rule name (SIEM 规则名称)")
|
||||
correlation_uid: Optional[str] = Field(default="",
|
||||
description="Case correlation ID, alerts with the same correlation_uid are linked to the same event (事件关联 ID,相同 correlation_uid 告警关联到同一个事件)")
|
||||
|
||||
src_url: Optional[str] = Field(default="", description="Source alert URL (原始告警 URL)")
|
||||
source_uid: Optional[str] = Field(default="",
|
||||
description="Source product ID, can be used to locate the unique alert in the source system (原始告警 唯一ID, 可通过该 ID 在原始来源中定位唯一告警)")
|
||||
data_sources: Optional[List[str]] = Field(default=[], description="Underlying data sources (告警源生成告警的数据来源列表)")
|
||||
|
||||
analytic_name: Optional[str] = Field(default="", description="Analytic engine name (分析引擎名称)")
|
||||
analytic_type: Optional[AlertAnalyticType] = Field(default=AlertAnalyticType.UNKNOWN, description="Analytic engine type (分析引擎类型)")
|
||||
analytic_state: Optional[AlertAnalyticState] = Field(default=None, description="Analytic rule state (分析规则状态)")
|
||||
analytic_desc: Optional[str] = Field(default="", description="Analytic rule description (分析规则描述)")
|
||||
|
||||
tactic: Optional[str] = Field(default="", description="Mapped MITRE tactic (映射的 MITRE 战术)")
|
||||
technique: Optional[str] = Field(default="", description="Mapped MITRE technique (映射的 MITRE 技术)")
|
||||
sub_technique: Optional[str] = Field(default="", description="Mapped MITRE sub-technique (映射的 MITRE 子技术)")
|
||||
mitigation: Optional[str] = Field(default="", description="Suggested mitigation (建议的缓解措施)")
|
||||
|
||||
product_category: Optional[ProductCategory] = Field(default=None, description="Source product category (原始产品类别)")
|
||||
product_vendor: Optional[str] = Field(default=None, description="Source vendor (原始厂商)", json_schema_extra={"type": 2})
|
||||
product_name: Optional[str] = Field(default=None, description="Source product name (原始产品名称)", json_schema_extra={"type": 2})
|
||||
product_feature: Optional[str] = Field(default=None, description="Source product feature (原始产品功能)", json_schema_extra={"type": 2})
|
||||
|
||||
policy_name: Optional[str] = Field(default="", description="Trigger policy name (触发策略名称)")
|
||||
policy_type: Optional[AlertPolicyType] = Field(default=None, description="Trigger policy type (触发策略类型)")
|
||||
policy_desc: Optional[str] = Field(default="", description="Trigger policy description (触发策略描述)")
|
||||
|
||||
risk_level: Optional[AlertRiskLevel] = Field(default=None, description="Assessed risk level (评估的风险等级)")
|
||||
|
||||
status: Optional[AlertStatus] = Field(default=None, description="Alert handling status (告警处理状态)")
|
||||
status_detail: Optional[str] = Field(default="", description="Handling status details (处理状态详情)")
|
||||
remediation: Optional[str] = Field(default="", description="Remediation advice or record (处置建议或记录)")
|
||||
|
||||
unmapped: Optional[str] = Field(default="", description="Raw unmapped fields, JSON Format (原始未映射字段 JSON 格式)")
|
||||
|
||||
raw_data: Optional[str] = Field(default="", description="Raw alert log JSON (原始告警日志 JSON)")
|
||||
|
||||
# AI字段
|
||||
severity_ai: Optional[Severity] = Field(default=None, description="AI-assessed severity (AI 评估严重程度)")
|
||||
confidence_ai: Optional[Confidence] = Field(default=None, description="AI-assessed confidence (AI 评估置信度)")
|
||||
impact_ai: Optional[Impact] = Field(default=Impact.UNKNOWN, description="AI-assessed potential impact (AI 评估潜在影响)")
|
||||
risk_level_ai: Optional[AlertRiskLevel] = Field(default=None, description="Assessed risk level (AI 评估的风险等级)")
|
||||
comment_ai: Optional[str] = Field(default="", description="AI-generated comment (AI 生成的注释)")
|
||||
|
||||
# 反向关联,无需手动处理
|
||||
case: Optional[List[Union[CaseModel, str]]] = Field(default=None,
|
||||
description="Linked case row_id, reverse association, auto-linked, no manual setting needed (关联案例行 ID,反向关联,自动化关联,无需手动设置)")
|
||||
|
||||
artifacts: Optional[List[Union[ArtifactModel, str]]] = Field(default=None, description="Extracted artifacts (关联表, 提取的实体列表)")
|
||||
enrichments: Optional[List[Union[AlertModel, str]]] = Field(default=None, description="Alert enrichments (关联表, 告警富化)")
|
||||
|
||||
|
||||
class CaseModel(BaseSystemModel):
|
||||
_AI_EXCLUDE_FIELDS: ClassVar[set[str]] = {"attachments", "workbook", "summary_ai", "comment_ai", "attack_stage_ai", "severity_ai", "confidence_ai",
|
||||
"threat_hunting_report_ai", "verdict_ai"}
|
||||
|
||||
id: Optional[str] = Field(default=None, description="Record ID e.g. case_000001 (记录 ID e.g. case_000001)")
|
||||
title: Optional[str] = Field(default="", description="Case title (案例标题)")
|
||||
severity: Optional[Severity] = Field(default=None,
|
||||
description="Analyst-assessed severity (分析师评估严重程度)")
|
||||
impact: Optional[Impact] = Field(default=None, description="Analyst-assessed impact (分析师评估影响)")
|
||||
priority: Optional[CasePriority] = Field(default=None, description="Response priority (响应优先级)")
|
||||
|
||||
confidence: Optional[Confidence] = Field(default=None, description="Analyst-assessed confidence (分析师评估置信度)")
|
||||
description: Optional[str] = Field(default="", description="Case description (案例描述)")
|
||||
|
||||
category: Optional[ProductCategory] = Field(default=None,
|
||||
description="Case category (案例类别)")
|
||||
tags: Optional[List[str]] = Field(default=[], description="Case tags (案例标签)", json_schema_extra={"type": 2})
|
||||
|
||||
status: Optional[CaseStatus] = Field(default=None,
|
||||
description="Case handling status (案例处理状态)")
|
||||
assignee_l1: Optional[AutoAccount] = Field(default=None, description="Assigned L1 analyst (分配的 L1 分析师)")
|
||||
acknowledged_time: Optional[AutoDatetime] = Field(default=None, description="L1 first acknowledged time (L1 首次接手时间)")
|
||||
comment: Optional[str] = Field(default="", description="Case analyst comment (案例分析师注释)")
|
||||
attachments: Optional[List[Union[AttachmentModel, AttachmentCreateModel]]] = Field(default=[], description="Case attachments (分析师上传的案例附件)")
|
||||
|
||||
assignee_l2: Optional[AutoAccount] = Field(default=None,
|
||||
description="Assigned or escalated L2 analyst (分配或升级的 L2 分析师)")
|
||||
assignee_l3: Optional[AutoAccount] = Field(default=None,
|
||||
description="Assigned or escalated L3 analyst (分配或升级的 L3 分析师)")
|
||||
closed_time: Optional[AutoDatetime] = Field(default=None, description="Case closed time (案例关闭时间)")
|
||||
verdict: Optional[CaseVerdict] = Field(
|
||||
default=None, description="Final verdict (最终判定结果)")
|
||||
summary: Optional[str] = Field(default="", description="Closure summary (结案摘要)")
|
||||
|
||||
correlation_uid: Optional[str] = Field(default="", description="Case correlation ID (案例关联 ID)")
|
||||
|
||||
workbook: Optional[str] = Field(default="", description="Investigation workbook (调查工作手册)")
|
||||
|
||||
# ai 字段
|
||||
severity_ai: Optional[Severity] = Field(default=None, description="AI-assessed severity (AI 评估严重程度)")
|
||||
confidence_ai: Optional[Confidence] = Field(default=None, description="AI-assessed confidence (AI 评估置信度)")
|
||||
impact_ai: Optional[Impact] = Field(default=None, description="AI-assessed impact (AI 评估影响)")
|
||||
priority_ai: Optional[CasePriority] = Field(default=None, description="AI-assessed response priority (AI 评估响应优先级)")
|
||||
attack_stage_ai: Optional[AttackStage] = Field(default="", description="AI-assessed attack stage (AI 评估攻击阶段)")
|
||||
comment_ai: Optional[str] = Field(default="", description="AI-generated comment (AI 生成的注释)")
|
||||
verdict_ai: Optional[CaseVerdict] = Field(default=None, description="AI-generated final verdict (AI 生成的最终判定结果)")
|
||||
summary_ai: Optional[str] = Field(default="", description="AI-generated closure summary (AI 生成的结案摘要)")
|
||||
|
||||
# 公式自动计算字段,无需手动赋值
|
||||
start_time_calc: Optional[Any] = Field(default=None, description="Calculated start time (计算的开始时间)")
|
||||
end_time_calc: Optional[Any] = Field(default=None, description="Calculated end time (计算的结束时间)")
|
||||
detect_time_calc: Optional[Any] = Field(default=None, description="Calculated detect time (计算的检测时间)")
|
||||
acknowledge_time_calc: Optional[Any] = Field(default=None, description="Calculated acknowledge time (计算的接手时间)")
|
||||
respond_time_calc: Optional[Any] = Field(default=None, description="Calculated response time (计算的响应时间)")
|
||||
|
||||
# 关联表
|
||||
tickets: Optional[List[Union[TicketModel, str]]] = Field(default=None, description="Linked external tickets (关联外部工单)")
|
||||
enrichments: Optional[List[Union[EnrichmentModel, str]]] = Field(default=None, description="Case enrichments (案例富化)")
|
||||
alerts: Optional[List[Union[AlertModel, str]]] = Field(default=None, description="Merged alerts (合并的告警)")
|
||||
|
||||
@field_validator('attachments', mode='before')
|
||||
def handle_attachments(cls, v):
|
||||
if v == "":
|
||||
return []
|
||||
return v
|
||||
Reference in New Issue
Block a user