diff --git a/AGENTS/agent_cmdb.py b/AGENTS/agent_cmdb.py deleted file mode 100644 index d45d84f..0000000 --- a/AGENTS/agent_cmdb.py +++ /dev/null @@ -1,169 +0,0 @@ -import os -from typing import Annotated, List, Literal, Any - -from langchain.agents import create_agent -from langchain_core.messages import HumanMessage, AIMessage -from langchain_core.runnables import RunnableConfig -from langgraph.graph import END, StateGraph, add_messages -from langgraph.graph.state import CompiledStateGraph -from langgraph.prebuilt import ToolNode -from pydantic import BaseModel, Field - -from Lib.baseplaybook import LanggraphPlaybook -from Lib.configs import DATA_DIR -from Lib.llmapi import load_system_prompt_template -from PLUGINS.LLM.llmapi import LLMAPI -# Modify the following functions to the actual CMDB API -from PLUGINS.Mock.CMDB.CMDB import get_ci_context_tool, fuzzy_search_ci_tool, get_cis_by_software_tool, get_cis_by_port_tool, get_cis_by_service_tool, \ - get_cis_by_user_tool - -AGENT_NODE = "AGENT_NODE" -TOOL_NODE = "TOOL_NODE" -MAX_ITERATIONS = 2 - - -class AgentState(BaseModel): - messages: Annotated[List[Any], add_messages] = Field(default_factory=list) - loop_count: int = Field(default=0, description="Count of agent iterations") - - -class AgentCMDB(object): - - @staticmethod - def cmdb_query_asset( - query: Annotated[str, "The CMDB query in natural language (e.g., 'Find asset with IP 10.10.10.10')"] - ) -> Annotated[str, "A JSON containing asset details"]: - """ - Query internal asset information from CMDB. - """ - agent = AgentGraphCMDB() - result = agent.cmdb_query(query) - return result - - -# Use langgraph to create a CMDB query agent for finer-grained control -class AgentGraphCMDB(LanggraphPlaybook): - - def __init__(self): - super().__init__() # do not delete this code - self.init() - - def init(self): - tools = [ - get_ci_context_tool, - fuzzy_search_ci_tool, - get_cis_by_software_tool, - get_cis_by_port_tool, - get_cis_by_service_tool, - get_cis_by_user_tool, - ] - - tool_node = ToolNode(tools, name=TOOL_NODE) - - def route_after_agent(state: AgentState) -> Literal[TOOL_NODE, END]: - if state.loop_count >= MAX_ITERATIONS: - self.logger.debug(f"Max iterations ({MAX_ITERATIONS}) reached, ending agent.") - return END - last_message = state.messages[-1] - if last_message.tool_calls: - return TOOL_NODE - return END - - def agent_node(state: AgentState): - self.logger.debug(f"Agent Node Invoked (Loop: {state.loop_count})") - - system_prompt_template = self.load_system_prompt_template(f"system") - system_message = system_prompt_template.format() - - messages = [ - system_message, - *state.messages - ] - - if state.loop_count >= MAX_ITERATIONS - 1: - self.logger.warning("Approaching max iterations, forcing agent to provide final answer.") - - stop_instruction = ( - "\n\n[SYSTEM NOTICE]: You have reached the search limit. " - "Do not call any more tools. Please provide your final conclusion " - "based ONLY on the information gathered above." - ) - messages.append(HumanMessage(content=stop_instruction)) - - llm_api = LLMAPI() - base_llm = llm_api.get_model(tag=["fast"]) - response: AIMessage = base_llm.invoke(messages) - else: - llm_api = LLMAPI() - llm = llm_api.get_model(tag=["fast", "function_calling"]) - llm_with_tools = llm.bind_tools(tools) - response: AIMessage = llm_with_tools.invoke(messages) - - if state.loop_count >= MAX_ITERATIONS - 1: - if response.tool_calls: - self.logger.info("Stripping hallucinated tool calls in final round.") - response.tool_calls = [] - - return {"messages": [response], "loop_count": state.loop_count + 1} - - workflow = StateGraph(AgentState) - - workflow.add_node(AGENT_NODE, agent_node) - workflow.add_node(TOOL_NODE, tool_node) - - workflow.set_entry_point(AGENT_NODE) - workflow.add_conditional_edges(AGENT_NODE, route_after_agent) - workflow.add_edge(TOOL_NODE, AGENT_NODE) - - self.graph: CompiledStateGraph = workflow.compile(checkpointer=self.get_checkpointer()) - - def cmdb_query(self, query): - self.graph.checkpointer.delete_thread(self.module_name) - config = RunnableConfig() - config["configurable"] = {"thread_id": self.module_name} - self.agent_state = AgentState(messages=[HumanMessage(content=query)], loop_count=0) - response = self.graph.invoke(self.agent_state, config) - return response['messages'][-1].content - - -# Use the create_agent method to create a CMDB query agent for a simpler implementation -def cmdb_query( - query: Annotated[str, "The CMDB query in natural language (e.g., 'Find asset with IP 10.10.10.10')"] -) -> Annotated[str, "The query result in JSON format"]: - """ - Query internal asset information from CMDB using natural language. - """ - llm_api = LLMAPI() - - llm = llm_api.get_model(tag=["fast", "function_calling"]) - - CMDB_AGENT_TOOLS = [ - get_ci_context_tool, - fuzzy_search_ci_tool, - get_cis_by_software_tool, - get_cis_by_port_tool, - get_cis_by_service_tool, - get_cis_by_user_tool, - ] - prompt_path = os.path.join(DATA_DIR, "Agent_CMDB", "system.md") - system_prompt_template = load_system_prompt_template(prompt_path) - agent = create_agent( - model=llm, - tools=CMDB_AGENT_TOOLS, - system_prompt=system_prompt_template.format(), - ) - - response = agent.invoke({"messages": [HumanMessage(content=query)]}) - - result = response['messages'][-1].content - return result - - -if __name__ == "__main__": - query = "Find asset information for IP address 192.168.10.5" - - # result = cmdb_query(query) - - agent = AgentGraphCMDB() - result = agent.cmdb_query(query) - print(result) diff --git a/AGENTS/agent_report.py b/AGENTS/agent_report.py deleted file mode 100644 index d5abc8e..0000000 --- a/AGENTS/agent_report.py +++ /dev/null @@ -1,149 +0,0 @@ -from typing import Annotated, List, Any - -from langchain_core.messages import AIMessage, HumanMessage -from langgraph.graph import END, StateGraph, add_messages -from langgraph.graph.state import CompiledStateGraph -from pydantic import BaseModel, Field, ConfigDict - -from Lib.api import get_current_time_str -from Lib.baseplaybook import LanggraphPlaybook -from PLUGINS.LLM.llmapi import LLMAPI - -AGENT_NODE_SUMMARIZE = "SUMMARIZE" -AGENT_NODE_REPORT = "REPORT" -MAX_EVIDENCE_CHARS = 120000 - -_graph_agent_instance = None - - -def get_report_graph() -> CompiledStateGraph: - global _graph_agent_instance - if _graph_agent_instance is None: - _graph_agent_instance = GraphAgentReport() - return _graph_agent_instance.graph - - -def _normalize_content(content: Any) -> str: - if content is None: - return "" - if isinstance(content, str): - return content - return str(content) - - -def _build_evidence_entries(messages: List[Any]) -> List[dict]: - entries = [] - for idx, message in enumerate(messages, 1): - role = getattr(message, "type", message.__class__.__name__) - content = _normalize_content(getattr(message, "content", "")) - tool_calls = getattr(message, "tool_calls", None) - if tool_calls: - content = f"{content}\n\nTool calls: {tool_calls}" - entries.append({"id": f"E{idx}", "role": role, "content": content}) - return entries - - -def _build_evidence_text(entries: List[dict]) -> str: - lines = [] - for entry in entries: - lines.append(f"{entry['id']} | role={entry['role']} | {entry['content']}") - return "\n\n".join(lines) - - -def _build_citations(entries: List[dict]) -> List[dict]: - citations = [] - for entry in entries: - excerpt = entry["content"].replace("\n", " ").strip() - if len(excerpt) > 500: - excerpt = excerpt[:500] + "..." - citations.append({"id": entry["id"], "role": entry["role"], "excerpt": excerpt}) - return citations - - -def _split_entries(entries: List[dict], max_chars: int) -> List[List[dict]]: - chunks = [] - current = [] - current_len = 0 - for entry in entries: - entry_text = f"{entry['id']} | role={entry['role']} | {entry['content']}" - entry_len = len(entry_text) + 2 - if current and current_len + entry_len > max_chars: - chunks.append(current) - current = [] - current_len = 0 - current.append(entry) - current_len += entry_len - if current: - chunks.append(current) - return chunks - - -class AgentState(BaseModel): - model_config = ConfigDict(arbitrary_types_allowed=True) - messages: Annotated[List[Any], add_messages] = Field(default_factory=list) - summary_digest: str = Field(default="") - report_markdown: str = Field(default="") - citations: List[dict] = Field(default_factory=list) - - -class GraphAgentReport(LanggraphPlaybook): - def __init__(self): - super().__init__() - self._summary_prompt_template = self.load_system_prompt_template("summary") - self._summary_merge_prompt_template = self.load_system_prompt_template("summary_merge") - self._report_prompt_template = self.load_system_prompt_template("system") - self._llm_api = LLMAPI() - self._llm_base = self._llm_api.get_model(tag=["powerful"]) - self.graph = self._build_graph() - - def _build_graph(self) -> CompiledStateGraph: - def summarize_node(state: AgentState): - entries = _build_evidence_entries(state.messages) - citations = _build_citations(entries) - evidence_text = _build_evidence_text(entries) - if len(evidence_text) <= MAX_EVIDENCE_CHARS: - system_message = self._summary_prompt_template.format( - CURRENT_UTC_TIME=get_current_time_str(), - REPORT_LANGUAGE="English" - ) - human_message = HumanMessage(content=evidence_text) - response: AIMessage = self._llm_base.invoke([system_message, human_message]) - return {"summary_digest": response.content, "citations": citations} - chunks = _split_entries(entries, MAX_EVIDENCE_CHARS) - partials = [] - for chunk in chunks: - chunk_text = _build_evidence_text(chunk) - system_message = self._summary_prompt_template.format( - CURRENT_UTC_TIME=get_current_time_str(), - REPORT_LANGUAGE="English" - ) - human_message = HumanMessage(content=f"PARTIAL_DIGEST: true\n\n{chunk_text}") - response: AIMessage = self._llm_base.invoke([system_message, human_message]) - partials.append(response.content) - merge_system_message = self._summary_merge_prompt_template.format( - CURRENT_UTC_TIME=get_current_time_str(), - REPORT_LANGUAGE="English" - ) - merge_human_message = HumanMessage(content="\n\n".join(partials)) - merged: AIMessage = self._llm_base.invoke([merge_system_message, merge_human_message]) - return {"summary_digest": merged.content, "citations": citations} - - def report_node(state: AgentState): - system_message = self._report_prompt_template.format( - CURRENT_UTC_TIME=get_current_time_str(), - REPORT_LANGUAGE="English" - ) - human_message = HumanMessage(content=state.summary_digest) - response: AIMessage = self._llm_base.invoke([system_message, human_message]) - return { - "messages": [response], - "report_markdown": response.content - } - - workflow = StateGraph(AgentState) - workflow.add_node(AGENT_NODE_SUMMARIZE, summarize_node) - workflow.add_node(AGENT_NODE_REPORT, report_node) - workflow.set_entry_point(AGENT_NODE_SUMMARIZE) - workflow.add_edge(AGENT_NODE_SUMMARIZE, AGENT_NODE_REPORT) - workflow.add_edge(AGENT_NODE_REPORT, END) - return workflow.compile() diff --git a/AGENTS/simpler/agents.py b/AGENTS/simpler/agents.py deleted file mode 100644 index 113c025..0000000 --- a/AGENTS/simpler/agents.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Annotated - -from langchain.agents import create_agent -from langchain_core.messages import HumanMessage - -from AGENTS.agent_siem import tools -from Lib.api import get_current_time_str -from Lib.configs import DATA_DIR -from Lib.llmapi import load_system_prompt_template -from Lib.log import logger -from PLUGINS.LLM.llmapi import LLMAPI - - -# Alternative, simpler agent implementation using create_agent -def create_siem_agent( - query: Annotated[str, "A natural language query for SIEM."] -) -> Annotated[str, "A summary of the findings from the SIEM search."]: - """ -a simpler, stateless agent created using the create_agent factory function from langchain.agents. - """ - - logger.info(f"Creating SIEM agent with query: {query[:100]}...") - prompt_path = os.path.join(DATA_DIR, "Agent_SIEM", "system_prompt.md") - logger.debug(f"Loading system prompt from: {prompt_path}") - system_prompt = load_system_prompt_template(prompt_path).format(CURRENT_UTC_TIME=get_current_time_str()) - logger.debug(f"System prompt loaded successfully") - - llm_api = LLMAPI() - llm = llm_api.get_model(tag=["fast", "function_calling"]) - logger.debug(f"LLM model obtained with tags: ['fast', 'function_calling']") - - logger.debug(f"Creating agent with {len(tools)} tools") - agent = create_agent(llm, tools, system_prompt=system_prompt) - logger.debug(f"Agent created successfully") - - logger.info(f"Invoking agent...") - response = agent.invoke({"messages": [HumanMessage(content=query)]}) - logger.info(f"Agent invocation completed") - - result = response['messages'][-1].content - logger.info(f"Agent result extracted, result length: {len(result)} characters") - return result - - -if __name__ == "__main__": - import os - import django - - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ASP.settings") - django.setup() - - print("\n--- Using create_agent for Query ---") - test_query = "Have there been any suspicious logins for the user 'admin' on Windows machines?" - result_simple = create_siem_agent(test_query) - print(result_simple) diff --git a/DATA/AGENTS/Agent_CMDB/system.md b/DATA/AGENTS/Agent_CMDB/system.md deleted file mode 100644 index 02061f5..0000000 --- a/DATA/AGENTS/Agent_CMDB/system.md +++ /dev/null @@ -1,2 +0,0 @@ -You are a CMDB query assistant. You can call appropriate CMDB tools to query based on the user's natural language query request and return the results in JSON -format. \ No newline at end of file diff --git a/DATA/AGENTS/Agent_Report/summary.md b/DATA/AGENTS/Agent_Report/summary.md deleted file mode 100644 index 9afdf2e..0000000 --- a/DATA/AGENTS/Agent_Report/summary.md +++ /dev/null @@ -1,35 +0,0 @@ -You are a SOC summarization subgraph. Your task is to read the provided evidence list and produce a summary digest for report generation. - -Hard rules: -- Output language: {REPORT_LANGUAGE}. -- Use ONLY information present in the evidence list. Do not invent facts. -- Every material claim MUST be backed by a cited Evidence ID. -- Do not drop information. If you must compress, keep all material facts and preserve all IOCs, assets, identities, alerts, and timestamps. -- The evidence list is ordered and uses Evidence IDs (E1, E2, ...). Use these IDs in your output. -- Output must be valid JSON only, no surrounding text. - -If the input begins with "PARTIAL_DIGEST: true", produce a partial digest in the same JSON schema, preserving all items from the chunk. - -JSON schema to output: -{ - "evidence_index": [{"id": "E1", "role": "human|ai|tool|system|other", "excerpt": "..."}], - "facts": [{"statement": "...", "evidence": ["E1", "E2"]}], - "timeline": [{"time": "...", "event": "...", "evidence": ["E1"]}], - "observations": [{"category": "Network|Endpoint|Identity|Email|Cloud|Other", "detail": "...", "evidence": ["E1"]}], - "entities": { - "assets": ["..."], - "identities": ["..."], - "iocs": ["..."], - "alerts": ["..."], - "tools_queries": ["..."], - "files": ["..."], - "processes": ["..."], - "urls_domains": ["..."], - "emails": ["..."], - "ips": ["..."], - "hashes": ["..."] - }, - "decisions_actions": [{"action": "...", "evidence": ["E1"]}], - "uncertainties": ["..."] -} - diff --git a/DATA/AGENTS/Agent_Report/summary_merge.md b/DATA/AGENTS/Agent_Report/summary_merge.md deleted file mode 100644 index 1ca5b0c..0000000 --- a/DATA/AGENTS/Agent_Report/summary_merge.md +++ /dev/null @@ -1,16 +0,0 @@ -You are a SOC digest merger. Your task is to merge multiple JSON summary digests into one comprehensive digest. - -Hard rules: -- Output language: {REPORT_LANGUAGE}. -- Use ONLY information present in the partial digests. Do not invent facts. -- Do not drop information. If duplicates exist, consolidate but keep all unique facts and evidence IDs. -- Output must be valid JSON only, no surrounding text. - -Merge logic: -- evidence_index: union by id; keep the most informative excerpt. -- facts, timeline, observations, decisions_actions: union items; merge if statements are equivalent; combine evidence lists. -- entities: union unique items per list. -- uncertainties: union unique items. - -Output JSON schema must match the summary digest schema exactly. - diff --git a/DATA/AGENTS/Agent_Report/system.md b/DATA/AGENTS/Agent_Report/system.md deleted file mode 100644 index 9ef6a2d..0000000 --- a/DATA/AGENTS/Agent_Report/system.md +++ /dev/null @@ -1,115 +0,0 @@ -You are a SOC reporting subgraph. Your task is to read the provided summary digest and generate a cybersecurity analysis -report in Markdown. - -Hard rules: - -- Output language: {REPORT_LANGUAGE}. If the user explicitly requests another language in the messages, follow the user - request. -- Use ONLY information present in the summary digest. Do not invent facts. -- Every material claim MUST be backed by a cited Evidence ID. -- If evidence is missing, write "Unknown" or "Not provided in messages". -- The output must strictly follow the fixed template and headings below. -- Evidence IDs must reference the order of the input messages, starting from E1 for the first message. -- Provide a concise evidence excerpt in the Evidence Index. -- Do not add extra sections or reorder headings. - -Report time: {CURRENT_UTC_TIME} - -# SOC Cybersecurity Analysis Report - -## Executive Summary - -- Overall assessment: -- Severity: -- Rationale (Evidence: ) - -## Detection and Alert Context - -- Detection sources: -- Rule/signature/alert IDs: -- Initial trigger: -- Confidence level: - -## Scope and Assumptions - -- Data sources: Summary digest only -- Time window inferred: -- Environment inferred: -- Known limitations: - -## Key Findings (TL;DR) -- - -## Timeline (Inferred) - -| Time (UTC if available) | Event | Supporting Evidence IDs | -|-------------------------|-------|-------------------------| -| | | | - -## Observations and Evidence - -### Network -- - -### Endpoint -- - -### Identity and Access -- - -### Email and Collaboration -- - -### Cloud and SaaS -- - -### Other -- - -## Asset and Identity Context - -- Affected assets (hostnames/IPs/roles): -- Affected identities (users/roles/privilege): -- Business criticality: - -## Threat Assessment - -- Likely attack path / kill chain: -- MITRE ATT&CK mapping (Technique + rationale + Evidence IDs): -- Adversary intent or goal (if inferable): - -## Impact and Exposure Assessment - -- Affected assets/users: -- Potential impact types: -- Exposure window: -- Potential blast radius: - -## Containment, Eradication, and Recovery - -### Immediate (0-24h) -- - -### Short-term (1-7d) -- - -### Long-term Hardening -- - -## Validation Steps -- - -## Open Questions and Data Requests -- - -## Lessons Learned (Evidence-based only) -- - -## Appendix - -### Indicators (IOCs) -- - -### Evidence Index - -- E1: diff --git a/PLUGINS/ClaudeCode/skills/asp-alert-en/SKILL.md b/PLUGINS/ClaudeCode/skills/asp-alert-en/SKILL.md index aaf222b..b4e51d5 100644 --- a/PLUGINS/ClaudeCode/skills/asp-alert-en/SKILL.md +++ b/PLUGINS/ClaudeCode/skills/asp-alert-en/SKILL.md @@ -1,7 +1,7 @@ --- name: asp-alert-en -description: 'Review ASP alerts, update AI analysis fields, inspect alert discussions, or attach enrichment after analysis.' -argument-hint: 'review alert | list alerts [filters] | update alert ' +description: 'Review ASP alerts for triage analysis.' +argument-hint: 'review alert | list alerts [filters]' compatibility: connect to asp mcp server metadata: author: Funnywolf @@ -21,17 +21,13 @@ An alert is secondary data in ASP. Each alert belongs to a case, and each alert - The user gives an alert ID and wants a quick review, inspection, or summary. - The user wants to find alerts by status, severity, confidence, or correlation UID. -- The user wants to inspect discussion context for an alert. -- The user wants to update AI analysis fields on an alert. - The user wants to attach enrichment to an alert after analysis. ## Operating Rules - Keep the response focused on triage value rather than repeating schema fields. - If the user is working on a specific alert, prefer `list_alerts(alert_id=, limit=1)` because the current MCP surface does not expose a separate `get_alert` tool. -- If the user wants to save structured analysis back onto the alert, use the `asp-enrichment-en` skill. - -Note: alerts only support `severity_ai`, `confidence_ai`, and `comment_ai` updates; verdict and summary fields belong to case updates. +- Alerts are currently read-only. If the user wants to save structured analysis back onto the alert, use the `asp-enrichment-en` skill. ## Additional Information @@ -41,10 +37,8 @@ Note: alerts only support `severity_ai`, `confidence_ai`, and `comment_ai` updat ## Decision Flow 1. If the user provides a specific alert ID or says "open", "show", "review", or "summarize" an alert, call `list_alerts(alert_id=, limit=1)`. -2. If the user wants discussion context, call `get_alert_discussions(alert_id)` after retrieving the alert. -3. If the user wants to browse or compare alerts, use `list_alerts` with supported filters. -4. If the user wants to update AI severity, AI confidence, or AI comment, call `update_alert`. -5. If the user wants to attach analysis results, intelligence, or structured context to the alert, use the `asp-enrichment-en` skill. +2. If the user wants to browse or compare alerts, use `list_alerts` with supported filters. +3. If the user wants to attach analysis results, intelligence, or structured context to the alert, use the `asp-enrichment-en` skill. ## SOP @@ -54,15 +48,13 @@ Note: alerts only support `severity_ai`, `confidence_ai`, and `comment_ai` updat 2. If the user only needs the basic alert information, call `list_alerts(alert_id=, limit=1)`. 3. If the result is empty, state that the alert was not found. 4. Parse the first JSON record. -5. If the user wants analyst discussion context, call `get_alert_discussions(alert_id)`. -6. Present only the most useful triage fields. +5. Present only the most useful triage fields. Preferred response structure: - `Alert`: alert ID, title or name, severity, status, confidence, correlation UID. - `Timeline`: created or updated time when present. - `Key Context`: source, rule, category, owner, or other high-signal fields. -- `Discussions`: only the most relevant analyst or system notes when needed. - `Assessment`: short triage judgment. ### List Alerts @@ -80,31 +72,19 @@ Preferred response structure: Then add one short explanation line when needed. -### Update Alert AI Fields - -1. Require `alert_id`. -2. Extract only supported AI fields: `severity_ai`, `confidence_ai`, and `comment_ai`. -3. Call `update_alert` with only the changed fields. -4. If the result is `None`, state that the alert was not found. -5. Confirm only the fields that changed. - ## Clarification Rules - Ask for `alert_id` only when it is missing for alert-related actions. - Ask for enum clarification only when the requested value does not map cleanly to ASP values. -- If the user says "lower confidence", "raise severity", or "leave a note", map it directly to the matching AI field when the intent is clear. ## Output Rules - Be concise. - Do not output raw JSON unless the user explicitly asks for it. - Prefer triage wording over schema wording. -- If alert data and discussion context are both used, merge them into one coherent view. -- State blockers clearly: alert not found, unsupported filter, invalid enum value, or incomplete follow-up payload. +- State blockers clearly: alert not found, unsupported filter, invalid enum value. ## Failure Handling - If the alert does not exist, say so directly. - If filters return no results, say so directly and suggest the most useful refinement. -- If the requested update field is unsupported, say which alert fields are writable. -- If the enrichment input is incomplete, ask one focused follow-up instead of guessing. \ No newline at end of file diff --git a/PLUGINS/ClaudeCode/skills/asp-alert-zh/SKILL.md b/PLUGINS/ClaudeCode/skills/asp-alert-zh/SKILL.md index 04b46c4..6f1bda3 100644 --- a/PLUGINS/ClaudeCode/skills/asp-alert-zh/SKILL.md +++ b/PLUGINS/ClaudeCode/skills/asp-alert-zh/SKILL.md @@ -1,7 +1,7 @@ --- name: asp-alert-zh -description: '审查 ASP 告警、更新 AI 分析字段、查看告警讨论。' -argument-hint: 'review alert | list alerts [filters] | update alert ' +description: '查看 ASP 告警并进行分诊分析。' +argument-hint: 'review alert | list alerts [filters]' compatibility: connect to asp mcp server metadata: author: Funnywolf @@ -21,13 +21,13 @@ Alert 是 ASP 中的二级数据,每个 Alert 都会挂载到一个 Case,一个 - 用户给出一个告警 ID,希望快速查看、审查或总结。 - 用户希望按状态、严重级别、置信度或 correlation UID 查找告警。 -- 用户想查看某条告警的分析讨论内容。 -- 用户想更新告警上的 AI 分析字段。 - 用户想在分析后把 enrichment 附加到告警。 ## 运行规则 - 回复要聚焦于分诊价值,而不是原样回显 schema 字段。 +- Alert 当前为只读接口,如需更新分析结果请使用 enrichment。 +- 如果用户需要保存分析结果或结构化上下文到告警上,使用 `asp-enrichment-zh` skill。 ## 补充信息 @@ -35,11 +35,9 @@ Alert 是 ASP 中的二级数据,每个 Alert 都会挂载到一个 Case,一个 ## 决策流程 -1. 如果用户提供了具体告警 ID,或要求“open”“show”“review”“summarize”某条告警,调用 `list_alerts(alert_id=, limit=1)`。 -2. 如果用户要求讨论上下文,在取回告警后调用 `get_alert_discussions(alert_id)`。 -3. 如果用户要浏览或对比多条告警,使用带支持过滤条件的 `list_alerts`。 -4. 如果用户要更新 AI severity、AI confidence 或 AI comment,调用 `update_alert`。 -5. 如果用户要附加分析结果、情报或结构化上下文,使用 `asp-enrichment-zh` skill。 +1. 如果用户提供了具体告警 ID,或要求"open""show""review""summarize"某条告警,调用 `list_alerts(alert_id=, limit=1)`。 +2. 如果用户要浏览或对比多条告警,使用带支持过滤条件的 `list_alerts`。 +3. 如果用户要附加分析结果、情报或结构化上下文,使用 `asp-enrichment-zh` skill。 ## SOP @@ -49,15 +47,13 @@ Alert 是 ASP 中的二级数据,每个 Alert 都会挂载到一个 Case,一个 2. 如果只需要快速查看告警基本信息,调用 `list_alerts(alert_id=, limit=1)` 即可。 3. 如果结果为空,直接说明找不到该告警。 4. 解析第一条 JSON 记录。 -5. 如果用户要求分析讨论上下文,调用 `get_alert_discussions(alert_id)`。 -6. 只呈现最有价值的分诊字段。 +5. 只呈现最有价值的分诊字段。 首选回复结构: - `Alert`:alert ID、标题或名称、严重级别、状态、置信度、correlation UID。 - `Timeline`:存在时给出创建或更新时间。 - `Key Context`:来源、规则、类别、负责人或其他高信号字段。 -- `Discussions`:只在需要时给出最相关的分析或系统备注。 - `Assessment`:简短分诊判断。 ### 列出告警 @@ -75,30 +71,19 @@ Alert 是 ASP 中的二级数据,每个 Alert 都会挂载到一个 Case,一个 然后在需要时补一句简短解释。 -### 更新告警 AI 字段 - -1. 要求提供 `alert_id`。 -2. 只提取支持的 AI 字段:`severity_ai`、`confidence_ai`、`comment_ai`。 -3. 仅带变更字段调用 `update_alert`。 -4. 如果结果为 `None`,说明找不到该告警。 -5. 只确认实际修改的字段。 - ## 澄清规则 - 只有在缺少告警相关操作所需参数时才询问 `alert_id`。 - 只有当请求值不能清晰映射到 ASP 枚举时,才要求用户澄清枚举值。 -- 如果用户说“降低 confidence”“提高 severity”或“留个备注”,在意图明确时直接映射到对应 AI 字段。 ## 输出规则 - 保持简洁。 - 除非用户明确要求,否则不要输出原始 JSON。 - 优先使用分诊语义,而不是 schema 语义。 -- 如果同时用了告警数据和讨论内容,要合并成一个连贯视图。 -- 清楚指出阻塞项:告警不存在、不支持的过滤条件、无效枚举值,或不完整的追加载荷。 +- 清楚指出阻塞项:告警不存在、不支持的过滤条件、无效枚举值。 ## 失败处理 - 如果告警不存在,直接说明。 - 如果过滤无结果,直接说明并建议最有用的收敛方式。 -- 如果请求更新的字段不受支持,明确指出哪些告警字段是可写的。 diff --git a/PLUGINS/ClaudeCode/skills/asp-artifact-en/SKILL.md b/PLUGINS/ClaudeCode/skills/asp-artifact-en/SKILL.md index a05896a..52a85e9 100644 --- a/PLUGINS/ClaudeCode/skills/asp-artifact-en/SKILL.md +++ b/PLUGINS/ClaudeCode/skills/asp-artifact-en/SKILL.md @@ -1,6 +1,6 @@ --- name: asp-artifact-en -description: 'Find artifacts by IOC and attach enrichment to artifacts.' +description: 'Find artifacts by IOC.' argument-hint: 'review artifact | list artifacts [filters]' compatibility: connect to asp mcp server metadata: @@ -19,15 +19,13 @@ Artifacts are created automatically by system processes. You can list and analyz ## When to Use -- The user wants to find artifacts by value, type, role, owner, or reputation. -- The user wants to attach enrichment or structured analysis to an artifact. +- The user wants to find artifacts by value, type, role, or owner. ## Operating Rules - Treat artifacts as the smallest investigation object on the platform. - Use `list_artifacts` for lookup and review. -- If the user wants to save analysis on the artifact itself, use `create_enrichment` plus `attach_enrichment_to_target`. -- For the full enrichment persistence workflow, use the `asp-enrichment-en` skill. +- If the user wants to save analysis on the artifact, use the `asp-enrichment-en` skill. ## Additional Information @@ -51,8 +49,8 @@ Artifacts are created automatically by system processes. You can list and analyz Preferred response structure: -| Artifact ID | Value | Type | Role | Owner | Reputation | Summary | -|-------------|-------|------|------|-------|------------|---------| +| Artifact ID | Value | Type | Role | Owner | Summary | +|-------------|-------|------|------|-------|---------| Then add one short explanation line when needed. diff --git a/PLUGINS/ClaudeCode/skills/asp-artifact-zh/SKILL.md b/PLUGINS/ClaudeCode/skills/asp-artifact-zh/SKILL.md index 5f97241..e322335 100644 --- a/PLUGINS/ClaudeCode/skills/asp-artifact-zh/SKILL.md +++ b/PLUGINS/ClaudeCode/skills/asp-artifact-zh/SKILL.md @@ -19,15 +19,13 @@ artifact 由系统自动创建,用户只能查询和分析已有 artifact, ## 适用场景 -- 用户想按 value、type、role、owner 或 reputation 查找 artifact。 -- 用户想给 artifact 附加 enrichment 或结构化分析。 +- 用户想按 value、type、role、owner 查找 artifact。 ## 运行规则 - 把 artifact 视为平台里的最小调查对象。 - 查询和审查时使用 `list_artifacts`。 -- 如果用户想把分析结果保存到 artifact 本身,使用 `create_enrichment` 加 `attach_enrichment_to_target`。 -- 如需完整的 enrichment 持久化流程,使用 `asp-enrichment-zh` skill。 +- 如需保存分析结果到 artifact,使用 `asp-enrichment-zh` skill。 ## 补充信息 @@ -50,8 +48,8 @@ artifact 由系统自动创建,用户只能查询和分析已有 artifact, 首选回复结构: -| Artifact ID | Value | Type | Role | Owner | Reputation | Summary | -|-------------|-------|------|------|-------|------------|---------| +| Artifact ID | Value | Type | Role | Owner | Summary | +|-------------|-------|------|------|-------|---------| 然后在需要时补一句简短解释。 @@ -70,4 +68,3 @@ artifact 由系统自动创建,用户只能查询和分析已有 artifact, - 如果没有匹配的 artifact,直接说明,并建议最有用的收敛方式。 - 如果目标 artifact 不存在,直接说明。 - diff --git a/PLUGINS/ClaudeCode/skills/asp-enrichment-en/SKILL.md b/PLUGINS/ClaudeCode/skills/asp-enrichment-en/SKILL.md index cc92399..e5b8a9f 100644 --- a/PLUGINS/ClaudeCode/skills/asp-enrichment-en/SKILL.md +++ b/PLUGINS/ClaudeCode/skills/asp-enrichment-en/SKILL.md @@ -1,11 +1,11 @@ --- name: asp-enrichment-en description: 'Save structured data as enrichment and attach it to a case, alert, or artifact.' -argument-hint: 'create enrichment for | attach enrichment to ' +argument-hint: 'create enrichment [fields]' compatibility: connect to asp mcp server metadata: author: Funnywolf - version: 0.1.0 + version: 0.2.0 mcp-server: asp category: cyber security tags: [ enrichment, analysis, context, investigation ] @@ -21,55 +21,37 @@ Use this skill when analysis results need to be saved back into ASP as structure - The user wants to save structured analysis, intelligence, or investigation conclusions. - The user wants to attach context to a case, alert, or artifact. - The user wants to persist SIEM findings, threat intel, asset context, or analyst conclusions. -- The user already has an enrichment and wants to reuse it on a target object. ## Operating Rules - Treat enrichment as the platform's structured result layer, not as a generic comment field. - When the goal is to persist analysis on a `case`, `alert`, or `artifact`, use this skill. -- Separate creation from attachment. -- Use `create_enrichment` for a new result record. -- Use `attach_enrichment_to_target` only after you have the enrichment row_id. +- Use `create_enrichment` to create the enrichment record and attach it to the target in one step. - Keep the payload compact and actionable. - Use the object-specific skill first when the user is still inspecting the object, and use this skill when saving the result. -## Additional Information - -- `row_id` is the UUID for each enrichment record and is used for data association. -- `enrichment_id` is the human-readable unique ID for each enrichment record. - ## Decision Flow -1. If the user wants to save a new structured result, call `create_enrichment` first. -2. If the user wants to attach the result to a case, alert, or artifact, call `attach_enrichment_to_target`. -3. If the user already has an enrichment row_id, skip creation and attach it directly. -4. If the user is still exploring the object rather than saving a result, use the corresponding object skill first. +1. If the user wants to save a new structured result on a target object, call `create_enrichment(target_id=..., ...)`. +2. If the user is still exploring the object rather than saving a result, use the corresponding object skill first. ## SOP -### Create And Attach New Enrichment +### Create Enrichment 1. Require `target_id` such as `case_000001`, `alert_000001`, or `artifact_000001`. 2. Convert the user's analysis into a compact structured enrichment payload. -3. Call `create_enrichment` and keep the returned enrichment row_id. -4. Call `attach_enrichment_to_target(target_id=, enrichment_row_id=)`. -5. Confirm that the enrichment was created and attached successfully. +3. Call `create_enrichment(target_id=, name=..., type=..., ...)`. +4. Confirm the created enrichment row_id and that it is attached to the target. Preferred response structure: - `Target ID`: target ID - `Enrichment`: created enrichment row_id -### Attach Existing Enrichment - -1. Require `target_id` and `enrichment_row_id`. -2. Call `attach_enrichment_to_target(target_id=, enrichment_row_id=)`. -3. Confirm that the enrichment was attached successfully. - ## Clarification Rules - Ask for `target_id` only when it is missing. -- Ask for the enrichment row_id only when the user wants to reuse an existing enrichment and did not provide it. - If the user only says "save this result", infer the most obvious target object from the current request when it is clear, and prefer Case. ## Output Rules @@ -83,4 +65,3 @@ Preferred response structure: - If the target object does not exist, say so directly. - If the enrichment payload is incomplete, ask one focused follow-up instead of guessing. -- If attachment fails because the enrichment row_id is missing, ask for it or create a new enrichment first. diff --git a/PLUGINS/ClaudeCode/skills/asp-enrichment-zh/SKILL.md b/PLUGINS/ClaudeCode/skills/asp-enrichment-zh/SKILL.md index 2022da6..adeaa70 100644 --- a/PLUGINS/ClaudeCode/skills/asp-enrichment-zh/SKILL.md +++ b/PLUGINS/ClaudeCode/skills/asp-enrichment-zh/SKILL.md @@ -1,11 +1,11 @@ --- name: asp-enrichment-zh description: '把结构化数据保存为 enrichment,并附加到 case、alert 或 artifact。' -argument-hint: 'create enrichment for | attach enrichment to ' +argument-hint: 'create enrichment [fields]' compatibility: connect to asp mcp server metadata: author: Funnywolf - version: 0.1.0 + version: 0.2.0 mcp-server: asp category: cyber security tags: [ enrichment, analysis, context, investigation ] @@ -14,64 +14,47 @@ metadata: # ASP Enrichment -当数据需要以结构化上下文形式保存回 ASP 且挂载到对应 case , alert 或 artifact 时,使用这个 skill。 +当数据需要以结构化上下文形式保存回 ASP 且挂载到对应 case、alert 或 artifact 时,使用这个 skill。 ## 适用场景 - 用户想保存结构化分析、情报或调查结论。 - 用户想把上下文附加到 case、alert 或 artifact。 - 用户想持久化 SIEM 发现、威胁情报、资产上下文或分析师结论。 -- 用户已经有 enrichment,希望把它复用并附加到目标对象。 ## 运行规则 - 把 enrichment 视为平台的结构化结果层,而不是普通评论字段。 - 当目标是把分析结果持久化到 `case`、`alert` 或 `artifact` 上时,使用这个 skill。 -- 区分“创建 enrichment”和“附加 enrichment”两个动作。 -- 新结果记录使用 `create_enrichment`。 -- 只有在已经拿到 enrichment row_id 后,才使用 `attach_enrichment_to_target`。 +- 使用 `create_enrichment` 创建 enrichment 记录并自动附加到目标对象。 - enrichment payload 保持紧凑且可操作。 - 查看对象本身时优先使用对象对应的 skill;保存结果时再使用本 skill。 -## 补充信息 - -- row_id 为每条 enrichment 记录的UUID,用于数据关联. enrichment_id 是每条 enrichment 记录人类可读的唯一ID - ## 决策流程 -1. 如果用户想保存新的结构化结果,先调用 `create_enrichment`。 -2. 如果用户想把结果附加到 case、alert 或 artifact,调用 `attach_enrichment_to_target`。 -3. 如果用户已经有现成的 enrichment row_id,跳过创建,直接附加。 -4. 如果用户还处于对象探索阶段而不是保存结果,先使用对应对象 skill。 +1. 如果用户想在目标对象上保存新的结构化结果,调用 `create_enrichment(target_id=..., ...)`。 +2. 如果用户还处于对象探索阶段而不是保存结果,先使用对应对象 skill。 当你已经有明确的分析结论,例如 verdict、TTP 集合、风险评级或缓解建议,并且这些内容需要保存在目标对象上时,就切换到这个 skill。 ## SOP -### 创建并附加新的 Enrichment +### 创建 Enrichment -1. 要求提供`target_id` (比如 case_000001 / alert_000001 / artifact_000001)。 +1. 要求提供 `target_id`(如 case_000001 / alert_000001 / artifact_000001)。 2. 把用户的分析整理成紧凑的结构化 enrichment payload。 -3. 调用 `create_enrichment` 并保留返回的 enrichment row_id。 -4. 调用`attach_enrichment_to_target(target_id=, enrichment_row_id=)`。 -5. 确认 enrichment 已创建并附加成功。 +3. 调用 `create_enrichment(target_id=, name=..., type=..., ...)`。 +4. 确认创建后的 enrichment row_id 及其已附加到目标对象。 首选回复结构: - `Target ID`:目标 ID - `Enrichment`:创建出的 enrichment row_id -### 附加已有 Enrichment - -1. 要求提供 `target_id` 和 `enrichment_row_id`。 -2. 调用`attach_enrichment_to_target(target_id=, enrichment_row_id=)`。 -3. 确认 enrichment 已附加成功。 - ## 澄清规则 - 只有在缺失时才询问 `target_id`。 -- 只有当用户要复用现有 enrichment 且未提供时,才询问 enrichment row_id。 -- 如果用户只说“把这个结果保存一下”,在上下文明确时推断最明显的目标对象,优先选择 Case。 +- 如果用户只说”把这个结果保存一下”,在上下文明确时推断最明显的目标对象,优先选择 Case。 ## 输出规则 @@ -84,4 +67,3 @@ metadata: - 如果目标对象不存在,直接说明。 - 如果 enrichment payload 不完整,只问一个聚焦问题,不要猜测。 -- 如果附加失败是因为缺少 enrichment row_id,就要求用户提供,或先创建新的 enrichment。 diff --git a/PLUGINS/MCP/llmfunc.py b/PLUGINS/MCP/llmfunc.py index 207eb92..ab50710 100644 --- a/PLUGINS/MCP/llmfunc.py +++ b/PLUGINS/MCP/llmfunc.py @@ -193,6 +193,8 @@ def list_artifacts( # Enrichment def create_enrichment( + target_id: Annotated[str, Field( + description="Target object ID to attach the enrichment to; must start with case_, alert_, or artifact_ (挂载富化的目标对象 ID,须以 case_、alert_ 或 artifact_ 开头)")], name: Annotated[str, Field(description="Enrichment name (富化名称)")] = "", type: Annotated[EnrichmentType, Field(description="Enrichment type (富化类型)")] = EnrichmentType.OTHER, provider: Annotated[EnrichmentProvider, Field(description="Enrichment provider (富化提供商)")] = EnrichmentProvider.OTHER, @@ -201,7 +203,7 @@ def create_enrichment( desc: Annotated[str, Field(description="Enrichment summary (富化摘要)")] = "", data: Annotated[str, Field(description="Detailed enrichment JSON string (详细富化 JSON 字符串)")] = "" ) -> Annotated[str, Field(description="Created enrichment record row ID (创建的 Enrichment 行 ID)")]: - """Create one enrichment record. (创建一条富化记录)""" + """Create one enrichment record and attach it to a target case, alert, or artifact. (创建一条富化记录并挂载到目标 Case、Alert 或 Artifact)""" model = EnrichmentModel() model.name = name model.type = type @@ -210,38 +212,19 @@ def create_enrichment( model.src_url = src_url model.desc = desc model.data = data - return Enrichment.create(model) + enrichment_row_id = Enrichment.create(model) - -def attach_enrichment_to_target( - target_id: Annotated[str, Field( - description="Target object ID to receive the enrichment; must start with case_, alert_, or artifact_ (接收富化的目标对象 ID,须以 case_、alert_ 或 artifact_ 开头)")], - enrichment_row_id: Annotated[ - str, Field(description="Enrichment record row ID returned by create_enrichment (由 create_enrichment 返回的 Enrichment 行 ID)")] -) -> Annotated[ - Optional[str], Field(description="Attached enrichment record row ID, or None if target not found (挂载后的 Enrichment 行 ID,目标不存在时返回 None)")]: - """Attach one existing enrichment record to an existing case, alert, or artifact. (将已有富化记录挂载到 Case、Alert 或 Artifact)""" normalized_target_id = target_id.strip().lower() - if normalized_target_id.startswith("case_"): - return Case.attach_enrichment( - case_id=target_id, - enrichment_row_id=enrichment_row_id - ) + Case.attach_enrichment(case_id=target_id, enrichment_row_id=enrichment_row_id) + elif normalized_target_id.startswith("alert_"): + Alert.attach_enrichment(alert_id=target_id, enrichment_row_id=enrichment_row_id) + elif normalized_target_id.startswith("artifact_"): + Artifact.attach_enrichment(artifact_id=target_id, enrichment_row_id=enrichment_row_id) + else: + raise ValueError("target_id must start with one of: case_, alert_, artifact_") - if normalized_target_id.startswith("alert_"): - return Alert.attach_enrichment( - alert_id=target_id, - enrichment_row_id=enrichment_row_id - ) - - if normalized_target_id.startswith("artifact_"): - return Artifact.attach_enrichment( - artifact_id=target_id, - enrichment_row_id=enrichment_row_id - ) - - raise ValueError("target_id must start with one of: case_, alert_, artifact_") + return enrichment_row_id # Ticket @@ -475,7 +458,6 @@ REGISTERED_MCP_TOOLS = [ # enrichment create_enrichment, - attach_enrichment_to_target, # playbook list_available_playbook_definitions,