From 1123be1f8eb52b1358567f6879873cf3b944ebe6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 18 Jun 2026 18:47:54 +0800 Subject: [PATCH] feat(aiops): normalize multi-signal alert cards --- apps/api/src/services/telegram_gateway.py | 272 +++++++++++++++++- .../tests/test_telegram_message_templates.py | 120 ++++++++ docs/LOGBOOK.md | 43 +++ .../TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md | 14 + 4 files changed, 448 insertions(+), 1 deletion(-) diff --git a/apps/api/src/services/telegram_gateway.py b/apps/api/src/services/telegram_gateway.py index 6f41054f5..dc5956222 100644 --- a/apps/api/src/services/telegram_gateway.py +++ b/apps/api/src/services/telegram_gateway.py @@ -108,6 +108,160 @@ _HOST_PROCESS_LINE_RE = re.compile( r"(?:\S+\s+){6}" r"(?P.+?)\s*$" ) +_PRIVATE_IP_RE = re.compile( + r"\b(?:10|192\.168|172\.(?:1[6-9]|2\d|3[0-1]))(?:\.\d{1,3}){2}\b" +) +_URL_RE = re.compile(r"https?://\S+") +_ABSOLUTE_PATH_RE = re.compile(r"/(?:[\w@.+-]+/){2,}[\w@.+-]*") +_SECRET_ASSIGNMENT_RE = re.compile( + r"(?i)\b(token|secret|password|authorization|api[_-]?key|bearer)\b\s*[:=]\s*\S+" +) +_BEARER_RE = re.compile(r"(?i)\bBearer\s+[A-Za-z0-9._~+/=-]+") +_AI_SIGNAL_TARGET_LABEL_RE = re.compile( + r"\b(?:host|target|service|domain|agent\.name|node|route)\s*=\s*\"?(?P[A-Za-z0-9_.:-]+)\"?" +) + + +@dataclass(frozen=True) +class AiOpsSignalSpec: + event_type: str + title: str + lane: str + source_tokens: tuple[str, ...] + gate_tokens: tuple[str, ...] + impact: str + next_step: str + recommendation_lines: tuple[str, ...] + forbidden_summary: str + default_severity: str = "P2" + + +_AI_OPS_SIGNAL_SPECS: tuple[AiOpsSignalSpec, ...] = ( + AiOpsSignalSpec( + event_type="wazuh_intrusion_signal", + title="Wazuh 入侵訊號", + lane="security_intrusion_triage", + source_tokens=( + "wazuh", + "rule.level", + "syscheck", + "authentication_failed", + "authentication failure", + "wazuh_alert", + ), + gate_tokens=("alert", "rule.level", "intrusion", "critical", "auth", "fired"), + impact="可能涉及入侵、權限濫用或檔案完整性變更;先轉 SIEM evidence,不直接封鎖或改 firewall", + next_step="建立 Wazuh security triage candidate,要求 agent、rule、severity、evidence ref 與 reviewer 欄位", + recommendation_lines=( + "├ 彙整 Wazuh rule / agent / evidence ref,建立 security incident candidate", + "├ 要求 owner 補影響範圍、誤報判斷、封鎖條件與驗證計畫", + "└ 回寫 Timeline / KM / PlayBook,Verifier 需讀回 SIEM 或 no-write evidence", + ), + forbidden_summary="不封鎖 IP、不改 firewall、不停服務、不貼 raw log;除非 owner / window / verifier 齊全。", + default_severity="P1", + ), + AiOpsSignalSpec( + event_type="kali_assessment_signal", + title="Kali 評估訊號", + lane="security_assessment_review", + source_tokens=("kali_assessment_signal", "kali", "nmap", "nikto", "sqlmap", "metasploit"), + gate_tokens=("assessment", "finding", "scan", "vulnerability", "critical", "high"), + impact="安全評估結果需要轉成 owner review;不得把 Kali active scan 當成已授權 remediation", + next_step="建立 Kali finding review candidate,分離 passive evidence、active scan request 與 remediation gate", + recommendation_lines=( + "├ 將 finding 轉成脫敏 evidence ref,不把完整掃描輸出貼進 Telegram", + "├ 補 owner scope、允許的測試窗口、目標清單與 false positive 判斷", + "└ 若需 active scan 或修復,必須另開批准與維護窗口", + ), + forbidden_summary="不觸發 active scan、不打 exploit、不改服務、不改 firewall;目前僅建立 review candidate。", + ), + AiOpsSignalSpec( + event_type="nginx_config_drift", + title="Nginx 配置漂移", + lane="public_gateway_config_drift", + source_tokens=("nginx_config_drift", "nginx drift", "nginx", "vhost", "nginx -t"), + gate_tokens=("drift", "config", "reload", "failed", "nginx -t", "route"), + impact="公開入口配置可能與 repo / rendered diff 不一致;先要求脫敏 live conf 與 route health readback", + next_step="建立 Public Gateway rendered diff candidate,等待 redacted export、nginx -t evidence 與 rollback owner", + recommendation_lines=( + "├ 比對 repo source、rendered diff、redacted live export 與 route ownership", + "├ 補 nginx -t、reload/no-reload、route smoke、TLS/ACME 與 rollback evidence", + "└ 將差異回寫 Public Gateway Gate,不直接 reload", + ), + forbidden_summary="不讀 raw live conf、不執行 nginx -t、不 reload、不改 DNS/TLS;只建立漂移候選。", + ), + AiOpsSignalSpec( + event_type="backup_restore_escrow_signal", + title="備份 / 還原 / 金庫訊號", + lane="backup_restore_escrow_triage", + source_tokens=( + "backupaggregaterunfailed", + "backupconfigcapturepartial", + "coldstartrecoveryblocked", + "escrow_missing", + "restic", + "rclone", + "offsite", + "backup", + ), + gate_tokens=("failed", "missing", "blocked", "stale", "escrow", "restore"), + impact="資料保護或 DR 信任度下降;先讀 freshness / escrow / restore evidence,不假性標綠", + next_step="建立 backup/restore readback candidate,要求 freshness SLO、restore target、escrow metadata 與 no-false-green 判讀", + recommendation_lines=( + "├ 讀回 backup-status、offsite verify、escrow missing count 與 restore drill evidence", + "├ 缺 escrow 或 restore evidence 時標 blocked,不偽造 marker", + "└ 下一步沉澱到 DR scorecard、KM / PlayBook / Verifier", + ), + forbidden_summary="不跑 backup/restore、不 remote delete、不寫 escrow marker、不改 retention;只建立 evidence candidate。", + default_severity="P1", + ), + AiOpsSignalSpec( + event_type="provider_freshness_signal", + title="Provider freshness 訊號", + lane="source_provider_freshness_triage", + source_tokens=( + "sourceprovideringestionstale", + "provider_freshness", + "ingestion stale", + "sentry", + "signoz", + "freshness", + ), + gate_tokens=("stale", "freshness", "window", "provider", "ingestion", "timeout"), + impact="觀測來源可能過期或 ingestion 中斷;不能把 route 200 或 dashboard up 當健康", + next_step="建立 provider freshness candidate,要求 provider window、last seen、direct ref 與 verifier readback", + recommendation_lines=( + "├ 對齊 Sentry / SigNoz / provider freshness window 與 direct reference", + "├ 標出缺資料、過期、timeout 與 no-false-green 風險", + "└ 回寫報表資料信任度與 AgentOps readiness,不直接切 provider", + ), + forbidden_summary="不切 provider、不呼叫付費模型、不改 env、不 reload;先補 freshness evidence。", + ), + AiOpsSignalSpec( + event_type="supply_chain_drift", + title="供應鏈漂移訊號", + lane="supply_chain_drift_review", + source_tokens=( + "dependency_supply_chain_drift", + "supply-chain", + "supply chain", + "npm audit", + "pip-audit", + "cve-", + "dependency drift", + "package drift", + ), + gate_tokens=("drift", "audit", "vulnerability", "cve-", "package", "dependency"), + impact="依賴、套件或 runner build 來源有風險;需接 package owner 與 CI evidence,不直接升級", + next_step="建立 supply-chain review candidate,要求 package scope、CVE/evidence ref、CI run 與 rollback plan", + recommendation_lines=( + "├ 彙整 package、lockfile、CVE/evidence ref、CI run 與 affected surface", + "├ 判斷是否需要 owner review、maintenance window 與 rollback plan", + "└ 產出 upgrade/downgrade/dismiss candidate,等待 reviewer acceptance", + ), + forbidden_summary="不直接升級套件、不改 lockfile、不重跑 deploy、不修改 secrets;先建立 review candidate。", + ), +) def _is_host_resource_alert_text(text: str) -> bool: @@ -122,6 +276,120 @@ def _is_host_resource_alert_text(text: str) -> bool: ) +def _contains_any_token(text: str, tokens: tuple[str, ...]) -> bool: + lowered = text.lower() + return any(token.lower() in lowered for token in tokens) + + +def _classify_aiops_signal_alert(text: str) -> AiOpsSignalSpec | None: + for spec in _AI_OPS_SIGNAL_SPECS: + if _contains_any_token(text, spec.source_tokens) and _contains_any_token( + text, + spec.gate_tokens, + ): + return spec + return None + + +def _is_aiops_signal_alert_text(text: str) -> bool: + return _classify_aiops_signal_alert(text) is not None + + +def _sanitize_aiops_signal_value(value: str) -> str: + sanitized = _BEARER_RE.sub("Bearer [redacted-secret]", value) + sanitized = _SECRET_ASSIGNMENT_RE.sub(lambda m: f"{m.group(1)}=[redacted-secret]", sanitized) + sanitized = _URL_RE.sub("[redacted-url]", sanitized) + sanitized = _PRIVATE_IP_RE.sub("[redacted-private-ip]", sanitized) + sanitized = _ABSOLUTE_PATH_RE.sub("[redacted-path]", sanitized) + sanitized = re.sub(r"\s+", " ", sanitized).strip() + return sanitized[:140] + + +def _aiops_signal_target(text: str, spec: AiOpsSignalSpec) -> str: + match = _AI_SIGNAL_TARGET_LABEL_RE.search(text) + if match: + target = _sanitize_aiops_signal_value(match.group("target")) + return target or spec.event_type + if spec.event_type == "backup_restore_escrow_signal": + return "backup_restore" + if spec.event_type == "nginx_config_drift": + return "public_gateway" + if spec.event_type == "supply_chain_drift": + return "source_control" + return spec.event_type + + +def _aiops_signal_severity(text: str, spec: AiOpsSignalSpec) -> str: + lowered = text.lower() + level_match = re.search(r"\brule\.level\s*=\s*\"?(?P\d+)\"?", text) + if level_match and int(level_match.group("level")) >= 10: + return "P1" + if any(token in lowered for token in ("p0", "critical", "crit", "failed", "blocked")): + return "P1" + if any(token in lowered for token in ("p1", "high", "warning", "warn", "stale")): + return "P2" + return spec.default_severity + + +def _aiops_signal_evidence_lines(text: str) -> list[str]: + candidate_lines: list[str] = [] + for raw_line in text.splitlines(): + stripped = raw_line.strip() + if not stripped: + continue + if len(candidate_lines) >= 3: + break + sanitized = _sanitize_aiops_signal_value(stripped) + if not sanitized: + continue + candidate_lines.append(sanitized) + + if not candidate_lines: + return ["└ 尚未收到可解析的脫敏 evidence,請補 owner evidence ref。"] + + lines = [f"├ {html.escape(line)}" for line in candidate_lines] + lines[-1] = "└" + lines[-1][1:] + return lines + + +def format_aiops_signal_alert_card(text: str) -> str: + """把資安 / 維運 raw signal 轉成 AI 自動化事件包。""" + spec = _classify_aiops_signal_alert(text) + if spec is None: + return text + + target = _aiops_signal_target(text, spec) + severity = _aiops_signal_severity(text, spec) + evidence_lines = _aiops_signal_evidence_lines(text) + + return "\n".join( + [ + f"{severity} AIOps 訊號|{html.escape(spec.title)}", + "ai_automation_alert_card_v1", + "", + "一眼摘要", + f"├ 事件類型:{html.escape(spec.event_type)}", + f"├ Target:{html.escape(target)}", + f"├ 影響:{html.escape(spec.impact)}", + "└ 產品判讀:raw_signal_normalized / no_runtime_write", + "", + "AI 自動化判讀", + f"├ Lane:{html.escape(spec.lane)}", + "├ Gate:candidate_only / runtime_write_gate=0", + f"└ 下一步:{html.escape(spec.next_step)}", + "", + "Top evidence", + *evidence_lines, + "", + "建議下一步", + *spec.recommendation_lines, + "", + "禁止事項", + f"└ {html.escape(spec.forbidden_summary)}", + ] + ) + + def _resource_load_bar(load_text: str) -> str: try: load = max(float(load_text), 0.0) @@ -387,9 +655,11 @@ def format_host_resource_alert_card(text: str) -> str: def normalize_alert_notification_payload(text: str, parse_mode: str) -> tuple[str, str]: - """最後出口統一脫敏監控告警;host 類 raw dump 一律改成 HTML 事件卡。""" + """最後出口統一脫敏監控告警;raw signal 一律改成 HTML 事件卡。""" if _is_host_resource_alert_text(text): return format_host_resource_alert_card(text), "HTML" + if _is_aiops_signal_alert_text(text): + return format_aiops_signal_alert_card(text), "HTML" return text, parse_mode diff --git a/apps/api/tests/test_telegram_message_templates.py b/apps/api/tests/test_telegram_message_templates.py index fab9ed9c2..a0225a880 100644 --- a/apps/api/tests/test_telegram_message_templates.py +++ b/apps/api/tests/test_telegram_message_templates.py @@ -19,6 +19,7 @@ from src.services.telegram_gateway import ( TelegramGateway, TelegramMessage, WeeklyReportMessage, + format_aiops_signal_alert_card, format_host_resource_alert_card, normalize_telegram_send_message_payload, ) @@ -136,6 +137,96 @@ def test_ci_runner_load_alert_becomes_capacity_event_packet() -> None: assert "不 kill process" in result +def test_wazuh_alert_becomes_aiops_signal_event_packet() -> None: + """Wazuh raw alert 不可把內網 IP、完整路徑或 token 直接送進 Telegram。""" + raw_alert = """ +Wazuh alert fired rule.level=12 agent.name="web-110" +full_log=/var/ossec/logs/alerts/alerts.json srcip=192.168.0.110 +Authorization: Bearer abcdefghijklmnopqrstuvwxyz token=super-secret +syscheck integrity changed on /etc/nginx/sites-enabled/awoooi.conf +""" + + result = format_aiops_signal_alert_card(raw_alert) + + assert "P1 AIOps 訊號|Wazuh 入侵訊號" in result + assert "ai_automation_alert_card_v1" in result + assert "wazuh_intrusion_signal" in result + assert "security_intrusion_triage" in result + assert "candidate_only" in result + assert "runtime_write_gate=0" in result + assert "Top evidence" in result + assert "Timeline / KM / PlayBook" in result + assert "不封鎖 IP" in result + assert "192.168.0.110" not in result + assert "/var/ossec" not in result + assert "/etc/nginx" not in result + assert "abcdefghijkl" not in result + assert "super-secret" not in result + + +def test_nginx_drift_alert_becomes_public_gateway_event_packet() -> None: + """Nginx drift 應進 Public Gateway gate,而不是直接要求 reload。""" + raw_alert = """ +alertname="nginx_config_drift" target="188-gateway" +nginx -t failed for /etc/nginx/sites-enabled/awoooi.conf +diff_url=https://internal.example.invalid/raw/live.conf +""" + + result = format_aiops_signal_alert_card(raw_alert) + + assert "AIOps 訊號|Nginx 配置漂移" in result + assert "nginx_config_drift" in result + assert "public_gateway_config_drift" in result + assert "redacted live export" in result + assert "runtime_write_gate=0" in result + assert "不執行 nginx -t" in result + assert "不 reload" in result + assert "/etc/nginx" not in result + assert "internal.example.invalid" not in result + + +@pytest.mark.parametrize( + ("raw_alert", "event_type", "lane"), + [ + ( + 'alertname="BackupAggregateRunFailed" host="110" escrow_missing=5 restic failed', + "backup_restore_escrow_signal", + "backup_restore_escrow_triage", + ), + ( + 'alertname="SourceProviderIngestionStale" service="sentry" freshness window stale', + "provider_freshness_signal", + "source_provider_freshness_triage", + ), + ( + "dependency_supply_chain_drift package=next CVE-2026-0001 npm audit vulnerability", + "supply_chain_drift", + "supply_chain_drift_review", + ), + ( + "kali_assessment_signal target=public-gateway nmap finding high risk", + "kali_assessment_signal", + "security_assessment_review", + ), + ], +) +def test_aiops_signal_formatter_covers_non_host_alert_lanes( + raw_alert: str, + event_type: str, + lane: str, +) -> None: + """非 host 類告警也必須進 AI lane / Gate / Verifier 語義。""" + result = format_aiops_signal_alert_card(raw_alert) + + assert "ai_automation_alert_card_v1" in result + assert event_type in result + assert lane in result + assert "candidate_only" in result + assert "runtime_write_gate=0" in result + assert "Top evidence" in result + assert "禁止事項" in result + + @pytest.mark.asyncio async def test_send_alert_notification_normalizes_host_resource_raw_dump(monkeypatch) -> None: """send_alert_notification 是最後出口,必須自動套用 AI 自動化事件包。""" @@ -168,6 +259,35 @@ async def test_send_alert_notification_normalizes_host_resource_raw_dump(monkeyp assert "/workspace/wooo/" not in payload["text"] +@pytest.mark.asyncio +async def test_send_alert_notification_normalizes_aiops_signal_alert(monkeypatch) -> None: + """send_alert_notification 也必須把 Nginx / Wazuh 類訊號轉成 AI 事件包。""" + sent_requests = [] + gateway = TelegramGateway() + + async def fake_send_request(method, payload): + sent_requests.append((method, payload)) + return {"ok": True} + + monkeypatch.setattr(TelegramGateway, "alert_chat_id", property(lambda _self: "chat")) + monkeypatch.setattr(gateway, "_send_request", fake_send_request) + + await gateway.send_alert_notification( + text=( + 'alertname="nginx_config_drift" target="188-gateway" ' + "nginx -t failed for /etc/nginx/sites-enabled/awoooi.conf" + ), + parse_mode="MarkdownV2", + ) + + payload = sent_requests[0][1] + assert payload["parse_mode"] == "HTML" + assert "ai_automation_alert_card_v1" in payload["text"] + assert "public_gateway_config_drift" in payload["text"] + assert "runtime_write_gate=0" in payload["text"] + assert "/etc/nginx" not in payload["text"] + + def test_prisma_generate_alert_redacts_raw_process_json_and_urls() -> None: """Prisma generate 類 root Node.js 告警不得把路徑、URL 或 JSON 直接送出。""" raw_alert = """WARN h110-gitea 🔴 CPU 警告: used=29.8% load=8.62 diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 7f5fcdbfa..3a13cb059 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,46 @@ +## 2026-06-18|Telegram AIOps 多訊號事件包覆蓋 + +**背景**:統帥指出 110 CPU 滿載、orphan Chrome、CI build/test 負載這類問題不應靠人工臨場判讀;AWOOOI / AwoooP / IwoooS 必須把監控、告警、PlayBook、KM、修復候選與 verifier 接成真正 AI 自動化產品。既有 host / runner CPU 告警已可轉成 `ai_automation_alert_card_v1`,本段把同一個最後出口規格擴展到 Wazuh、Kali、Nginx drift、Backup / Restore / Escrow、Provider freshness 與 Supply-chain drift。 + +**完成內容**: +- `TelegramGateway.normalize_alert_notification_payload()` 新增非 host 類 AIOps signal formatter;`send_alert_notification()`、`send_text()` 與 `_send_request("sendMessage", ...)` 最後出口都會套用,避免 raw signal 直接洗 Telegram。 +- 新增事件類型與 AI lane 映射: + - `wazuh_intrusion_signal` → `security_intrusion_triage` + - `kali_assessment_signal` → `security_assessment_review` + - `nginx_config_drift` → `public_gateway_config_drift` + - `backup_restore_escrow_signal` → `backup_restore_escrow_triage` + - `provider_freshness_signal` → `source_provider_freshness_triage` + - `supply_chain_drift` → `supply_chain_drift_review` +- 所有新卡片都固定顯示 `candidate_only / runtime_write_gate=0`,並把下一步導向 owner evidence、reviewer acceptance、rendered diff / freshness / restore / SIEM readback、KM / PlayBook / Verifier 沉澱。 +- 新增 Telegram 最後出口脫敏:URL、內網 IP、絕對路徑、Bearer token、token / password / secret / api key 類 assignment 會從 Top evidence 移除或遮罩。 +- `docs/awooop/TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md` 已補多訊號 AI 事件包契約,明確禁止把 Telegram 送達、UI 可見、route 200 或 raw alert 當 runtime 授權。 + +**邊界**: +- 這是 formatter / product contract / test 覆蓋,不是 live Wazuh block、Kali active scan、`nginx -t`、Nginx reload、backup / restore、remote delete、provider switch、package upgrade、workflow dispatch、Telegram 實發、host write 或 production write。 +- 新事件包只建立候選、Gate、Verifier 與 Learning 語義;runtime execution、action button、owner response received / accepted 仍全部維持 `0 / false`。 + +**Telegram Gateway 橙區審查**: +- 變更必要性:CPU / CI / Wazuh / Nginx / backup / provider / supply-chain 類告警若只貼 raw log,仍需要人工臨場判讀,無法成為 AI 自動化產品入口。 +- 影響範圍:只修改 Telegram 最後出口 formatter;不改 Bot token、chat routing、callback handler、approval button、webhook、outbound mirror、send API 或 secrets。 +- 替代方案:可只更新文件或只做前端 readback,但 raw signal 仍會在送出時外洩路徑 / 內網 / token 片段,因此必須在最後出口加防護。 +- 風險緩解:host formatter 優先順序不變;非 host formatter 需要同時命中 source token 與 gate token,降低一般通知被誤轉卡片的機率。 +- 回滾計畫:回退 `feat(aiops): normalize multi-signal alert cards` 即可恢復既有 host-only formatter。 +- 測試覆蓋:新增 Wazuh、Nginx、backup、provider freshness、supply-chain、Kali 與最後出口測試;rebase 後 targeted suite `71 passed`。 + +**驗證**: +- `/Users/ogt/.pyenv/shims/python3.11 -m py_compile apps/api/src/services/telegram_gateway.py apps/api/tests/test_telegram_message_templates.py`:通過。 +- `DATABASE_URL='postgresql+asyncpg://test:test@localhost/test' /Users/ogt/.pyenv/shims/python3.11 -m pytest apps/api/tests/test_telegram_message_templates.py -q`:`71 passed`。 +- `git diff --check`:通過。 +- `python3 scripts/ops/doc-secrets-sanity-check.py docs .gitea`:`DOC_SECRET_SANITY_OK scanned_files=912`。 +- `python3 scripts/security/source-control-owner-response-guard.py --root .`:`SOURCE_CONTROL_OWNER_RESPONSE_GUARD_OK`。 +- `python3 scripts/security/security-mirror-progress-guard.py --root .`:`SECURITY_MIRROR_PROGRESS_GUARD_OK`。 +- `python3 scripts/security/public-frontend-env-guard.py --root .`:`violations=0 / runtime_gate=0`。 + +**完成度同步**: +- Telegram AIOps 多訊號事件包覆蓋:本地 `100%`,待正式部署驗證後更新 production readback。 +- AI 自動化告警產品化:host / runner 已落地;本段補 Wazuh / Kali / Nginx / backup / provider freshness / supply-chain 最後出口卡片,仍需接入 AwoooP Run / Work Item / KM / PlayBook live readback。 +- IwoooS headline 仍維持 `64%`,active runtime gate 仍 `0`。 + ## 2026-06-18|Telegram 主機資源 raw dump 最後出口補洞 **背景**:統帥貼出 `15:11:26` 的 Telegram raw CPU / root Node.js / Prisma JSON dump,指出這類訊息既難讀又會把內部路徑、套件命令與外部檢查 endpoint 暴露到戰情室。前一段 formatter 已覆蓋 `send_alert_notification()` 與 `send_text()`,但應用內仍可能有人直接呼叫 `TelegramGateway._send_request("sendMessage", payload)`,因此需要把 `_send_request` 也升級成最後出口防線。 diff --git a/docs/awooop/TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md b/docs/awooop/TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md index ca8124743..0c597a308 100644 --- a/docs/awooop/TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md +++ b/docs/awooop/TELEGRAM-INCIDENT-NOTIFICATION-MODEL.md @@ -69,6 +69,20 @@ Host / runner 資源告警的第一版落地: - build 壓力會分流到 `runner_build_resource_pressure`;Prisma / package install 壓力會分流到 `runner_prisma_generate_resource_pressure`;預設都是 `candidate_only / runtime_write_gate=0`。 - 這只建立 AI 候選與判讀入口,不代表允許 kill process、restart 服務、改 Nginx、改 firewall 或執行 Kali active scan。 +非 host 維運 / 資安訊號的第一版落地: + +- `TelegramGateway.send_alert_notification()` 與 `send_text()` 現在也會把 Wazuh、Kali、Nginx drift、Backup / Restore / Escrow、Provider freshness、Supply-chain drift 類訊號轉成同一版 `ai_automation_alert_card_v1`。 +- 事件類型固定映射: + - `wazuh_intrusion_signal` → `security_intrusion_triage` + - `kali_assessment_signal` → `security_assessment_review` + - `nginx_config_drift` → `public_gateway_config_drift` + - `backup_restore_escrow_signal` → `backup_restore_escrow_triage` + - `provider_freshness_signal` → `source_provider_freshness_triage` + - `supply_chain_drift` → `supply_chain_drift_review` +- 所有上述卡片都必須顯示 `candidate_only / runtime_write_gate=0`,並把下一步導向 owner evidence、reviewer acceptance、rendered diff / freshness / restore / SIEM readback、KM / PlayBook / Verifier 沉澱。 +- formatter 會在 Telegram 最後出口脫敏 URL、內網 IP、絕對路徑、Bearer token、token / password / secret / api key 類 assignment;Top evidence 最多保留三行摘要。 +- 這只把 raw signal 變成 AI 自動化候選,不代表允許 Wazuh 封鎖、Kali active scan、`nginx -t` / reload、backup / restore / remote delete、provider switch、package upgrade、workflow dispatch 或 production write。 + Host / runner 告警卡片必須採用下列第一屏版型: ```text