Merge remote-tracking branch 'gitea/main' into codex/github-private-backup-readback-20260627

This commit is contained in:
Your Name
2026-06-27 11:44:58 +08:00
9 changed files with 635 additions and 23 deletions

View File

@@ -26,6 +26,9 @@ from src.services.iwooos_wazuh_readonly_status import (
from src.services.iwooos_wazuh_live_metadata_gate import (
load_latest_iwooos_wazuh_live_metadata_gate,
)
from src.services.iwooos_wazuh_owner_evidence_preflight import (
load_latest_iwooos_wazuh_owner_evidence_preflight,
)
from src.services.public_redaction import redact_public_lan_topology
@@ -79,6 +82,34 @@ async def get_iwooos_wazuh_live_metadata_gate() -> dict[str, Any]:
) from exc
@router.get(
"/api/v1/iwooos/wazuh-owner-evidence-preflight",
response_model=dict[str, Any],
summary="取得 Wazuh 負責人證據收件預檢讀回",
description=(
"讀取已提交的 Wazuh 代理清單負責人證據收件預檢,回傳公開安全的欄位數、"
"審查檢查、分流、拒收內容計數與 0 / false 邊界。此端點不查 Wazuh、"
"不讀主機、不保存原始載荷、不收機密明文、不啟用主動回應、不改 Nginx / "
"Docker / K8s / firewall。"
),
)
async def get_iwooos_wazuh_owner_evidence_preflight() -> dict[str, Any]:
"""回傳 Wazuh manager registry 負責人證據收件預檢只讀狀態。"""
try:
payload = await asyncio.to_thread(load_latest_iwooos_wazuh_owner_evidence_preflight)
return redact_public_lan_topology(payload)
except FileNotFoundError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(exc),
) from exc
except (json.JSONDecodeError, ValueError) as exc:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"IwoooS Wazuh 負責人證據預檢無效:{exc}",
) from exc
@router.get(
"/api/v1/iwooos/runtime-security-readback",
response_model=dict[str, Any],

View File

@@ -21,6 +21,7 @@ _SNAPSHOT_FILES = {
"wazuh_coverage": "wazuh-managed-host-coverage-gate.snapshot.json",
"wazuh_runtime": "wazuh-agent-visibility-runtime-gate.snapshot.json",
"wazuh_live_metadata_gate": "wazuh-readonly-live-metadata-env-gate.snapshot.json",
"wazuh_owner_evidence_preflight": "wazuh-agent-visibility-owner-evidence-preflight.snapshot.json",
"kali_status": "kali-integration-status.snapshot.json",
"soc_control": "soc-siem-kali-wazuh-integration-control.snapshot.json",
"alert_readability": "telegram-alert-readability-guard.snapshot.json",
@@ -33,6 +34,7 @@ _EXPECTED_SCHEMAS = {
"wazuh_coverage": "wazuh_managed_host_coverage_gate_v1",
"wazuh_runtime": "wazuh_agent_visibility_runtime_gate_v1",
"wazuh_live_metadata_gate": "iwooos_wazuh_readonly_live_metadata_env_gate_v1",
"wazuh_owner_evidence_preflight": "wazuh_agent_visibility_owner_evidence_preflight_v1",
"kali_status": "kali_integration_status_v1",
"soc_control": "soc_siem_kali_wazuh_integration_control_v1",
"alert_readability": "telegram_alert_readability_guard_v1",
@@ -73,6 +75,7 @@ def load_latest_iwooos_runtime_security_readback(
owner_gap_summary = _summary(snapshots["owner_gap"])
wazuh_summary = _summary(snapshots["wazuh_coverage"])
live_metadata_gate_summary = _summary(snapshots["wazuh_live_metadata_gate"])
owner_evidence_preflight_summary = _summary(snapshots["wazuh_owner_evidence_preflight"])
soc_summary = _summary(snapshots["soc_control"])
alert_summary = _summary(snapshots["alert_readability"])
dispatch_summary = _summary(snapshots["owner_dispatch"])
@@ -95,7 +98,7 @@ def load_latest_iwooos_runtime_security_readback(
"source_refs": source_refs,
"summary": {
"source_snapshot_count": len(source_refs),
"p0_lane_count": 8,
"p0_lane_count": 9,
"control_plane_visibility_percent": _average_percent(
soc_summary.get("coverage_percent_after_soc_integration_control"),
intrusion_summary.get("coverage_percent_after_prevention_control"),
@@ -139,6 +142,36 @@ def load_latest_iwooos_runtime_security_readback(
"wazuh_live_metadata_gate_live_query_authorized_count": _int(
live_metadata_gate_summary.get("wazuh_api_live_query_authorized_count")
),
"wazuh_owner_evidence_required_field_count": _int(
owner_evidence_preflight_summary.get("required_field_count")
),
"wazuh_owner_evidence_reviewer_check_count": _int(
owner_evidence_preflight_summary.get("reviewer_check_count")
),
"wazuh_owner_evidence_outcome_lane_count": _int(
owner_evidence_preflight_summary.get("outcome_lane_count")
),
"wazuh_owner_evidence_forbidden_payload_count": _int(
owner_evidence_preflight_summary.get("forbidden_payload_count")
),
"wazuh_owner_evidence_expected_alias_count": _int(
owner_evidence_preflight_summary.get("expected_scope_alias_count")
),
"wazuh_owner_evidence_registry_export_received_count": _int(
owner_evidence_preflight_summary.get("registry_export_received_count")
),
"wazuh_owner_evidence_registry_export_accepted_count": _int(
owner_evidence_preflight_summary.get("registry_export_accepted_count")
),
"wazuh_owner_evidence_received_count": _int(
owner_evidence_preflight_summary.get("owner_evidence_received_count")
),
"wazuh_owner_evidence_accepted_count": _int(
owner_evidence_preflight_summary.get("owner_evidence_accepted_count")
),
"wazuh_owner_evidence_runtime_gate_count": _int(
owner_evidence_preflight_summary.get("runtime_gate_count")
),
"kali_active_scan_authorized_count": _int(soc_summary.get("kali_active_scan_authorized_count")),
"kali_execute_authorized_count": _int(soc_summary.get("kali_execute_authorized_count")),
"kali_finding_envelope_accepted_count": _int(soc_summary.get("kali_finding_envelope_accepted_count")),
@@ -206,6 +239,30 @@ def load_latest_iwooos_runtime_security_readback(
},
["docs/security/wazuh-readonly-live-metadata-env-gate.snapshot.json"],
),
_lane(
"wazuh_owner_evidence_preflight",
snapshots["wazuh_owner_evidence_preflight"].get(
"status",
"owner_evidence_preflight_ready_no_runtime_action",
),
0,
"locked",
"補齊 manager registry 脫敏封包、逐主機矩陣、Dashboard API 狀態與負責人決策",
{
"required_fields": owner_evidence_preflight_summary.get("required_field_count", 0),
"reviewer_checks": owner_evidence_preflight_summary.get("reviewer_check_count", 0),
"outcome_lanes": owner_evidence_preflight_summary.get("outcome_lane_count", 0),
"forbidden_payloads": owner_evidence_preflight_summary.get("forbidden_payload_count", 0),
"owner_received": owner_evidence_preflight_summary.get("owner_evidence_received_count", 0),
"owner_accepted": owner_evidence_preflight_summary.get("owner_evidence_accepted_count", 0),
"registry_export_accepted": owner_evidence_preflight_summary.get(
"registry_export_accepted_count",
0,
),
"runtime_gate": owner_evidence_preflight_summary.get("runtime_gate_count", 0),
},
["docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json"],
),
_lane(
"wazuh_dashboard_api",
"degraded_api_connection_not_green",
@@ -303,6 +360,7 @@ def load_latest_iwooos_runtime_security_readback(
"告警格式合約不代表通知已實發或已取得 receipt",
"Wazuh 正式只讀路由 disabled 或退化時仍是 P0 紅燈",
"Wazuh 即時中繼資料必須先通過負責人、機密中繼資料、唯讀範圍與啟用後讀回",
"Wazuh 負責人證據預檢 ready 不代表已收件、已接受或可啟用 active response",
],
}
@@ -421,6 +479,9 @@ def _require_runtime_boundaries(snapshots: dict[str, dict[str, Any]]) -> None:
"secret_value_collection_allowed_count",
"wazuh_api_live_query_authorized_count",
"wazuh_active_response_authorized_count",
"active_response_authorized_count",
"registry_export_accepted_count",
"owner_evidence_accepted_count",
"post_enable_readback_passed_count",
):
if key in summary and _int(summary.get(key)) != 0:

View File

@@ -0,0 +1,264 @@
"""
IwoooS Wazuh owner evidence preflight readback.
This module only exposes committed, public-safe preflight metadata. It never
queries Wazuh, never reads secret values, and never authorizes active response,
host writes, scans, restarts, reloads, or gateway changes.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from src.services.snapshot_paths import default_security_dir
_DEFAULT_SECURITY_DIR = default_security_dir(Path(__file__))
_SNAPSHOT_FILE = "wazuh-agent-visibility-owner-evidence-preflight.snapshot.json"
_EXPECTED_SCHEMA = "wazuh_agent_visibility_owner_evidence_preflight_v1"
_REQUIRED_FALSE_BOUNDARIES = {
"agent_identity_public_display_allowed",
"host_write_authorized",
"internal_ip_public_display_allowed",
"raw_wazuh_payload_storage_allowed",
"runtime_execution_authorized",
"secret_value_collection_allowed",
"wazuh_active_response_authorized",
"wazuh_api_live_query_authorized",
}
def load_latest_iwooos_wazuh_owner_evidence_preflight(
security_dir: Path | None = None,
) -> dict[str, Any]:
"""Load the committed Wazuh owner evidence preflight as a public-safe payload."""
directory = security_dir or _DEFAULT_SECURITY_DIR
snapshot = _load_snapshot(directory)
_require_boundaries(snapshot)
summary = _summary(snapshot)
contract = snapshot.get("registry_export_contract")
contract = contract if isinstance(contract, dict) else {}
merged_summary = {
"required_field_count": _int(summary.get("required_field_count")),
"reviewer_check_count": _int(summary.get("reviewer_check_count")),
"outcome_lane_count": _int(summary.get("outcome_lane_count")),
"forbidden_payload_count": _int(summary.get("forbidden_payload_count")),
"expected_scope_alias_count": _int(summary.get("expected_scope_alias_count")),
"per_host_required_field_count": _int(summary.get("per_host_required_field_count")),
"allowed_collection_method_count": _len(contract.get("allowed_collection_methods")),
"registry_export_received_count": _int(summary.get("registry_export_received_count")),
"registry_export_accepted_count": _int(summary.get("registry_export_accepted_count")),
"owner_evidence_received_count": _int(summary.get("owner_evidence_received_count")),
"owner_evidence_accepted_count": _int(summary.get("owner_evidence_accepted_count")),
"owner_evidence_rejected_count": _int(summary.get("owner_evidence_rejected_count")),
"owner_evidence_quarantined_count": _int(summary.get("owner_evidence_quarantined_count")),
"runtime_gate_count": _int(summary.get("runtime_gate_count")),
"wazuh_api_live_query_authorized_count": 0,
"active_response_authorized_count": _int(summary.get("active_response_authorized_count")),
"host_write_authorized_count": _int(summary.get("host_write_authorized_count")),
"secret_value_collection_allowed_count": _int(summary.get("secret_value_collection_allowed_count")),
}
return {
"schema_version": "iwooos_wazuh_owner_evidence_preflight_readback_v1",
"status": snapshot.get("status", "owner_evidence_preflight_ready_no_runtime_action"),
"mode": "committed_snapshot_readback_redacted_metadata_only",
"source_refs": [
f"docs/security/{_SNAPSHOT_FILE}",
"scripts/security/wazuh-agent-visibility-owner-evidence-preflight.py",
],
"summary": merged_summary,
"items": _items(merged_summary),
"boundary_markers": _boundary_markers(merged_summary),
"boundaries": {
"wazuh_api_live_query_authorized": False,
"wazuh_active_response_authorized": False,
"host_write_authorized": False,
"secret_value_collection_allowed": False,
"raw_wazuh_payload_storage_allowed": False,
"agent_identity_public_display_allowed": False,
"internal_ip_public_display_allowed": False,
"runtime_execution_authorized": False,
"not_authorization": True,
},
"no_false_green_rules": [
"負責人證據預檢 ready 不代表已收件或已接受",
"Wazuh 儀表板可見不是 manager registry counts 已驗收",
"Dashboard index pattern 三綠勾不可替代 API connection、API version 或 manager registry",
"agent service active、TCP 連線或舊截圖不可替代逐主機 registry matrix",
"收件封包若夾帶原始紀錄、內網識別、agent 原名或機密,必須隔離,不得渲染到前台",
"active response、host write、firewall、Nginx、Docker、K8s 或 secret 變更一律不是這個預檢授權",
],
}
def _load_snapshot(directory: Path) -> dict[str, Any]:
path = directory / _SNAPSHOT_FILE
if not path.is_file():
raise FileNotFoundError(f"{path}: Wazuh 負責人證據預檢快照不存在")
with path.open(encoding="utf-8") as handle:
payload = json.load(handle)
if not isinstance(payload, dict):
raise ValueError(f"{path}: expected JSON object")
if payload.get("schema_version") != _EXPECTED_SCHEMA:
raise ValueError(f"{path}: expected schema_version={_EXPECTED_SCHEMA}")
return payload
def _summary(payload: dict[str, Any]) -> dict[str, Any]:
summary = payload.get("summary")
return summary if isinstance(summary, dict) else {}
def _int(value: Any) -> int:
return value if isinstance(value, int) else 0
def _len(value: Any) -> int:
return len(value) if isinstance(value, list) else 0
def _items(summary: dict[str, int]) -> list[dict[str, Any]]:
return [
_item(
"scope_aliases",
"EV-0",
"scope_aliases_ready",
"warn",
{
"expected_scope_aliases": summary["expected_scope_alias_count"],
"allowed_collection_methods": summary["allowed_collection_method_count"],
},
),
_item(
"registry_counts",
"EV-1",
"waiting_redacted_counts",
"warn",
{
"registry_export_received": summary["registry_export_received_count"],
"registry_export_accepted": summary["registry_export_accepted_count"],
},
),
_item(
"per_host_matrix",
"EV-2",
"waiting_per_host_matrix",
"warn",
{"per_host_required_fields": summary["per_host_required_field_count"]},
),
_item(
"time_window",
"EV-3",
"waiting_time_window",
"warn",
{"owner_received": summary["owner_evidence_received_count"]},
),
_item(
"health_refs",
"EV-4",
"waiting_health_refs",
"warn",
{"reviewer_checks": summary["reviewer_check_count"]},
),
_item(
"redaction",
"EV-5",
"reject_sensitive_payloads",
"locked",
{
"forbidden_payloads": summary["forbidden_payload_count"],
"quarantined": summary["owner_evidence_quarantined_count"],
},
),
_item(
"owner_decision",
"EV-6",
"waiting_owner_decision",
"warn",
{
"owner_received": summary["owner_evidence_received_count"],
"owner_accepted": summary["owner_evidence_accepted_count"],
},
),
_item(
"runtime_boundary",
"EV-7",
"runtime_closed",
"locked",
{
"runtime_gate": summary["runtime_gate_count"],
"live_query": summary["wazuh_api_live_query_authorized_count"],
"active_response": summary["active_response_authorized_count"],
"host_write": summary["host_write_authorized_count"],
},
),
]
def _item(
item_id: str,
check: str,
state_key: str,
tone: str,
metrics: dict[str, int],
) -> dict[str, Any]:
return {
"item_id": item_id,
"check": check,
"state_key": state_key,
"tone": tone,
"metrics": metrics,
}
def _boundary_markers(summary: dict[str, int]) -> list[str]:
return [
f"必要欄位={summary['required_field_count']}",
f"審查檢查={summary['reviewer_check_count']}",
f"結果分流={summary['outcome_lane_count']}",
f"拒收敏感類型={summary['forbidden_payload_count']}",
f"公開節點別名={summary['expected_scope_alias_count']}",
f"逐主機矩陣欄位={summary['per_host_required_field_count']}",
f"允許收集方式={summary['allowed_collection_method_count']}",
f"registry export 已收件={summary['registry_export_received_count']}",
f"registry export 已接受={summary['registry_export_accepted_count']}",
f"負責人證據已收件={summary['owner_evidence_received_count']}",
f"負責人證據已接受={summary['owner_evidence_accepted_count']}",
f"負責人證據已隔離={summary['owner_evidence_quarantined_count']}",
f"執行期閘門={summary['runtime_gate_count']}",
f"Wazuh 即時查詢={summary['wazuh_api_live_query_authorized_count']}",
f"Wazuh 主動回應={summary['active_response_authorized_count']}",
f"主機寫入={summary['host_write_authorized_count']}",
f"機密明文收集={summary['secret_value_collection_allowed_count']}",
"原始 Wazuh 載荷保存=false",
"agent 身分前台顯示=false",
"內網識別前台顯示=false",
"不是執行授權=true",
]
def _require_boundaries(payload: dict[str, Any]) -> None:
summary = _summary(payload)
for key in (
"registry_export_accepted_count",
"owner_evidence_accepted_count",
"runtime_gate_count",
"active_response_authorized_count",
"host_write_authorized_count",
"secret_value_collection_allowed_count",
):
if _int(summary.get(key)) != 0:
raise ValueError(f"Wazuh 負責人證據預檢 summary.{key} 必須維持 0")
boundaries = payload.get("execution_boundaries")
if not isinstance(boundaries, dict):
raise ValueError("Wazuh 負責人證據預檢 execution_boundaries 缺失")
for key in _REQUIRED_FALSE_BOUNDARIES:
if boundaries.get(key) is not False:
raise ValueError(f"Wazuh 負責人證據預檢 execution_boundaries.{key} 必須維持 false")
if boundaries.get("not_authorization") is not True:
raise ValueError("Wazuh 負責人證據預檢 not_authorization 必須維持 true")

View File

@@ -20,8 +20,8 @@ def test_iwooos_runtime_security_readback_preserves_zero_runtime_gates() -> None
assert payload["schema_version"] == "iwooos_runtime_security_readback_v1"
assert payload["status"] == "blocked_waiting_owner_evidence_and_runtime_gates"
assert payload["summary"]["source_snapshot_count"] == 9
assert payload["summary"]["p0_lane_count"] == 8
assert payload["summary"]["source_snapshot_count"] == 10
assert payload["summary"]["p0_lane_count"] == 9
assert payload["summary"]["runtime_gate_count"] == 0
assert payload["summary"]["owner_response_received_count"] == 0
assert payload["summary"]["owner_response_accepted_count"] == 0
@@ -36,6 +36,16 @@ def test_iwooos_runtime_security_readback_preserves_zero_runtime_gates() -> None
assert payload["summary"]["wazuh_live_metadata_gate_readonly_scope_accepted_count"] == 0
assert payload["summary"]["wazuh_live_metadata_gate_post_enable_readback_count"] == 0
assert payload["summary"]["wazuh_live_metadata_gate_live_query_authorized_count"] == 0
assert payload["summary"]["wazuh_owner_evidence_required_field_count"] == 28
assert payload["summary"]["wazuh_owner_evidence_reviewer_check_count"] == 15
assert payload["summary"]["wazuh_owner_evidence_outcome_lane_count"] == 8
assert payload["summary"]["wazuh_owner_evidence_forbidden_payload_count"] == 22
assert payload["summary"]["wazuh_owner_evidence_expected_alias_count"] == 6
assert payload["summary"]["wazuh_owner_evidence_registry_export_received_count"] == 0
assert payload["summary"]["wazuh_owner_evidence_registry_export_accepted_count"] == 0
assert payload["summary"]["wazuh_owner_evidence_received_count"] == 0
assert payload["summary"]["wazuh_owner_evidence_accepted_count"] == 0
assert payload["summary"]["wazuh_owner_evidence_runtime_gate_count"] == 0
assert payload["summary"]["kali_active_scan_authorized_count"] == 0
assert payload["summary"]["kali_execute_authorized_count"] == 0
assert payload["summary"]["alert_receipt_runtime_send_count"] == 0
@@ -53,6 +63,7 @@ def test_iwooos_runtime_security_readback_lanes_are_candidate_only() -> None:
"wazuh_registry",
"wazuh_live_route",
"wazuh_live_metadata_gate",
"wazuh_owner_evidence_preflight",
"wazuh_dashboard_api",
"kali_intake",
"alert_readability",
@@ -66,6 +77,7 @@ def test_iwooos_runtime_security_readback_lanes_are_candidate_only() -> None:
assert all(lane["lane_id"] != "wazuh_registry" or lane["completion_percent"] == 0 for lane in payload["lanes"])
assert all(lane["lane_id"] != "wazuh_live_route" or lane["metrics"]["route_degraded"] == 1 for lane in payload["lanes"])
assert all(lane["lane_id"] != "wazuh_live_metadata_gate" or lane["completion_percent"] == 0 for lane in payload["lanes"])
assert all(lane["lane_id"] != "wazuh_owner_evidence_preflight" or lane["metrics"]["owner_accepted"] == 0 for lane in payload["lanes"])
def test_iwooos_runtime_security_readback_api_is_public_safe(monkeypatch) -> None:
@@ -85,6 +97,8 @@ def test_iwooos_runtime_security_readback_api_is_public_safe(monkeypatch) -> Non
assert data["summary"]["wazuh_live_route_degraded_count"] == 1
assert data["summary"]["wazuh_live_metadata_available_count"] == 0
assert data["summary"]["wazuh_live_metadata_gate_live_query_authorized_count"] == 0
assert data["summary"]["wazuh_owner_evidence_accepted_count"] == 0
assert data["summary"]["wazuh_owner_evidence_runtime_gate_count"] == 0
assert data["boundaries"]["secret_value_collection_allowed"] is False
assert "192.168.0." not in response.text
assert "工作視窗" not in response.text
@@ -169,3 +183,44 @@ def test_iwooos_wazuh_live_metadata_gate_api_is_public_safe(monkeypatch) -> None
assert "工作視窗" not in response.text
assert "批准!繼續" not in response.text
assert "WAZUH_API_PASSWORD" not in response.text
def test_iwooos_wazuh_owner_evidence_preflight_api_is_public_safe(monkeypatch) -> None:
monkeypatch.delenv("IWOOOS_WAZUH_READONLY_ENABLED", raising=False)
monkeypatch.delenv("WAZUH_API_BASE_URL", raising=False)
monkeypatch.delenv("WAZUH_API_USERNAME", raising=False)
monkeypatch.delenv("WAZUH_API_PASSWORD", raising=False)
response = _client().get("/api/v1/iwooos/wazuh-owner-evidence-preflight")
assert response.status_code == 200
data = response.json()
assert data["schema_version"] == "iwooos_wazuh_owner_evidence_preflight_readback_v1"
assert data["status"] == "owner_evidence_preflight_ready_no_runtime_action"
assert data["summary"]["required_field_count"] == 28
assert data["summary"]["reviewer_check_count"] == 15
assert data["summary"]["outcome_lane_count"] == 8
assert data["summary"]["forbidden_payload_count"] == 22
assert data["summary"]["expected_scope_alias_count"] == 6
assert data["summary"]["per_host_required_field_count"] == 9
assert data["summary"]["registry_export_received_count"] == 0
assert data["summary"]["registry_export_accepted_count"] == 0
assert data["summary"]["owner_evidence_received_count"] == 0
assert data["summary"]["owner_evidence_accepted_count"] == 0
assert data["summary"]["runtime_gate_count"] == 0
assert data["summary"]["wazuh_api_live_query_authorized_count"] == 0
assert data["summary"]["active_response_authorized_count"] == 0
assert data["summary"]["host_write_authorized_count"] == 0
assert data["summary"]["secret_value_collection_allowed_count"] == 0
assert data["boundaries"]["wazuh_api_live_query_authorized"] is False
assert data["boundaries"]["wazuh_active_response_authorized"] is False
assert data["boundaries"]["host_write_authorized"] is False
assert data["boundaries"]["runtime_execution_authorized"] is False
assert data["boundaries"]["not_authorization"] is True
assert len(data["items"]) == 8
assert any(marker == "必要欄位=28" for marker in data["boundary_markers"])
assert any(rule.startswith("負責人證據預檢 ready") for rule in data["no_false_green_rules"])
assert "192.168.0." not in response.text
assert "工作視窗" not in response.text
assert "批准!繼續" not in response.text
assert "WAZUH_API_PASSWORD" not in response.text

View File

@@ -20300,7 +20300,7 @@
},
"runtimeSecurityReadback": {
"eyebrow": "IwoooS Runtime 資安讀回",
"title": "條 P0 資安線先接到同一張讀回板",
"title": "條 P0 資安線先接到同一張讀回板",
"subtitle": "這張板讀取後端彙整的只讀快照,並附上 Wazuh 正式只讀路由的公開安全 aggregate 讀回;它不保存 raw Wazuh payload、不啟動掃描、不送訊息、不改主機。",
"statusLabel": "讀回狀態",
"statusDetail": "讀回成功只代表 IwoooS 能看見目前的證據邊界runtime 寫入、主動回應、掃描、重啟、Nginx reload、workflow 修改與機密操作仍全部關閉。",
@@ -20361,6 +20361,10 @@
"title": "Wazuh 即時中繼資料閘門",
"body": "正式路由讀回後仍必須補負責人回覆、機密來源中繼資料、管理節點健康、唯讀範圍與啟用後讀回;即時查詢授權維持 0。"
},
"wazuh_owner_evidence_preflight": {
"title": "Wazuh 負責人證據預檢",
"body": "把 manager registry 脫敏封包、逐主機矩陣、Dashboard API 狀態、拒收敏感類型與負責人決策先變成可驗收格式;收件、接受與執行期仍為 0。"
},
"wazuh_dashboard_api": {
"title": "Wazuh Dashboard API",
"body": "API connection / API version 還要補 readbackindex pattern 通過不能宣稱 Wazuh 全綠。"
@@ -20553,8 +20557,24 @@
"subtitle": "這張卡把 Wazuh 管理器代理清單真相的必要欄位、審查檢查、拒收分流與禁止內容公開給操作員;目前尚未收到或接受任何負責人證據,也不授權主機操作。",
"checkLabel": "檢核",
"stateLabel": "狀態",
"boundaryTitle": "Owner evidence 收件邊界",
"loadingBoundary": "正在讀取負責人證據預檢",
"boundaryTitle": "負責人證據收件邊界",
"boundaryIntro": "以下鍵值固定:收件格式已準備好,且新增 6 個公開節點別名與逐主機匯出矩陣要求registry export、已收件、已接受與執行閘門仍為 0。任何原始紀錄、未脫敏截圖、內網位址、代理原名或機密都必須拒收或隔離。",
"status": {
"loading": "正在讀取 Wazuh 負責人證據預檢",
"failed": "Wazuh 負責人證據預檢尚未部署或讀取失敗",
"ready": "Wazuh 負責人證據預檢已讀回,但收件、接受與執行期仍為 0"
},
"states": {
"scope_aliases_ready": "公開別名已定義",
"waiting_redacted_counts": "待脫敏計數",
"waiting_per_host_matrix": "待逐主機矩陣",
"waiting_time_window": "待時間窗",
"waiting_health_refs": "待健康參照",
"reject_sensitive_payloads": "拒收敏感內容",
"waiting_owner_decision": "待負責人決策",
"runtime_closed": "執行期關閉"
},
"summary": {
"fields": {
"label": "必要欄位",

View File

@@ -20300,7 +20300,7 @@
},
"runtimeSecurityReadback": {
"eyebrow": "IwoooS Runtime 資安讀回",
"title": "條 P0 資安線先接到同一張讀回板",
"title": "條 P0 資安線先接到同一張讀回板",
"subtitle": "這張板讀取後端彙整的只讀快照,並附上 Wazuh 正式只讀路由的公開安全 aggregate 讀回;它不保存 raw Wazuh payload、不啟動掃描、不送訊息、不改主機。",
"statusLabel": "讀回狀態",
"statusDetail": "讀回成功只代表 IwoooS 能看見目前的證據邊界runtime 寫入、主動回應、掃描、重啟、Nginx reload、workflow 修改與機密操作仍全部關閉。",
@@ -20361,6 +20361,10 @@
"title": "Wazuh 即時中繼資料閘門",
"body": "正式路由讀回後仍必須補負責人回覆、機密來源中繼資料、管理節點健康、唯讀範圍與啟用後讀回;即時查詢授權維持 0。"
},
"wazuh_owner_evidence_preflight": {
"title": "Wazuh 負責人證據預檢",
"body": "把 manager registry 脫敏封包、逐主機矩陣、Dashboard API 狀態、拒收敏感類型與負責人決策先變成可驗收格式;收件、接受與執行期仍為 0。"
},
"wazuh_dashboard_api": {
"title": "Wazuh Dashboard API",
"body": "API connection / API version 還要補 readbackindex pattern 通過不能宣稱 Wazuh 全綠。"
@@ -20553,8 +20557,24 @@
"subtitle": "這張卡把 Wazuh 管理器代理清單真相的必要欄位、審查檢查、拒收分流與禁止內容公開給操作員;目前尚未收到或接受任何負責人證據,也不授權主機操作。",
"checkLabel": "檢核",
"stateLabel": "狀態",
"boundaryTitle": "Owner evidence 收件邊界",
"loadingBoundary": "正在讀取負責人證據預檢",
"boundaryTitle": "負責人證據收件邊界",
"boundaryIntro": "以下鍵值固定:收件格式已準備好,且新增 6 個公開節點別名與逐主機匯出矩陣要求registry export、已收件、已接受與執行閘門仍為 0。任何原始紀錄、未脫敏截圖、內網位址、代理原名或機密都必須拒收或隔離。",
"status": {
"loading": "正在讀取 Wazuh 負責人證據預檢",
"failed": "Wazuh 負責人證據預檢尚未部署或讀取失敗",
"ready": "Wazuh 負責人證據預檢已讀回,但收件、接受與執行期仍為 0"
},
"states": {
"scope_aliases_ready": "公開別名已定義",
"waiting_redacted_counts": "待脫敏計數",
"waiting_per_host_matrix": "待逐主機矩陣",
"waiting_time_window": "待時間窗",
"waiting_health_refs": "待健康參照",
"reject_sensitive_payloads": "拒收敏感內容",
"waiting_owner_decision": "待負責人決策",
"runtime_closed": "執行期關閉"
},
"summary": {
"fields": {
"label": "必要欄位",

View File

@@ -40,6 +40,8 @@ import {
type IwoooSSecurityControlCoverageResponse,
type IwoooSWazuhLiveMetadataGateItem,
type IwoooSWazuhLiveMetadataGateResponse,
type IwoooSWazuhOwnerEvidencePreflightItem,
type IwoooSWazuhOwnerEvidencePreflightResponse,
} from '@/lib/api-client'
type PostureMetric = {
@@ -2365,14 +2367,6 @@ const wazuhLiveMetadataEnvGateBoundaries = [
'not_authorization=true',
] as const
const wazuhOwnerEvidencePreflightSummary = [
{ key: 'fields', value: '23', icon: ClipboardCheck, tone: 'steady' },
{ key: 'aliases', value: '6', icon: Server, tone: 'warn' },
{ key: 'checks', value: '10', icon: ListChecks, tone: 'steady' },
{ key: 'received', value: '0', icon: FileWarning, tone: 'locked' },
{ key: 'accepted', value: '0', icon: Lock, tone: 'locked' },
] as const
const wazuhOwnerEvidencePreflightItems: WazuhOwnerEvidencePreflightItem[] = [
{ key: 'scopeAliases', check: 'EV-0', state: '6 個別名', icon: Server, tone: 'warn' },
{ key: 'registryCounts', check: 'EV-1', state: '待脫敏計數', icon: Server, tone: 'warn' },
@@ -2384,14 +2378,25 @@ const wazuhOwnerEvidencePreflightItems: WazuhOwnerEvidencePreflightItem[] = [
{ key: 'runtimeBoundary', check: 'EV-7', state: '不開執行', icon: Lock, tone: 'locked' },
] as const
const wazuhOwnerEvidencePreflightItemKeyById: Record<IwoooSWazuhOwnerEvidencePreflightItem['item_id'], WazuhOwnerEvidencePreflightItem['key']> = {
scope_aliases: 'scopeAliases',
registry_counts: 'registryCounts',
per_host_matrix: 'perHostMatrix',
time_window: 'timeWindow',
health_refs: 'healthRefs',
redaction: 'redaction',
owner_decision: 'ownerDecision',
runtime_boundary: 'runtimeBoundary',
}
const wazuhOwnerEvidencePreflightBoundaries = [
'wazuh_agent_visibility_owner_evidence_preflight_visible=true',
'wazuh_agent_visibility_owner_evidence_required_field_count=23',
'wazuh_agent_visibility_owner_evidence_reviewer_check_count=10',
'wazuh_agent_visibility_owner_evidence_required_field_count=28',
'wazuh_agent_visibility_owner_evidence_reviewer_check_count=15',
'wazuh_agent_visibility_owner_evidence_expected_scope_alias_count=6',
'wazuh_agent_visibility_owner_evidence_per_host_required_field_count=9',
'wazuh_agent_visibility_owner_evidence_outcome_lane_count=5',
'wazuh_agent_visibility_owner_evidence_forbidden_payload_count=18',
'wazuh_agent_visibility_owner_evidence_outcome_lane_count=8',
'wazuh_agent_visibility_owner_evidence_forbidden_payload_count=22',
'wazuh_agent_visibility_owner_evidence_registry_export_received_count=0',
'wazuh_agent_visibility_owner_evidence_registry_export_accepted_count=0',
'wazuh_agent_visibility_owner_evidence_received_count=0',
@@ -9115,6 +9120,92 @@ function IwoooSWazuhLiveMetadataEnvGateBoard() {
function IwoooSWazuhOwnerEvidencePreflightBoard() {
const t = useTranslations('iwooos.wazuhOwnerEvidencePreflight')
const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const }
const [data, setData] = useState<IwoooSWazuhOwnerEvidencePreflightResponse | null>(null)
const [loading, setLoading] = useState(true)
const [failed, setFailed] = useState(false)
useEffect(() => {
let mounted = true
async function loadPreflight() {
setLoading(true)
setFailed(false)
try {
const payload = await apiClient.getIwoooSWazuhOwnerEvidencePreflight()
if (mounted) {
setData(payload)
}
} catch {
if (mounted) {
setData(null)
setFailed(true)
}
} finally {
if (mounted) {
setLoading(false)
}
}
}
loadPreflight()
return () => {
mounted = false
}
}, [])
const summary = data?.summary
const summaryItems = [
{
key: 'fields',
value: summary ? String(summary.required_field_count) : loading ? '...' : '28',
icon: ClipboardCheck,
tone: 'steady',
},
{
key: 'aliases',
value: summary ? String(summary.expected_scope_alias_count) : loading ? '...' : '6',
icon: Server,
tone: 'warn',
},
{
key: 'checks',
value: summary ? String(summary.reviewer_check_count) : loading ? '...' : '15',
icon: ListChecks,
tone: 'steady',
},
{
key: 'received',
value: summary ? String(summary.owner_evidence_received_count) : loading ? '...' : '0',
icon: FileWarning,
tone: 'locked',
},
{
key: 'accepted',
value: summary ? String(summary.owner_evidence_accepted_count) : loading ? '...' : '0',
icon: Lock,
tone: 'locked',
},
] as const
const preflightItems = data?.items?.length
? data.items.map(item => {
const key = wazuhOwnerEvidencePreflightItemKeyById[item.item_id]
const fallback = wazuhOwnerEvidencePreflightItems.find(candidate => candidate.key === key)
return {
key,
check: item.check,
state: t(`states.${item.state_key}` as never),
icon: fallback?.icon ?? FileWarning,
tone: item.tone,
}
})
: wazuhOwnerEvidencePreflightItems
const boundaryMarkers = data?.boundary_markers?.length
? data.boundary_markers
: loading
? [t('loadingBoundary')]
: wazuhOwnerEvidencePreflightBoundaries
const statusText = loading ? t('status.loading') : failed ? t('status.failed') : t('status.ready')
const statusTone: 'steady' | 'warn' | 'locked' = loading || failed ? 'warn' : 'locked'
return (
<section
@@ -9132,10 +9223,14 @@ function IwoooSWazuhOwnerEvidencePreflightBoard() {
<p style={{ fontSize: 12, color: '#315a74', margin: '6px 0 0', lineHeight: 1.55, ...textWrap }}>
{t('subtitle')}
</p>
<div style={{ marginTop: 8, display: 'inline-flex', alignItems: 'center', gap: 8, color: toneColors[statusTone], fontSize: 12, fontWeight: 700, ...textWrap }}>
<ToneDot tone={statusTone} />
{statusText}
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(126px, 1fr))', gap: 8 }}>
{wazuhOwnerEvidencePreflightSummary.map(item => {
{summaryItems.map(item => {
const Icon = item.icon
return (
<div key={item.key} style={{ border: '0.5px solid #c8dbea', borderRadius: 8, padding: 12, background: '#fff' }}>
@@ -9163,7 +9258,7 @@ function IwoooSWazuhOwnerEvidencePreflightBoard() {
gap: 10,
}}
>
{wazuhOwnerEvidencePreflightItems.map(item => {
{preflightItems.map(item => {
const Icon = item.icon
return (
<div
@@ -9219,7 +9314,7 @@ function IwoooSWazuhOwnerEvidencePreflightBoard() {
{t('boundaryIntro')}
</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(230px, 1fr))', gap: 6 }}>
{wazuhOwnerEvidencePreflightBoundaries.map(item => (
{boundaryMarkers.map(item => (
<code
key={item}
style={{

View File

@@ -102,6 +102,7 @@ export interface IwoooSRuntimeSecurityReadbackLane {
| 'wazuh_registry'
| 'wazuh_live_route'
| 'wazuh_live_metadata_gate'
| 'wazuh_owner_evidence_preflight'
| 'wazuh_dashboard_api'
| 'kali_intake'
| 'alert_readability'
@@ -148,6 +149,16 @@ export interface IwoooSRuntimeSecurityReadbackResponse {
wazuh_live_metadata_gate_readonly_scope_accepted_count: number
wazuh_live_metadata_gate_post_enable_readback_count: number
wazuh_live_metadata_gate_live_query_authorized_count: number
wazuh_owner_evidence_required_field_count: number
wazuh_owner_evidence_reviewer_check_count: number
wazuh_owner_evidence_outcome_lane_count: number
wazuh_owner_evidence_forbidden_payload_count: number
wazuh_owner_evidence_expected_alias_count: number
wazuh_owner_evidence_registry_export_received_count: number
wazuh_owner_evidence_registry_export_accepted_count: number
wazuh_owner_evidence_received_count: number
wazuh_owner_evidence_accepted_count: number
wazuh_owner_evidence_runtime_gate_count: number
kali_active_scan_authorized_count: number
kali_execute_authorized_count: number
kali_finding_envelope_accepted_count: number
@@ -210,6 +221,53 @@ export interface IwoooSWazuhLiveMetadataGateResponse {
no_false_green_rules: string[]
}
export interface IwoooSWazuhOwnerEvidencePreflightItem {
item_id:
| 'scope_aliases'
| 'registry_counts'
| 'per_host_matrix'
| 'time_window'
| 'health_refs'
| 'redaction'
| 'owner_decision'
| 'runtime_boundary'
check: string
state_key: string
tone: IwoooSRuntimeSecurityReadbackTone
metrics: Record<string, number>
}
export interface IwoooSWazuhOwnerEvidencePreflightResponse {
schema_version: 'iwooos_wazuh_owner_evidence_preflight_readback_v1'
status: string
mode: string
source_refs: string[]
summary: {
required_field_count: number
reviewer_check_count: number
outcome_lane_count: number
forbidden_payload_count: number
expected_scope_alias_count: number
per_host_required_field_count: number
allowed_collection_method_count: number
registry_export_received_count: number
registry_export_accepted_count: number
owner_evidence_received_count: number
owner_evidence_accepted_count: number
owner_evidence_rejected_count: number
owner_evidence_quarantined_count: number
runtime_gate_count: number
wazuh_api_live_query_authorized_count: number
active_response_authorized_count: number
host_write_authorized_count: number
secret_value_collection_allowed_count: number
}
items: IwoooSWazuhOwnerEvidencePreflightItem[]
boundary_markers: string[]
boundaries: Record<string, boolean>
no_false_green_rules: string[]
}
export interface IwoooSSecurityControlCoverageDomain {
domain_id:
| 'high_value_asset_control'
@@ -320,6 +378,11 @@ export const apiClient = {
return handleResponse<IwoooSWazuhLiveMetadataGateResponse>(res)
},
async getIwoooSWazuhOwnerEvidencePreflight() {
const res = await fetch(`${API_BASE_URL}/iwooos/wazuh-owner-evidence-preflight`, { cache: 'no-store' })
return handleResponse<IwoooSWazuhOwnerEvidencePreflightResponse>(res)
},
async getIwoooSSecurityControlCoverage() {
const res = await fetch(`${API_BASE_URL}/iwooos/security-control-coverage`, { cache: 'no-store' })
return handleResponse<IwoooSSecurityControlCoverageResponse>(res)

View File

@@ -29513,9 +29513,12 @@ def validate(root: Path) -> None:
for expected in [
"iwooos-wazuh-owner-evidence-preflight-board",
"wazuhOwnerEvidencePreflight",
"wazuh_agent_visibility_owner_evidence_required_field_count=23",
"wazuh_agent_visibility_owner_evidence_required_field_count=28",
"wazuh_agent_visibility_owner_evidence_reviewer_check_count=15",
"wazuh_agent_visibility_owner_evidence_expected_scope_alias_count=6",
"wazuh_agent_visibility_owner_evidence_per_host_required_field_count=9",
"wazuh_agent_visibility_owner_evidence_outcome_lane_count=8",
"wazuh_agent_visibility_owner_evidence_forbidden_payload_count=22",
"wazuh_agent_visibility_owner_evidence_registry_export_received_count=0",
"wazuh_agent_visibility_owner_evidence_registry_export_accepted_count=0",
"wazuh_agent_visibility_owner_evidence_runtime_gate_count=0",