feat(iwooos): add wazuh runtime owner review readback
Some checks failed
CD Pipeline / tests (push) Successful in 1m48s
Code Review / ai-code-review (push) Failing after 19s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-28 11:28:31 +08:00
parent fde68fb843
commit 119267256a
9 changed files with 1462 additions and 7 deletions

View File

@@ -53,6 +53,12 @@ from src.services.iwooos_wazuh_runtime_controlled_apply_preflight import (
from src.services.iwooos_wazuh_runtime_controlled_apply_preflight import (
validate_iwooos_wazuh_runtime_controlled_apply_packet as validate_wazuh_runtime_controlled_apply_packet_payload,
)
from src.services.iwooos_wazuh_runtime_gate_owner_review_readback import (
load_latest_iwooos_wazuh_runtime_gate_owner_review_readback,
)
from src.services.iwooos_wazuh_runtime_gate_owner_review_readback import (
validate_iwooos_wazuh_runtime_gate_owner_review_packet as validate_wazuh_runtime_gate_owner_review_packet_payload,
)
from src.services.public_redaction import redact_public_lan_topology
router = APIRouter(tags=["IwoooS Security"])
@@ -327,6 +333,71 @@ async def validate_iwooos_wazuh_runtime_controlled_apply_packet(
) from exc
@router.get(
"/api/v1/iwooos/wazuh-runtime-gate-owner-review-readback",
response_model=dict[str, Any],
summary="取得 Wazuh runtime gate owner-review 只讀讀回",
description=(
"讀取已提交的 Wazuh runtime gate owner-review readback contract回傳 owner-review "
"decision、target selector、source-of-truth diff、check-mode / dry-run evidence、rollback、"
"post-apply verifier、KM / PlayBook writeback 與 0 / false 邊界。此端點不查 Wazuh API、"
"不讀主機、不重新註冊 agent、不重啟服務、不保存機密、不啟用主動回應、不改 Nginx / "
"Docker / K8s / firewall。"
),
)
async def get_iwooos_wazuh_runtime_gate_owner_review_readback() -> dict[str, Any]:
"""回傳 Wazuh runtime gate owner-review 公開安全只讀狀態。"""
try:
payload = await asyncio.to_thread(
load_latest_iwooos_wazuh_runtime_gate_owner_review_readback
)
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 runtime gate owner-review readback 無效:{exc}",
) from exc
@router.post(
"/api/v1/iwooos/wazuh-runtime-gate-owner-review-readback/validate-owner-review-packet",
response_model=dict[str, Any],
summary="驗證 Wazuh runtime gate 脫敏 owner-review packet",
description=(
"針對單次 owner / reviewer 提供的 redacted Wazuh runtime gate owner-review packet "
"進行 no-persist readiness validation回傳 accepted-for-readback / needs supplement / "
"quarantined / rejected runtime action 分流。此端點不保存 payload、不查 Wazuh API、不讀主機、"
"不重新註冊 agent、不重啟服務、不讀或回傳機密明文、不啟用主動回應、不改 Nginx / Docker / "
"K8s / firewall也不更新 runtime gate 總帳。"
),
)
async def validate_iwooos_wazuh_runtime_gate_owner_review_packet(
owner_review_packet: dict[str, Any],
) -> dict[str, Any]:
"""回傳單次 Wazuh runtime gate owner-review packet 的公開安全驗證結果。"""
try:
payload = await asyncio.to_thread(
validate_wazuh_runtime_gate_owner_review_packet_payload,
owner_review_packet,
)
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 runtime gate owner-review packet 驗證器無效:{exc}",
) from exc
@router.get(
"/api/v1/iwooos/runtime-security-readback",
response_model=dict[str, Any],

View File

@@ -23,6 +23,7 @@ _SNAPSHOT_FILES = {
"wazuh_live_metadata_gate": "wazuh-readonly-live-metadata-env-gate.snapshot.json",
"wazuh_owner_evidence_preflight": "wazuh-agent-visibility-owner-evidence-preflight.snapshot.json",
"wazuh_runtime_apply_preflight": "wazuh-runtime-controlled-apply-preflight.snapshot.json",
"wazuh_runtime_owner_review": "wazuh-runtime-gate-owner-review-readback.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",
@@ -37,6 +38,7 @@ _EXPECTED_SCHEMAS = {
"wazuh_live_metadata_gate": "iwooos_wazuh_readonly_live_metadata_env_gate_v1",
"wazuh_owner_evidence_preflight": "wazuh_agent_visibility_owner_evidence_preflight_v1",
"wazuh_runtime_apply_preflight": "wazuh_runtime_controlled_apply_preflight_v1",
"wazuh_runtime_owner_review": "wazuh_runtime_gate_owner_review_readback_v1",
"kali_status": "kali_integration_status_v1",
"soc_control": "soc_siem_kali_wazuh_integration_control_v1",
"alert_readability": "telegram_alert_readability_guard_v1",
@@ -86,6 +88,7 @@ def load_latest_iwooos_runtime_security_readback(
runtime_apply_preflight_summary = _summary(
snapshots["wazuh_runtime_apply_preflight"]
)
runtime_owner_review_summary = _summary(snapshots["wazuh_runtime_owner_review"])
soc_summary = _summary(snapshots["soc_control"])
alert_summary = _summary(snapshots["alert_readability"])
dispatch_summary = _summary(snapshots["owner_dispatch"])
@@ -108,7 +111,7 @@ def load_latest_iwooos_runtime_security_readback(
"source_refs": source_refs,
"summary": {
"source_snapshot_count": len(source_refs),
"p0_lane_count": 10,
"p0_lane_count": 11,
"control_plane_visibility_percent": _average_percent(
soc_summary.get("coverage_percent_after_soc_integration_control"),
intrusion_summary.get("coverage_percent_after_prevention_control"),
@@ -234,6 +237,41 @@ def load_latest_iwooos_runtime_security_readback(
"wazuh_runtime_apply_runtime_gate_count": _int(
runtime_apply_preflight_summary.get("runtime_gate_count")
),
"wazuh_runtime_owner_review_target_selector_count": _int(
runtime_owner_review_summary.get("target_selector_count")
),
"wazuh_runtime_owner_review_source_diff_count": _int(
runtime_owner_review_summary.get("source_of_truth_diff_count")
),
"wazuh_runtime_owner_review_check_mode_plan_count": _int(
runtime_owner_review_summary.get("check_mode_plan_count")
),
"wazuh_runtime_owner_review_dry_run_evidence_count": _int(
runtime_owner_review_summary.get("dry_run_evidence_count")
),
"wazuh_runtime_owner_review_rollback_plan_count": _int(
runtime_owner_review_summary.get("rollback_plan_count")
),
"wazuh_runtime_owner_review_post_apply_verifier_count": _int(
runtime_owner_review_summary.get("post_apply_verifier_count")
),
"wazuh_runtime_owner_review_km_writeback_count": _int(
runtime_owner_review_summary.get("km_playbook_writeback_count")
),
"wazuh_runtime_owner_review_packet_received_count": _int(
runtime_owner_review_summary.get("owner_review_packet_received_count")
),
"wazuh_runtime_owner_review_packet_review_ready_count": _int(
runtime_owner_review_summary.get(
"owner_review_packet_review_ready_count"
)
),
"wazuh_runtime_owner_review_packet_accepted_count": _int(
runtime_owner_review_summary.get("owner_review_packet_accepted_count")
),
"wazuh_runtime_owner_review_runtime_gate_count": _int(
runtime_owner_review_summary.get("runtime_gate_count")
),
"kali_active_scan_authorized_count": _int(
soc_summary.get("kali_active_scan_authorized_count")
),
@@ -433,6 +471,57 @@ def load_latest_iwooos_runtime_security_readback(
"docs/security/wazuh-runtime-controlled-apply-preflight.snapshot.json"
],
),
_lane(
"wazuh_runtime_gate_owner_review",
snapshots["wazuh_runtime_owner_review"].get(
"status",
"runtime_gate_owner_review_packet_committed_no_runtime_action",
),
55
if _int(
runtime_owner_review_summary.get(
"owner_review_packet_accepted_count"
)
)
else 0,
"steady"
if _int(
runtime_owner_review_summary.get(
"owner_review_packet_accepted_count"
)
)
else "locked",
"進入 allowlisted check-mode 與 dry-run readbackruntime gate 仍關閉",
{
"target_selectors": runtime_owner_review_summary.get(
"target_selector_count", 0
),
"source_diff": runtime_owner_review_summary.get(
"source_of_truth_diff_count", 0
),
"check_mode": runtime_owner_review_summary.get(
"check_mode_plan_count", 0
),
"dry_run": runtime_owner_review_summary.get(
"dry_run_evidence_count", 0
),
"rollback": runtime_owner_review_summary.get(
"rollback_plan_count", 0
),
"post_apply_verifier": runtime_owner_review_summary.get(
"post_apply_verifier_count", 0
),
"owner_review_accepted": runtime_owner_review_summary.get(
"owner_review_packet_accepted_count", 0
),
"runtime_gate": runtime_owner_review_summary.get(
"runtime_gate_count", 0
),
},
[
"docs/security/wazuh-runtime-gate-owner-review-readback.snapshot.json"
],
),
_lane(
"wazuh_dashboard_api",
"degraded_api_connection_not_green",
@@ -562,6 +651,7 @@ def load_latest_iwooos_runtime_security_readback(
"Wazuh 即時中繼資料必須先通過負責人、機密中繼資料、唯讀範圍與啟用後讀回",
"Wazuh 負責人證據預檢 ready 不代表已收件、已接受或可啟用 active response",
"Wazuh controlled apply preflight ready 不代表 runtime gate 已開或已執行修復",
"Wazuh runtime gate owner-review accepted 只代表 review readiness不代表已查 live Wazuh 或可寫主機",
],
}

View File

@@ -0,0 +1,727 @@
"""
IwoooS Wazuh runtime gate owner-review readback.
This service exposes a committed owner-review readiness contract and a
no-persist validator for redacted runtime-gate owner-review packets. It never
queries live Wazuh, reads host data, reads secrets, persists raw payloads, or
authorizes runtime actions.
"""
from __future__ import annotations
import json
import re
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-runtime-gate-owner-review-readback.snapshot.json"
_EXPECTED_SCHEMA = "wazuh_runtime_gate_owner_review_readback_v1"
_REQUIRED_FALSE_BOUNDARIES = {
"active_scan_authorized",
"alertmanager_reload_authorized",
"auto_block_authorized",
"credentialed_scan_authorized",
"firewall_change_authorized",
"host_write_authorized",
"kali_execute_authorized",
"kali_scan_authorized",
"nginx_reload_authorized",
"production_write_authorized",
"runtime_execution_authorized",
"runtime_gate_open",
"secret_value_collection_allowed",
"telegram_send_authorized",
"wazuh_active_response_authorized",
"wazuh_api_live_query_authorized",
}
_SENSITIVE_TEXT_PATTERNS = {
"internal_ip": re.compile(
r"\b(?:10|127|172\.(?:1[6-9]|2\d|3[01])|192\.168)\.\d{1,3}\.\d{1,3}\b"
),
"authorization_header": re.compile(r"Authorization\s*:", re.IGNORECASE),
"bearer_token": re.compile(r"Bearer\s+[A-Za-z0-9._-]{10,}", re.IGNORECASE),
"basic_auth": re.compile(r"Basic\s+[A-Za-z0-9+/=]{10,}", re.IGNORECASE),
"password_assignment": re.compile(
r"password\s*[:=]\s*['\"][^'\"]+['\"]", re.IGNORECASE
),
"token_assignment": re.compile(r"token\s*[:=]\s*['\"][^'\"]+['\"]", re.IGNORECASE),
"cookie_assignment": re.compile(
r"cookie\s*[:=]\s*['\"][^'\"]+['\"]", re.IGNORECASE
),
"client_keys": re.compile(r"client\.keys", re.IGNORECASE),
"private_key": re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
"raw_session_text": re.compile(
r"(工作視窗|批准!繼續|source_thread_id|raw session)", re.IGNORECASE
),
}
_FORBIDDEN_KEY_FRAGMENTS = {
"authorization_header",
"basic_auth",
"bearer_token",
"client_keys",
"cookie",
"env_file",
"full_cli_output",
"full_journal",
"hostname",
"internal_ip",
"password",
"private_key",
"raw_agent_identity",
"raw_dashboard_request",
"raw_env",
"raw_hostname",
"raw_log",
"raw_runtime_volume",
"raw_wazuh_payload",
"session",
"stored_api_password",
"token",
"unredacted_screenshot",
"wazuh_api_password",
}
_RUNTIME_ACTION_KEYS = {
"active_response_enable",
"agent_reenroll",
"agent_restart",
"ansible_apply",
"ansible_playbook_run",
"apply_now",
"argocd_sync",
"credentialed_scan",
"database_migration",
"docker_restart",
"execute_now",
"exploit_attempt",
"firewall_change",
"force_push",
"host_write",
"k8s_apply",
"kali_active_scan",
"nginx_reload",
"repo_ref_delete",
"runtime_execution_authorized",
"secret_rotation",
"systemd_restart",
"wazuh_active_response",
"wazuh_agent_reenroll",
"wazuh_agent_restart",
"wazuh_api_live_query",
"wazuh_manager_restart",
"workflow_trigger",
}
def load_latest_iwooos_wazuh_runtime_gate_owner_review_readback(
security_dir: Path | None = None,
) -> dict[str, Any]:
"""Load the public-safe Wazuh runtime gate owner-review contract."""
directory = security_dir or _DEFAULT_SECURITY_DIR
snapshot = _load_snapshot(directory)
_require_boundaries(snapshot)
summary = _summary(snapshot)
merged_summary = {
"expected_scope_alias_count": _int(summary.get("expected_scope_alias_count")),
"target_selector_count": _int(summary.get("target_selector_count")),
"source_of_truth_diff_count": _int(summary.get("source_of_truth_diff_count")),
"check_mode_plan_count": _int(summary.get("check_mode_plan_count")),
"dry_run_evidence_count": _int(summary.get("dry_run_evidence_count")),
"rollback_plan_count": _int(summary.get("rollback_plan_count")),
"post_apply_verifier_count": _int(summary.get("post_apply_verifier_count")),
"km_playbook_writeback_count": _int(summary.get("km_playbook_writeback_count")),
"maintenance_window_review_count": _int(
summary.get("maintenance_window_review_count")
),
"owner_review_packet_received_count": _int(
summary.get("owner_review_packet_received_count")
),
"owner_review_packet_review_ready_count": _int(
summary.get("owner_review_packet_review_ready_count")
),
"owner_review_packet_accepted_count": _int(
summary.get("owner_review_packet_accepted_count")
),
"owner_review_packet_supplement_required_count": _int(
summary.get("owner_review_packet_supplement_required_count")
),
"owner_review_packet_quarantined_count": _int(
summary.get("owner_review_packet_quarantined_count")
),
"owner_review_runtime_action_rejected_count": _int(
summary.get("owner_review_runtime_action_rejected_count")
),
"forbidden_payload_count": _int(summary.get("forbidden_payload_count")),
"forbidden_action_count": _int(summary.get("forbidden_action_count")),
"runtime_gate_count": _int(summary.get("runtime_gate_count")),
"wazuh_api_live_query_authorized_count": _int(
summary.get("wazuh_api_live_query_authorized_count")
),
"wazuh_active_response_authorized_count": _int(
summary.get("wazuh_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_runtime_gate_owner_review_readback_v1",
"source_schema_version": snapshot["schema_version"],
"status": snapshot.get(
"status", "runtime_gate_owner_review_packet_committed_no_runtime_action"
),
"mode": snapshot.get(
"mode", "committed_owner_review_readback_no_live_wazuh_no_secret_collection"
),
"source_refs": [
f"docs/security/{_SNAPSHOT_FILE}",
"docs/security/wazuh-runtime-controlled-apply-preflight.snapshot.json",
],
"owner_review_packet_validation_endpoint": (
"/api/v1/iwooos/wazuh-runtime-gate-owner-review-readback/validate-owner-review-packet"
),
"owner_review_packet_validation_mode": "no_persist_owner_review_readback_no_runtime_action",
"summary": merged_summary,
"target_selectors": _target_selectors(snapshot.get("target_selectors")),
"required_owner_review_fields": _strings(
snapshot.get("required_owner_review_fields")
),
"review_items": _review_items(snapshot.get("review_items")),
"outcome_lanes": _strings(snapshot.get("outcome_lanes")),
"forbidden_payloads": _strings(snapshot.get("forbidden_payloads")),
"forbidden_actions": _strings(snapshot.get("forbidden_actions")),
"boundary_markers": _boundary_markers(merged_summary),
"boundaries": {
"payload_persisted": False,
"wazuh_api_live_query_authorized": False,
"wazuh_active_response_authorized": False,
"wazuh_agent_reenroll_authorized": False,
"wazuh_agent_restart_authorized": False,
"wazuh_manager_restart_authorized": False,
"host_write_authorized": False,
"active_scan_authorized": False,
"kali_execute_authorized": False,
"nginx_reload_authorized": False,
"secret_value_collection_allowed": False,
"runtime_execution_authorized": False,
"runtime_gate_open": False,
"not_authorization": True,
},
"no_false_green_rules": _strings(snapshot.get("no_false_green_rules")),
}
def validate_iwooos_wazuh_runtime_gate_owner_review_packet(
owner_review_packet: dict[str, Any],
security_dir: Path | None = None,
) -> dict[str, Any]:
"""Validate one redacted runtime-gate owner-review packet without applying it."""
contract = load_latest_iwooos_wazuh_runtime_gate_owner_review_readback(security_dir)
snapshot = _load_snapshot(security_dir or _DEFAULT_SECURITY_DIR)
required_fields = _strings(snapshot.get("required_owner_review_fields"))
expected_aliases = {
item["node_alias"]
for item in contract["target_selectors"]
if item.get("node_alias")
}
findings: list[dict[str, Any]] = []
if not isinstance(owner_review_packet, dict):
findings.append(
_finding(
"ORG-01",
"blocker",
"request_runtime_gate_owner_review_supplement",
"runtime gate owner-review packet must be a JSON object.",
[],
)
)
return _validation_result(
contract, "request_runtime_gate_owner_review_supplement", findings
)
sensitive_hits = _collect_sensitive_hits(owner_review_packet)
if sensitive_hits:
findings.append(
_finding(
"ORG-04",
"critical",
"quarantine_sensitive_payload",
"runtime gate owner-review packet contains forbidden or likely unredacted content; response omits raw values.",
[hit["path"] for hit in sensitive_hits[:12]],
{"categories": sorted({hit["category"] for hit in sensitive_hits})},
)
)
return _validation_result(contract, "quarantine_sensitive_payload", findings)
runtime_hits = _collect_runtime_action_hits(owner_review_packet)
if runtime_hits:
findings.append(
_finding(
"ORG-05",
"critical",
"reject_runtime_action_request",
"runtime gate owner-review packet requested runtime execution; this validator only records review readiness.",
runtime_hits[:12],
)
)
return _validation_result(contract, "reject_runtime_action_request", findings)
missing_fields = [
field
for field in required_fields
if not _present(owner_review_packet.get(field))
]
if missing_fields:
findings.append(
_finding(
"ORG-01",
"blocker",
"request_runtime_gate_owner_review_supplement",
"runtime gate owner-review packet is missing required fields.",
missing_fields,
)
)
alias_issue = _validate_aliases(
owner_review_packet.get("target_selector_aliases"), expected_aliases
)
if alias_issue:
findings.append(
_finding(
"ORG-02",
"blocker",
"request_target_selector_fix",
alias_issue,
["target_selector_aliases"],
)
)
if (
owner_review_packet.get("owner_review_intent")
!= "commit_runtime_gate_owner_review_readback_only"
):
findings.append(
_finding(
"ORG-03",
"blocker",
"request_runtime_gate_owner_review_decision_fix",
"owner_review_intent must be commit_runtime_gate_owner_review_readback_only.",
["owner_review_intent"],
)
)
if (
owner_review_packet.get("owner_review_decision")
!= "accept_controlled_apply_review_readiness_only"
):
findings.append(
_finding(
"ORG-06",
"blocker",
"request_runtime_gate_owner_review_decision_fix",
"owner_review_decision must be accept_controlled_apply_review_readiness_only and must not claim runtime gate opening.",
["owner_review_decision"],
)
)
if owner_review_packet.get("runtime_boundary_ack") != "runtime_gate_remains_closed":
findings.append(
_finding(
"ORG-07",
"blocker",
"request_runtime_boundary_ack_fix",
"runtime_boundary_ack must state runtime_gate_remains_closed.",
["runtime_boundary_ack"],
)
)
if owner_review_packet.get("secret_boundary_ack") != "no_secret_value_collected":
findings.append(
_finding(
"ORG-08",
"blocker",
"request_runtime_boundary_ack_fix",
"secret_boundary_ack must state no_secret_value_collected.",
["secret_boundary_ack"],
)
)
if (
owner_review_packet.get("live_wazuh_query_boundary_ack")
!= "no_live_wazuh_query_performed"
):
findings.append(
_finding(
"ORG-09",
"blocker",
"request_runtime_boundary_ack_fix",
"live_wazuh_query_boundary_ack must state no_live_wazuh_query_performed.",
["live_wazuh_query_boundary_ack"],
)
)
outcome = (
_first_blocking_lane(findings)
or "accepted_for_runtime_gate_owner_review_readback_only"
)
if outcome == "accepted_for_runtime_gate_owner_review_readback_only":
findings.append(
_finding(
"ORG-10",
"info",
"runtime_gate_owner_review_readback_ready",
"owner-review packet passed no-persist readiness validation; runtime gate remains closed.",
[
"owner_review_decision",
"check_mode_plan_ref",
"post_apply_verifier_ref",
],
)
)
return _validation_result(contract, outcome, findings)
def _load_snapshot(directory: Path) -> dict[str, Any]:
path = directory / _SNAPSHOT_FILE
if not path.is_file():
raise FileNotFoundError(
f"{path}: Wazuh runtime gate owner-review snapshot not found"
)
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 _strings(value: Any) -> list[str]:
if not isinstance(value, list):
return []
return [item for item in value if isinstance(item, str)]
def _target_selectors(value: Any) -> list[dict[str, Any]]:
if not isinstance(value, list):
return []
selectors: list[dict[str, Any]] = []
for item in value:
if not isinstance(item, dict):
continue
selectors.append(
{
"node_alias": str(item.get("node_alias", "")),
"scope": str(item.get("scope", "")),
"selector_kind": str(item.get("selector_kind", "")),
"runtime_write_allowed": item.get("runtime_write_allowed") is True,
"owner_review_scope": str(item.get("owner_review_scope", "")),
}
)
return selectors
def _review_items(value: Any) -> list[dict[str, Any]]:
if not isinstance(value, list):
return []
items: list[dict[str, Any]] = []
for item in value:
if not isinstance(item, dict):
continue
items.append(
{
"item_id": str(item.get("item_id", "")),
"title": str(item.get("title", "")),
"state_key": str(item.get("state_key", "")),
"accepted": item.get("accepted") is True,
"required_fields": _strings(item.get("required_fields")),
"next_gate": str(item.get("next_gate", "")),
}
)
return items
def _boundary_markers(summary: dict[str, int]) -> list[str]:
return [
"wazuh_runtime_gate_owner_review_visible=true",
"wazuh_runtime_gate_owner_review_validation_api_available=true",
f"wazuh_runtime_gate_owner_review_target_selector_count={summary['target_selector_count']}",
f"wazuh_runtime_gate_owner_review_source_diff_count={summary['source_of_truth_diff_count']}",
f"wazuh_runtime_gate_owner_review_check_mode_plan_count={summary['check_mode_plan_count']}",
f"wazuh_runtime_gate_owner_review_dry_run_evidence_count={summary['dry_run_evidence_count']}",
f"wazuh_runtime_gate_owner_review_packet_received_count={summary['owner_review_packet_received_count']}",
f"wazuh_runtime_gate_owner_review_packet_accepted_count={summary['owner_review_packet_accepted_count']}",
f"wazuh_runtime_gate_owner_review_runtime_gate_count={summary['runtime_gate_count']}",
"wazuh_api_live_query_authorized=false",
"wazuh_active_response_authorized=false",
"host_write_authorized=false",
"secret_value_collection_allowed=false",
"not_authorization=true",
]
def _require_boundaries(payload: dict[str, Any]) -> None:
summary = _summary(payload)
for key in (
"owner_review_packet_supplement_required_count",
"owner_review_packet_quarantined_count",
"owner_review_runtime_action_rejected_count",
"runtime_gate_count",
"wazuh_api_live_query_authorized_count",
"wazuh_active_response_authorized_count",
"host_write_authorized_count",
"secret_value_collection_allowed_count",
):
if _int(summary.get(key)) != 0:
raise ValueError(
f"Wazuh runtime gate owner-review summary.{key} must remain 0"
)
expected_alias_count = _int(summary.get("expected_scope_alias_count"))
target_selector_count = _int(summary.get("target_selector_count"))
target_selectors = _target_selectors(payload.get("target_selectors"))
if (
target_selector_count != expected_alias_count
or len(target_selectors) != expected_alias_count
):
raise ValueError(
"Wazuh runtime gate owner-review target selectors must match expected alias count"
)
if any(item.get("runtime_write_allowed") is True for item in target_selectors):
raise ValueError(
"Wazuh runtime gate owner-review target selectors must not allow runtime writes"
)
readiness_keys = (
"source_of_truth_diff_count",
"check_mode_plan_count",
"dry_run_evidence_count",
"rollback_plan_count",
"post_apply_verifier_count",
"km_playbook_writeback_count",
"maintenance_window_review_count",
"owner_review_packet_received_count",
"owner_review_packet_review_ready_count",
"owner_review_packet_accepted_count",
)
if any(_int(summary.get(key)) <= 0 for key in readiness_keys):
raise ValueError(
"Wazuh runtime gate owner-review readiness counters must be positive"
)
boundaries = payload.get("execution_boundaries")
if not isinstance(boundaries, dict):
raise ValueError("Wazuh runtime gate owner-review execution_boundaries missing")
for key in _REQUIRED_FALSE_BOUNDARIES:
if boundaries.get(key) is not False:
raise ValueError(
f"Wazuh runtime gate owner-review execution_boundaries.{key} must remain false"
)
if boundaries.get("not_authorization") is not True:
raise ValueError(
"Wazuh runtime gate owner-review not_authorization must remain true"
)
def _validation_result(
contract: dict[str, Any],
outcome_lane: str,
findings: list[dict[str, Any]],
) -> dict[str, Any]:
accepted = outcome_lane == "accepted_for_runtime_gate_owner_review_readback_only"
quarantined = outcome_lane == "quarantine_sensitive_payload"
rejected_runtime = outcome_lane == "reject_runtime_action_request"
supplement_required = not accepted and not quarantined and not rejected_runtime
return {
"schema_version": "iwooos_wazuh_runtime_gate_owner_review_packet_validation_result_v1",
"contract_schema_version": contract["schema_version"],
"status": outcome_lane,
"mode": "no_persist_runtime_gate_owner_review_no_runtime_no_secret_collection",
"outcome_lane": outcome_lane,
"accepted_for_runtime_gate_owner_review_readback_only": accepted,
"quarantined": quarantined,
"runtime_action_rejected": rejected_runtime,
"summary": {
"owner_review_packet_received_count": 1,
"owner_review_packet_review_ready_count": 1 if accepted else 0,
"owner_review_packet_accepted_count": 1 if accepted else 0,
"owner_review_packet_supplement_required_count": 1
if supplement_required
else 0,
"owner_review_packet_quarantined_count": 1 if quarantined else 0,
"owner_review_runtime_action_rejected_count": 1 if rejected_runtime else 0,
"runtime_gate_count": 0,
"wazuh_api_live_query_authorized_count": 0,
"wazuh_active_response_authorized_count": 0,
"host_write_authorized_count": 0,
"secret_value_collection_allowed_count": 0,
"finding_count": len(findings),
},
"validation_findings": findings,
"boundary_markers": [
"wazuh_runtime_gate_owner_review_packet_validation_received_count=1",
f"wazuh_runtime_gate_owner_review_packet_validation_accepted_count={1 if accepted else 0}",
f"wazuh_runtime_gate_owner_review_packet_validation_quarantined_count={1 if quarantined else 0}",
f"wazuh_runtime_gate_owner_review_packet_validation_runtime_action_rejected_count={1 if rejected_runtime else 0}",
"wazuh_runtime_gate_owner_review_packet_validation_runtime_gate_count=0",
"wazuh_runtime_gate_owner_review_packet_validation_no_persist=true",
"wazuh_api_live_query_authorized=false",
"wazuh_active_response_authorized=false",
"host_write_authorized=false",
"secret_value_collection_allowed=false",
"not_authorization=true",
],
"boundaries": {
"payload_persisted": False,
"wazuh_api_live_query_authorized": False,
"wazuh_active_response_authorized": False,
"wazuh_agent_reenroll_authorized": False,
"wazuh_agent_restart_authorized": False,
"wazuh_manager_restart_authorized": False,
"host_write_authorized": False,
"active_scan_authorized": False,
"kali_execute_authorized": False,
"nginx_reload_authorized": False,
"secret_value_collection_allowed": False,
"runtime_execution_authorized": False,
"runtime_gate_open": False,
"not_authorization": True,
},
"next_gate": "stage_allowlisted_check_mode_dry_run_before_runtime_gate"
if accepted
else "runtime_gate_owner_review_packet_fix_and_resubmit",
}
def _finding(
check_id: str,
severity: str,
lane: str,
message: str,
field_paths: list[str],
extra: dict[str, Any] | None = None,
) -> dict[str, Any]:
payload: dict[str, Any] = {
"check_id": check_id,
"severity": severity,
"lane": lane,
"message": message,
"field_paths": field_paths,
}
if extra:
payload.update(extra)
return payload
def _present(value: Any) -> bool:
if value is None:
return False
if isinstance(value, str):
return bool(value.strip())
if isinstance(value, list | dict | tuple | set):
return bool(value)
return True
def _validate_aliases(value: Any, expected_aliases: set[str]) -> str | None:
aliases = value if isinstance(value, list) else []
if not aliases or not all(isinstance(item, str) for item in aliases):
return "target_selector_aliases must be an array of public alias strings."
alias_set = set(aliases)
if len(aliases) != len(alias_set):
return "target_selector_aliases must not contain duplicates."
if alias_set != expected_aliases:
missing = sorted(expected_aliases - alias_set)
extra = sorted(alias_set - expected_aliases)
return f"target_selector_aliases must match expected public aliases; missing={missing} extra={extra}"
return None
def _collect_sensitive_hits(value: Any, path: str = "$") -> list[dict[str, str]]:
hits: list[dict[str, str]] = []
if isinstance(value, dict):
for key, item in value.items():
key_text = str(key)
key_lower = key_text.lower()
for fragment in _FORBIDDEN_KEY_FRAGMENTS:
if fragment in key_lower:
hits.append(
{
"path": f"{path}.{key_text}",
"category": f"forbidden_key:{fragment}",
}
)
hits.extend(_collect_sensitive_hits(item, f"{path}.{key_text}"))
return hits
if isinstance(value, list):
for index, item in enumerate(value):
hits.extend(_collect_sensitive_hits(item, f"{path}[{index}]"))
return hits
if isinstance(value, str):
for category, pattern in _SENSITIVE_TEXT_PATTERNS.items():
if pattern.search(value):
hits.append({"path": path, "category": category})
return hits
def _collect_runtime_action_hits(value: Any, path: str = "$") -> list[str]:
hits: list[str] = []
if isinstance(value, dict):
for key, item in value.items():
key_text = str(key)
normalized_key = key_text.lower().replace("-", "_").replace(" ", "_")
if normalized_key in _RUNTIME_ACTION_KEYS and item not in (
False,
None,
"",
[],
{},
):
hits.append(f"{path}.{key_text}")
hits.extend(_collect_runtime_action_hits(item, f"{path}.{key_text}"))
return hits
if isinstance(value, list):
for index, item in enumerate(value):
hits.extend(_collect_runtime_action_hits(item, f"{path}[{index}]"))
return hits
if isinstance(value, str):
normalized = value.lower().replace("-", "_").replace(" ", "_")
if normalized in _RUNTIME_ACTION_KEYS:
hits.append(path)
return hits
def _first_blocking_lane(findings: list[dict[str, Any]]) -> str | None:
severity_order = {"critical": 0, "blocker": 1, "warn": 2, "info": 3}
blocking = [
finding
for finding in findings
if finding.get("severity") in {"critical", "blocker"}
]
if not blocking:
return None
blocking.sort(
key=lambda finding: severity_order.get(str(finding.get("severity")), 99)
)
return str(
blocking[0].get("lane") or "request_runtime_gate_owner_review_supplement"
)

View File

@@ -10,6 +10,9 @@ from src.services.iwooos_runtime_security_readback import (
from src.services.iwooos_wazuh_runtime_controlled_apply_preflight import (
load_latest_iwooos_wazuh_runtime_controlled_apply_preflight,
)
from src.services.iwooos_wazuh_runtime_gate_owner_review_readback import (
load_latest_iwooos_wazuh_runtime_gate_owner_review_readback,
)
def _client() -> TestClient:
@@ -44,13 +47,44 @@ def _valid_runtime_controlled_apply_packet() -> dict[str, object]:
}
def _valid_runtime_gate_owner_review_packet() -> dict[str, object]:
return {
"owner_review_intent": "commit_runtime_gate_owner_review_readback_only",
"owner_reviewer_role": "iwooos-security-owner",
"owner_review_decision": "accept_controlled_apply_review_readiness_only",
"owner_review_decision_reason": "redacted owner-review packet accepts review readiness only; runtime gate remains closed",
"target_selector_aliases": [
"managed_core_node_a",
"managed_core_node_b",
"managed_core_node_c",
"managed_edge_node_a",
"managed_edge_node_b",
"managed_lab_node_a",
],
"source_of_truth_diff_ref": "docs/security/wazuh-runtime-gate-owner-review-readback.snapshot.json#source-diff",
"check_mode_plan_ref": "playbooks/wazuh-controlled-apply-check-mode#redacted-plan",
"dry_run_evidence_ref": "evidence/iwooos/wazuh-runtime-owner-review-dry-run-redacted-v1",
"blast_radius_statement": "public aliases only; no live Wazuh query and no host write in this owner-review readback",
"maintenance_window_ref": "maintenance/iwooos-wazuh-low-traffic-window-redacted-v1",
"rollback_plan_ref": "playbooks/wazuh-controlled-apply-rollback#redacted-plan",
"rollback_owner": "iwooos-security-owner",
"post_apply_verifier_ref": "verifiers/iwooos-wazuh-post-apply-readback#public-safe",
"km_playbook_writeback_ref": "km/playbook-trust/wazuh-runtime-gate-owner-review-v1",
"followup_owner": "iwooos-security-reviewer",
"audit_receipt_ref": "audit/iwooos-wazuh-runtime-gate-owner-review-redacted-v1",
"runtime_boundary_ack": "runtime_gate_remains_closed",
"secret_boundary_ack": "no_secret_value_collected",
"live_wazuh_query_boundary_ack": "no_live_wazuh_query_performed",
}
def test_iwooos_runtime_security_readback_preserves_zero_runtime_gates() -> None:
payload = load_latest_iwooos_runtime_security_readback()
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"] == 11
assert payload["summary"]["p0_lane_count"] == 10
assert payload["summary"]["source_snapshot_count"] == 12
assert payload["summary"]["p0_lane_count"] == 11
assert payload["summary"]["runtime_gate_count"] == 0
assert payload["summary"]["owner_response_received_count"] == 0
assert payload["summary"]["owner_response_accepted_count"] == 0
@@ -101,6 +135,21 @@ def test_iwooos_runtime_security_readback_preserves_zero_runtime_gates() -> None
assert payload["summary"]["wazuh_runtime_apply_km_writeback_count"] == 1
assert payload["summary"]["wazuh_runtime_apply_owner_review_ready_count"] == 1
assert payload["summary"]["wazuh_runtime_apply_runtime_gate_count"] == 0
assert payload["summary"]["wazuh_runtime_owner_review_target_selector_count"] == 6
assert payload["summary"]["wazuh_runtime_owner_review_source_diff_count"] == 1
assert payload["summary"]["wazuh_runtime_owner_review_check_mode_plan_count"] == 1
assert payload["summary"]["wazuh_runtime_owner_review_dry_run_evidence_count"] == 1
assert payload["summary"]["wazuh_runtime_owner_review_rollback_plan_count"] == 1
assert (
payload["summary"]["wazuh_runtime_owner_review_post_apply_verifier_count"] == 1
)
assert payload["summary"]["wazuh_runtime_owner_review_km_writeback_count"] == 1
assert payload["summary"]["wazuh_runtime_owner_review_packet_received_count"] == 1
assert (
payload["summary"]["wazuh_runtime_owner_review_packet_review_ready_count"] == 1
)
assert payload["summary"]["wazuh_runtime_owner_review_packet_accepted_count"] == 1
assert payload["summary"]["wazuh_runtime_owner_review_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
@@ -120,6 +169,7 @@ def test_iwooos_runtime_security_readback_lanes_are_candidate_only() -> None:
"wazuh_live_metadata_gate",
"wazuh_owner_evidence_preflight",
"wazuh_runtime_controlled_apply_preflight",
"wazuh_runtime_gate_owner_review",
"wazuh_dashboard_api",
"kali_intake",
"alert_readability",
@@ -152,6 +202,15 @@ def test_iwooos_runtime_security_readback_lanes_are_candidate_only() -> None:
or (lane["completion_percent"] == 45 and lane["metrics"]["runtime_gate"] == 0)
for lane in payload["lanes"]
)
assert all(
lane["lane_id"] != "wazuh_runtime_gate_owner_review"
or (
lane["completion_percent"] == 55
and lane["metrics"]["owner_review_accepted"] == 1
and lane["metrics"]["runtime_gate"] == 0
)
for lane in payload["lanes"]
)
def test_iwooos_runtime_security_readback_api_is_public_safe(monkeypatch) -> None:
@@ -178,6 +237,8 @@ def test_iwooos_runtime_security_readback_api_is_public_safe(monkeypatch) -> Non
assert data["summary"]["wazuh_owner_evidence_runtime_gate_count"] == 0
assert data["summary"]["wazuh_runtime_apply_preflight_ready_count"] == 1
assert data["summary"]["wazuh_runtime_apply_runtime_gate_count"] == 0
assert data["summary"]["wazuh_runtime_owner_review_packet_accepted_count"] == 1
assert data["summary"]["wazuh_runtime_owner_review_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
@@ -463,3 +524,151 @@ def test_iwooos_wazuh_runtime_controlled_apply_preflight_validator_rejects_runti
assert data["runtime_action_rejected"] is True
assert data["summary"]["controlled_apply_runtime_action_rejected_count"] == 1
assert data["summary"]["runtime_gate_count"] == 0
def test_iwooos_wazuh_runtime_gate_owner_review_readback_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)
payload = load_latest_iwooos_wazuh_runtime_gate_owner_review_readback()
assert (
payload["schema_version"]
== "iwooos_wazuh_runtime_gate_owner_review_readback_v1"
)
assert (
payload["status"]
== "runtime_gate_owner_review_packet_committed_no_runtime_action"
)
assert payload["summary"]["owner_review_packet_accepted_count"] == 1
assert payload["summary"]["target_selector_count"] == 6
assert payload["summary"]["runtime_gate_count"] == 0
assert payload["boundaries"]["runtime_execution_authorized"] is False
response = _client().get("/api/v1/iwooos/wazuh-runtime-gate-owner-review-readback")
assert response.status_code == 200
data = response.json()
assert (
data["schema_version"] == "iwooos_wazuh_runtime_gate_owner_review_readback_v1"
)
assert data["summary"]["owner_review_packet_received_count"] == 1
assert data["summary"]["owner_review_packet_review_ready_count"] == 1
assert data["summary"]["owner_review_packet_accepted_count"] == 1
assert data["summary"]["runtime_gate_count"] == 0
assert data["summary"]["wazuh_api_live_query_authorized_count"] == 0
assert data["summary"]["wazuh_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"]["payload_persisted"] is False
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_gate_open"] is False
assert data["boundaries"]["not_authorization"] is True
assert len(data["target_selectors"]) == 6
assert len(data["review_items"]) == 7
assert any(
marker == "wazuh_runtime_gate_owner_review_validation_api_available=true"
for marker in data["boundary_markers"]
)
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
def test_iwooos_wazuh_runtime_gate_owner_review_validator_accepts_redacted_packet(
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)
client = _client()
before = client.get(
"/api/v1/iwooos/wazuh-runtime-gate-owner-review-readback"
).json()
response = client.post(
"/api/v1/iwooos/wazuh-runtime-gate-owner-review-readback/validate-owner-review-packet",
json=_valid_runtime_gate_owner_review_packet(),
)
after = client.get("/api/v1/iwooos/wazuh-runtime-gate-owner-review-readback").json()
assert response.status_code == 200
data = response.json()
assert (
data["schema_version"]
== "iwooos_wazuh_runtime_gate_owner_review_packet_validation_result_v1"
)
assert data["status"] == "accepted_for_runtime_gate_owner_review_readback_only"
assert data["accepted_for_runtime_gate_owner_review_readback_only"] is True
assert data["summary"]["owner_review_packet_received_count"] == 1
assert data["summary"]["owner_review_packet_review_ready_count"] == 1
assert data["summary"]["owner_review_packet_accepted_count"] == 1
assert data["summary"]["runtime_gate_count"] == 0
assert data["summary"]["wazuh_api_live_query_authorized_count"] == 0
assert data["summary"]["wazuh_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"]["payload_persisted"] is False
assert data["boundaries"]["runtime_execution_authorized"] is False
assert data["boundaries"]["runtime_gate_open"] is False
assert before["summary"] == after["summary"]
assert "192.168.0." not in response.text
assert "工作視窗" not in response.text
assert "批准!繼續" not in response.text
def test_iwooos_wazuh_runtime_gate_owner_review_validator_quarantines_sensitive_payload(
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)
packet = _valid_runtime_gate_owner_review_packet()
packet[
"redacted_evidence_ref"
] = "redacted note includes 10.1.2.3 and Authorization: Bearer abcdefghijklmnop"
response = _client().post(
"/api/v1/iwooos/wazuh-runtime-gate-owner-review-readback/validate-owner-review-packet",
json=packet,
)
assert response.status_code == 200
data = response.json()
assert data["status"] == "quarantine_sensitive_payload"
assert data["quarantined"] is True
assert data["summary"]["owner_review_packet_quarantined_count"] == 1
assert data["summary"]["runtime_gate_count"] == 0
assert "10.1.2.3" not in response.text
assert "Bearer abcdefghijklmnop" not in response.text
def test_iwooos_wazuh_runtime_gate_owner_review_validator_rejects_runtime_action(
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)
packet = _valid_runtime_gate_owner_review_packet()
packet["wazuh_active_response"] = True
response = _client().post(
"/api/v1/iwooos/wazuh-runtime-gate-owner-review-readback/validate-owner-review-packet",
json=packet,
)
assert response.status_code == 200
data = response.json()
assert data["status"] == "reject_runtime_action_request"
assert data["runtime_action_rejected"] is True
assert data["summary"]["owner_review_runtime_action_rejected_count"] == 1
assert data["summary"]["runtime_gate_count"] == 0

View File

@@ -20429,8 +20429,8 @@
},
"runtimeSecurityReadback": {
"eyebrow": "IwoooS Runtime 資安讀回",
"title": "十條 P0 資安線先接到同一張讀回板",
"subtitle": "這張板讀取後端彙整的只讀快照,並附上 Wazuh 正式只讀路由controlled apply preflight 的公開安全彙總讀回;它不保存原始 Wazuh 載荷、不啟動掃描、不送訊息、不改主機。",
"title": "十條 P0 資安線先接到同一張讀回板",
"subtitle": "這張板讀取後端彙整的只讀快照,並附上 Wazuh 正式只讀路由controlled apply preflight 與 owner-review readback 的公開安全彙總;它不保存原始 Wazuh 載荷、不啟動掃描、不送訊息、不改主機。",
"statusLabel": "讀回狀態",
"statusDetail": "讀回成功只代表 IwoooS 能看見目前的證據邊界runtime 寫入、主動回應、掃描、重啟、Nginx reload、workflow 修改與機密操作仍全部關閉。",
"laneStatusLabel": "目前狀態",
@@ -20449,6 +20449,7 @@
"wazuh_live_metadata_gate": "等待即時中繼資料負責人回覆",
"wazuh_owner_evidence_preflight": "負責人證據預檢已就緒,尚未授權執行期動作",
"wazuh_runtime_controlled_apply_preflight": "受控執行預檢已就緒,執行期閘門仍關閉",
"wazuh_runtime_gate_owner_review": "owner-review packet 已接受為 review readiness執行期閘門仍關閉",
"wazuh_dashboard_api": "API 連線退化,禁止顯示綠燈",
"kali_intake": "部分執行期健康已整合,仍待完整驗收",
"alert_readability": "格式合約已就緒,尚未有實發收件證據",
@@ -20481,6 +20482,10 @@
"label": "受控預檢",
"detail": "target selector、diff、check-mode、rollback、verifier 與 writeback 已可審查runtime 仍為 0。"
},
"ownerReview": {
"label": "Owner review",
"detail": "review packet 已接受為 readback readiness不代表 live query、active response 或 host write。"
},
"ownerAccepted": {
"label": "負責人驗收",
"detail": "收到 / 接受都必須由正式 owner response 證明。"
@@ -20515,6 +20520,10 @@
"title": "Wazuh 受控執行預檢",
"body": "target selector、source-of-truth diff、check-mode / dry-run、rollback、post-apply verifier 與 KM / PlayBook writeback 已成為可審查 packet它仍不查 live Wazuh、不開 active response、不寫主機。"
},
"wazuh_runtime_gate_owner_review": {
"title": "Wazuh Runtime Gate Owner Review",
"body": "owner-review decision、target selector、diff、check-mode / dry-run evidence、rollback、verifier 與 writeback 已讀回;它只代表 review readiness不查 live Wazuh、不開 active response、不寫主機。"
},
"wazuh_dashboard_api": {
"title": "Wazuh Dashboard API",
"body": "API connection / API version 還要補 readbackindex pattern 通過不能宣稱 Wazuh 全綠。"

View File

@@ -20429,8 +20429,8 @@
},
"runtimeSecurityReadback": {
"eyebrow": "IwoooS Runtime 資安讀回",
"title": "十條 P0 資安線先接到同一張讀回板",
"subtitle": "這張板讀取後端彙整的只讀快照,並附上 Wazuh 正式只讀路由controlled apply preflight 的公開安全彙總讀回;它不保存原始 Wazuh 載荷、不啟動掃描、不送訊息、不改主機。",
"title": "十條 P0 資安線先接到同一張讀回板",
"subtitle": "這張板讀取後端彙整的只讀快照,並附上 Wazuh 正式只讀路由controlled apply preflight 與 owner-review readback 的公開安全彙總;它不保存原始 Wazuh 載荷、不啟動掃描、不送訊息、不改主機。",
"statusLabel": "讀回狀態",
"statusDetail": "讀回成功只代表 IwoooS 能看見目前的證據邊界runtime 寫入、主動回應、掃描、重啟、Nginx reload、workflow 修改與機密操作仍全部關閉。",
"laneStatusLabel": "目前狀態",
@@ -20449,6 +20449,7 @@
"wazuh_live_metadata_gate": "等待即時中繼資料負責人回覆",
"wazuh_owner_evidence_preflight": "負責人證據預檢已就緒,尚未授權執行期動作",
"wazuh_runtime_controlled_apply_preflight": "受控執行預檢已就緒,執行期閘門仍關閉",
"wazuh_runtime_gate_owner_review": "owner-review packet 已接受為 review readiness執行期閘門仍關閉",
"wazuh_dashboard_api": "API 連線退化,禁止顯示綠燈",
"kali_intake": "部分執行期健康已整合,仍待完整驗收",
"alert_readability": "格式合約已就緒,尚未有實發收件證據",
@@ -20481,6 +20482,10 @@
"label": "受控預檢",
"detail": "target selector、diff、check-mode、rollback、verifier 與 writeback 已可審查runtime 仍為 0。"
},
"ownerReview": {
"label": "Owner review",
"detail": "review packet 已接受為 readback readiness不代表 live query、active response 或 host write。"
},
"ownerAccepted": {
"label": "負責人驗收",
"detail": "收到 / 接受都必須由正式 owner response 證明。"
@@ -20515,6 +20520,10 @@
"title": "Wazuh 受控執行預檢",
"body": "target selector、source-of-truth diff、check-mode / dry-run、rollback、post-apply verifier 與 KM / PlayBook writeback 已成為可審查 packet它仍不查 live Wazuh、不開 active response、不寫主機。"
},
"wazuh_runtime_gate_owner_review": {
"title": "Wazuh Runtime Gate Owner Review",
"body": "owner-review decision、target selector、diff、check-mode / dry-run evidence、rollback、verifier 與 writeback 已讀回;它只代表 review readiness不查 live Wazuh、不開 active response、不寫主機。"
},
"wazuh_dashboard_api": {
"title": "Wazuh Dashboard API",
"body": "API connection / API version 還要補 readbackindex pattern 通過不能宣稱 Wazuh 全綠。"

View File

@@ -347,6 +347,7 @@ type RuntimeSecurityReadbackSummaryItem = {
| 'wazuhLive'
| 'metadataGate'
| 'controlledApplyPreflight'
| 'ownerReview'
| 'ownerAccepted'
| 'kaliRuntime'
| 'runtimeGate'
@@ -8244,6 +8245,7 @@ const runtimeSecurityLaneStatusKeys = new Set<IwoooSRuntimeSecurityReadbackRespo
'wazuh_live_metadata_gate',
'wazuh_owner_evidence_preflight',
'wazuh_runtime_controlled_apply_preflight',
'wazuh_runtime_gate_owner_review',
'wazuh_dashboard_api',
'kali_intake',
'alert_readability',
@@ -8327,6 +8329,12 @@ function IwoooSRuntimeSecurityReadbackBoard() {
icon: ListChecks,
tone: summary && summary.wazuh_runtime_apply_preflight_ready_count > 0 ? 'steady' : 'locked',
},
{
key: 'ownerReview',
value: summary ? String(summary.wazuh_runtime_owner_review_packet_accepted_count) : '...',
icon: ClipboardCheck,
tone: summary && summary.wazuh_runtime_owner_review_packet_accepted_count > 0 ? 'steady' : 'locked',
},
{
key: 'ownerAccepted',
value: summary ? `${summary.owner_response_accepted_count}/${summary.owner_response_received_count}` : '...',

View File

@@ -105,6 +105,7 @@ export interface IwoooSRuntimeSecurityReadbackLane {
| 'wazuh_live_metadata_gate'
| 'wazuh_owner_evidence_preflight'
| 'wazuh_runtime_controlled_apply_preflight'
| 'wazuh_runtime_gate_owner_review'
| 'wazuh_dashboard_api'
| 'kali_intake'
| 'alert_readability'
@@ -171,6 +172,17 @@ export interface IwoooSRuntimeSecurityReadbackResponse {
wazuh_runtime_apply_km_writeback_count: number
wazuh_runtime_apply_owner_review_ready_count: number
wazuh_runtime_apply_runtime_gate_count: number
wazuh_runtime_owner_review_target_selector_count: number
wazuh_runtime_owner_review_source_diff_count: number
wazuh_runtime_owner_review_check_mode_plan_count: number
wazuh_runtime_owner_review_dry_run_evidence_count: number
wazuh_runtime_owner_review_rollback_plan_count: number
wazuh_runtime_owner_review_post_apply_verifier_count: number
wazuh_runtime_owner_review_km_writeback_count: number
wazuh_runtime_owner_review_packet_received_count: number
wazuh_runtime_owner_review_packet_review_ready_count: number
wazuh_runtime_owner_review_packet_accepted_count: number
wazuh_runtime_owner_review_runtime_gate_count: number
kali_active_scan_authorized_count: number
kali_execute_authorized_count: number
kali_finding_envelope_accepted_count: number
@@ -343,6 +355,71 @@ export interface IwoooSWazuhRuntimeControlledApplyPreflightResponse {
no_false_green_rules: string[]
}
export interface IwoooSWazuhRuntimeGateOwnerReviewItem {
item_id:
| 'owner_decision'
| 'target_selector'
| 'source_of_truth_diff'
| 'check_mode_dry_run'
| 'rollback_and_maintenance'
| 'post_apply_verifier'
| 'learning_writeback'
title: string
state_key: string
accepted: boolean
required_fields: string[]
next_gate: string
}
export interface IwoooSWazuhRuntimeGateOwnerReviewReadbackResponse {
schema_version: 'iwooos_wazuh_runtime_gate_owner_review_readback_v1'
source_schema_version: 'wazuh_runtime_gate_owner_review_readback_v1'
status: string
mode: string
source_refs: string[]
owner_review_packet_validation_endpoint: string
owner_review_packet_validation_mode: string
summary: {
expected_scope_alias_count: number
target_selector_count: number
source_of_truth_diff_count: number
check_mode_plan_count: number
dry_run_evidence_count: number
rollback_plan_count: number
post_apply_verifier_count: number
km_playbook_writeback_count: number
maintenance_window_review_count: number
owner_review_packet_received_count: number
owner_review_packet_review_ready_count: number
owner_review_packet_accepted_count: number
owner_review_packet_supplement_required_count: number
owner_review_packet_quarantined_count: number
owner_review_runtime_action_rejected_count: number
forbidden_payload_count: number
forbidden_action_count: number
runtime_gate_count: number
wazuh_api_live_query_authorized_count: number
wazuh_active_response_authorized_count: number
host_write_authorized_count: number
secret_value_collection_allowed_count: number
}
target_selectors: Array<{
node_alias: string
scope: string
selector_kind: string
runtime_write_allowed: boolean
owner_review_scope: string
}>
required_owner_review_fields: string[]
review_items: IwoooSWazuhRuntimeGateOwnerReviewItem[]
outcome_lanes: string[]
forbidden_payloads: string[]
forbidden_actions: string[]
boundary_markers: string[]
boundaries: Record<string, boolean>
no_false_green_rules: string[]
}
export interface IwoooSWazuhManagedHostCoverageHost {
node_id: string
role: string
@@ -701,6 +778,11 @@ export const apiClient = {
return handleResponse<IwoooSWazuhRuntimeControlledApplyPreflightResponse>(res)
},
async getIwoooSWazuhRuntimeGateOwnerReviewReadback() {
const res = await fetch(`${API_BASE_URL}/iwooos/wazuh-runtime-gate-owner-review-readback`, { cache: 'no-store' })
return handleResponse<IwoooSWazuhRuntimeGateOwnerReviewReadbackResponse>(res)
},
async getIwoooSWazuhManagedHostCoverage() {
const res = await fetch(`${API_BASE_URL}/iwooos/wazuh-managed-host-coverage`, { cache: 'no-store' })
return handleResponse<IwoooSWazuhManagedHostCoverageResponse>(res)

View File

@@ -0,0 +1,250 @@
{
"schema_version": "wazuh_runtime_gate_owner_review_readback_v1",
"generated_at": "2026-06-28T11:05:00+08:00",
"status": "runtime_gate_owner_review_packet_committed_no_runtime_action",
"mode": "committed_owner_review_readback_no_live_wazuh_no_secret_collection",
"summary": {
"expected_scope_alias_count": 6,
"target_selector_count": 6,
"source_of_truth_diff_count": 1,
"check_mode_plan_count": 1,
"dry_run_evidence_count": 1,
"rollback_plan_count": 1,
"post_apply_verifier_count": 1,
"km_playbook_writeback_count": 1,
"maintenance_window_review_count": 1,
"owner_review_packet_received_count": 1,
"owner_review_packet_review_ready_count": 1,
"owner_review_packet_accepted_count": 1,
"owner_review_packet_supplement_required_count": 0,
"owner_review_packet_quarantined_count": 0,
"owner_review_runtime_action_rejected_count": 0,
"forbidden_payload_count": 18,
"forbidden_action_count": 20,
"runtime_gate_count": 0,
"wazuh_api_live_query_authorized_count": 0,
"wazuh_active_response_authorized_count": 0,
"host_write_authorized_count": 0,
"secret_value_collection_allowed_count": 0
},
"target_selectors": [
{
"node_alias": "managed_core_node_a",
"scope": "wazuh_manager_registry_accepted_alias",
"selector_kind": "public_alias_only",
"runtime_write_allowed": false,
"owner_review_scope": "runtime_gate_review_only"
},
{
"node_alias": "managed_core_node_b",
"scope": "wazuh_manager_registry_accepted_alias",
"selector_kind": "public_alias_only",
"runtime_write_allowed": false,
"owner_review_scope": "runtime_gate_review_only"
},
{
"node_alias": "managed_core_node_c",
"scope": "wazuh_manager_registry_accepted_alias",
"selector_kind": "public_alias_only",
"runtime_write_allowed": false,
"owner_review_scope": "runtime_gate_review_only"
},
{
"node_alias": "managed_edge_node_a",
"scope": "wazuh_manager_registry_accepted_alias",
"selector_kind": "public_alias_only",
"runtime_write_allowed": false,
"owner_review_scope": "runtime_gate_review_only"
},
{
"node_alias": "managed_edge_node_b",
"scope": "wazuh_manager_registry_accepted_alias",
"selector_kind": "public_alias_only",
"runtime_write_allowed": false,
"owner_review_scope": "runtime_gate_review_only"
},
{
"node_alias": "managed_lab_node_a",
"scope": "wazuh_manager_registry_accepted_alias",
"selector_kind": "public_alias_only",
"runtime_write_allowed": false,
"owner_review_scope": "runtime_gate_review_only"
}
],
"required_owner_review_fields": [
"owner_review_intent",
"owner_reviewer_role",
"owner_review_decision",
"owner_review_decision_reason",
"target_selector_aliases",
"source_of_truth_diff_ref",
"check_mode_plan_ref",
"dry_run_evidence_ref",
"blast_radius_statement",
"maintenance_window_ref",
"rollback_plan_ref",
"rollback_owner",
"post_apply_verifier_ref",
"km_playbook_writeback_ref",
"followup_owner",
"audit_receipt_ref",
"runtime_boundary_ack",
"secret_boundary_ack",
"live_wazuh_query_boundary_ack"
],
"review_items": [
{
"item_id": "owner_decision",
"title": "Owner review decision for runtime-gate readiness",
"state_key": "owner_review_decision_committed",
"accepted": true,
"required_fields": [
"owner_reviewer_role",
"owner_review_decision",
"owner_review_decision_reason"
],
"next_gate": "staged allowlisted check-mode before any future runtime gate change"
},
{
"item_id": "target_selector",
"title": "Public alias target selector",
"state_key": "target_selector_reviewed",
"accepted": true,
"required_fields": [
"target_selector_aliases"
],
"next_gate": "target selector remains public aliases only"
},
{
"item_id": "source_of_truth_diff",
"title": "Source-of-truth diff reference",
"state_key": "source_of_truth_diff_reviewed",
"accepted": true,
"required_fields": [
"source_of_truth_diff_ref"
],
"next_gate": "diff must be re-read before any future controlled apply"
},
{
"item_id": "check_mode_dry_run",
"title": "Check-mode and dry-run evidence",
"state_key": "check_mode_dry_run_reviewed",
"accepted": true,
"required_fields": [
"check_mode_plan_ref",
"dry_run_evidence_ref"
],
"next_gate": "dry-run evidence stays redacted and no host output is stored"
},
{
"item_id": "rollback_and_maintenance",
"title": "Rollback and maintenance window",
"state_key": "rollback_maintenance_reviewed",
"accepted": true,
"required_fields": [
"rollback_plan_ref",
"rollback_owner",
"maintenance_window_ref"
],
"next_gate": "rollback owner and maintenance window must be revalidated before runtime"
},
{
"item_id": "post_apply_verifier",
"title": "Post-apply verifier",
"state_key": "post_apply_verifier_reviewed",
"accepted": true,
"required_fields": [
"post_apply_verifier_ref"
],
"next_gate": "future runtime action requires production post-apply verifier readback"
},
{
"item_id": "learning_writeback",
"title": "KM and PlayBook trust writeback",
"state_key": "learning_writeback_reviewed",
"accepted": true,
"required_fields": [
"km_playbook_writeback_ref",
"audit_receipt_ref"
],
"next_gate": "writeback receipt must be committed after verifier"
}
],
"outcome_lanes": [
"accepted_for_runtime_gate_owner_review_readback_only",
"request_runtime_gate_owner_review_supplement",
"request_runtime_gate_owner_review_decision_fix",
"request_target_selector_fix",
"request_runtime_boundary_ack_fix",
"quarantine_sensitive_payload",
"reject_runtime_action_request"
],
"forbidden_payloads": [
"secret_value",
"token_value",
"private_key",
"cookie",
"session",
"authorization_header",
"client.keys",
"raw_wazuh_payload",
"raw_agent_identity",
"raw_hostname",
"internal_ip",
"full_cli_output",
"full_journal",
"raw_dashboard_request",
"unredacted_screenshot",
"private_namespace",
"raw_env_file",
"raw_runtime_volume"
],
"forbidden_actions": [
"wazuh_api_live_query",
"wazuh_active_response",
"wazuh_agent_restart",
"wazuh_agent_reenroll",
"wazuh_manager_restart",
"host_write",
"systemd_restart",
"docker_restart",
"nginx_reload",
"firewall_change",
"kali_active_scan",
"credentialed_scan",
"exploit_attempt",
"secret_rotation",
"k8s_apply",
"argocd_sync",
"database_migration",
"force_push",
"repo_ref_delete",
"workflow_trigger"
],
"execution_boundaries": {
"active_scan_authorized": false,
"alertmanager_reload_authorized": false,
"auto_block_authorized": false,
"credentialed_scan_authorized": false,
"firewall_change_authorized": false,
"host_write_authorized": false,
"kali_execute_authorized": false,
"kali_scan_authorized": false,
"nginx_reload_authorized": false,
"production_write_authorized": false,
"runtime_execution_authorized": false,
"runtime_gate_open": false,
"secret_value_collection_allowed": false,
"telegram_send_authorized": false,
"wazuh_active_response_authorized": false,
"wazuh_api_live_query_authorized": false,
"not_authorization": true
},
"no_false_green_rules": [
"Owner review packet accepted does not open runtime gate.",
"Committed owner review readback only records review readiness and does not query live Wazuh.",
"Target selectors are public aliases only and do not authorize host writes.",
"Check-mode and dry-run evidence references do not authorize active response.",
"Maintenance window, rollback, verifier, and writeback readiness must be revalidated before any future runtime action."
]
}