feat(awooop): mirror AI alert card metadata
This commit is contained in:
@@ -154,6 +154,9 @@ _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<target>[A-Za-z0-9_.:-]+)\"?"
|
||||
)
|
||||
_ALERT_CARD_CODE_VALUE_RE = re.compile(
|
||||
r"(?P<label>事件類型|Target|Lane):<code>(?P<value>[^<]+)</code>"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -2058,9 +2061,45 @@ def _reply_markup_domain_source_refs(payload: dict) -> dict[str, list[str]]:
|
||||
return {key: values[:20] for key, values in refs.items() if values}
|
||||
|
||||
|
||||
def _ai_automation_alert_card_metadata(text: str) -> dict[str, object] | None:
|
||||
"""Extract safe AwoooP mirror metadata from normalized AI alert cards."""
|
||||
if "ai_automation_alert_card_v1" not in text:
|
||||
return None
|
||||
|
||||
values: dict[str, str] = {}
|
||||
for match in _ALERT_CARD_CODE_VALUE_RE.finditer(text):
|
||||
values[match.group("label")] = html.unescape(match.group("value")).strip()
|
||||
|
||||
event_type = values.get("事件類型", "")
|
||||
lane = values.get("Lane", "")
|
||||
if not event_type or not lane:
|
||||
return None
|
||||
|
||||
gates: list[str] = []
|
||||
if "candidate_only" in text:
|
||||
gates.append("candidate_only")
|
||||
if "runtime_write_gate=0" in text:
|
||||
gates.append("runtime_write_gate=0")
|
||||
|
||||
metadata: dict[str, object] = {
|
||||
"schema_version": "ai_automation_alert_card_mirror_v1",
|
||||
"card_schema": "ai_automation_alert_card_v1",
|
||||
"event_type": event_type,
|
||||
"lane": lane,
|
||||
"target": values.get("Target", ""),
|
||||
"gates": gates,
|
||||
"candidate_only": "candidate_only" in gates,
|
||||
"runtime_write_gate_count": 0,
|
||||
"delivery_receipt_readback_required": True,
|
||||
"mirror_source": "legacy_telegram_gateway_outbound_message",
|
||||
}
|
||||
return metadata
|
||||
|
||||
|
||||
def _outbound_source_envelope(method: str, payload: dict) -> dict[str, object]:
|
||||
"""Build a redaction-friendly source envelope for Channel Hub replay."""
|
||||
text = str(payload.get("text") or payload.get("caption") or "")
|
||||
alert_card_metadata = _ai_automation_alert_card_metadata(text)
|
||||
incident_ids = sorted(
|
||||
set(_INCIDENT_ID_RE.findall(text))
|
||||
| set(_reply_markup_incident_ids(payload))
|
||||
@@ -2080,8 +2119,23 @@ def _outbound_source_envelope(method: str, payload: dict) -> dict[str, object]:
|
||||
if key == "event_ids":
|
||||
continue
|
||||
source_refs[key] = values[:20]
|
||||
if alert_card_metadata:
|
||||
_append_source_ref(
|
||||
source_refs,
|
||||
"alert_ids",
|
||||
alert_card_metadata.get("event_type"),
|
||||
)
|
||||
_append_source_ref(
|
||||
source_refs,
|
||||
"fingerprints",
|
||||
(
|
||||
"ai_automation_alert_card:"
|
||||
f"{alert_card_metadata.get('event_type')}:"
|
||||
f"{alert_card_metadata.get('lane')}"
|
||||
),
|
||||
)
|
||||
|
||||
return {
|
||||
envelope: dict[str, object] = {
|
||||
"adapter": "legacy_telegram_gateway",
|
||||
"method": method,
|
||||
"payload_sha256": _outbound_payload_hash(payload),
|
||||
@@ -2092,6 +2146,9 @@ def _outbound_source_envelope(method: str, payload: dict) -> dict[str, object]:
|
||||
"reply_markup": _reply_markup_summary(payload),
|
||||
"source_refs": source_refs,
|
||||
}
|
||||
if alert_card_metadata:
|
||||
envelope["ai_automation_alert_card"] = alert_card_metadata
|
||||
return envelope
|
||||
|
||||
|
||||
def _callback_reply_source_envelope_extra(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from src.services.telegram_gateway import (
|
||||
format_aiops_signal_alert_card,
|
||||
_outbound_source_envelope,
|
||||
_sanitize_telegram_error,
|
||||
)
|
||||
@@ -139,3 +140,39 @@ def test_outbound_source_envelope_reads_ai_advisory_refs_without_raw_callback()
|
||||
"ai_advisory:coverage_gap:auto_rule_creation"
|
||||
]
|
||||
assert "ai_advisory_handled:coverage_gap:auto_rule_creation" not in str(envelope)
|
||||
|
||||
|
||||
def test_outbound_source_envelope_marks_wazuh_ai_alert_card_for_awooop_readback() -> None:
|
||||
raw_alert = """
|
||||
wazuh_dashboard_api_readback_degraded dashboard agent list disappeared
|
||||
POST /api/check-stored-api status=429 POST /api/check-api status=500
|
||||
https://127.0.0.1:55000 is unreachable manager registry readback blocked
|
||||
full_log=/var/ossec/logs/alerts/alerts.json Authorization: Bearer abcdefghijklmnopqrstuvwxyz
|
||||
"""
|
||||
card = format_aiops_signal_alert_card(raw_alert)
|
||||
payload = {
|
||||
"chat_id": "-100123",
|
||||
"text": card,
|
||||
"parse_mode": "HTML",
|
||||
}
|
||||
|
||||
envelope = _outbound_source_envelope("sendMessage", payload)
|
||||
|
||||
card_metadata = envelope["ai_automation_alert_card"]
|
||||
assert card_metadata["schema_version"] == "ai_automation_alert_card_mirror_v1"
|
||||
assert card_metadata["card_schema"] == "ai_automation_alert_card_v1"
|
||||
assert card_metadata["event_type"] == "wazuh_dashboard_api_readback_degraded"
|
||||
assert card_metadata["lane"] == "siem_observability_readback_degraded"
|
||||
assert card_metadata["target"] == "wazuh_dashboard_api"
|
||||
assert card_metadata["gates"] == ["candidate_only", "runtime_write_gate=0"]
|
||||
assert card_metadata["runtime_write_gate_count"] == 0
|
||||
assert card_metadata["delivery_receipt_readback_required"] is True
|
||||
assert envelope["source_refs"]["alert_ids"] == [
|
||||
"wazuh_dashboard_api_readback_degraded"
|
||||
]
|
||||
assert envelope["source_refs"]["fingerprints"] == [
|
||||
"ai_automation_alert_card:wazuh_dashboard_api_readback_degraded:siem_observability_readback_degraded"
|
||||
]
|
||||
assert "127.0.0.1:55000" not in str(envelope)
|
||||
assert "/var/ossec" not in str(envelope)
|
||||
assert "abcdefghijkl" not in str(envelope)
|
||||
|
||||
Reference in New Issue
Block a user