This commit is contained in:
rookit
2026-04-20 16:44:11 +08:00
parent 84bd521b54
commit 66dcb2cb3d
6 changed files with 210 additions and 686 deletions
@@ -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 或索引失败。
View File
View File
View File
-686
View File
@@ -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