feat(iwooos): expose wazuh manager registry reviewer validation
Some checks failed
Code Review / ai-code-review (push) Successful in 16s
CD Pipeline / tests (push) Successful in 1m37s
CD Pipeline / build-and-deploy (push) Successful in 4m43s
CD Pipeline / post-deploy-checks (push) Has been cancelled
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-27 15:32:32 +08:00
parent 2278fd6c99
commit 5f5a171edd
11 changed files with 1610 additions and 0 deletions

View File

@@ -35,6 +35,9 @@ from src.services.iwooos_wazuh_live_metadata_gate import (
from src.services.iwooos_wazuh_managed_host_coverage import (
load_latest_iwooos_wazuh_managed_host_coverage,
)
from src.services.iwooos_wazuh_manager_registry_reviewer_validation import (
load_latest_iwooos_wazuh_manager_registry_reviewer_validation,
)
from src.services.iwooos_wazuh_owner_evidence_preflight import (
load_latest_iwooos_wazuh_owner_evidence_preflight,
)
@@ -147,6 +150,34 @@ async def get_iwooos_wazuh_managed_host_coverage() -> dict[str, Any]:
) from exc
@router.get(
"/api/v1/iwooos/wazuh-manager-registry-reviewer-validation",
response_model=dict[str, Any],
summary="取得 Wazuh manager registry reviewer validation 只讀讀回",
description=(
"讀取已提交的 Wazuh manager registry reviewer validation contract回傳 owner export "
"必要欄位、reviewer 檢查、evidence slots、結果分流、拒收內容與 0 / false 邊界。"
"此端點不收 raw payload、不查 Wazuh API、不讀主機、不重新註冊 agent、不重啟服務、"
"不保存機密、不啟用主動回應、不改 Nginx / Docker / K8s / firewall。"
),
)
async def get_iwooos_wazuh_manager_registry_reviewer_validation() -> dict[str, Any]:
"""回傳 Wazuh manager registry reviewer validation 公開安全只讀狀態。"""
try:
payload = await asyncio.to_thread(load_latest_iwooos_wazuh_manager_registry_reviewer_validation)
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 manager registry reviewer validation 無效:{exc}",
) from exc
@router.get(
"/api/v1/iwooos/runtime-security-readback",
response_model=dict[str, Any],

View File

@@ -0,0 +1,227 @@
"""
IwoooS Wazuh manager registry reviewer validation readback.
This service exposes a committed reviewer-validation contract for future
owner-provided redacted Wazuh manager registry exports. It never receives raw
payloads, queries Wazuh, reads host data, reads secrets, or authorizes runtime
actions.
"""
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-manager-registry-reviewer-validation.snapshot.json"
_EXPECTED_SCHEMA = "wazuh_manager_registry_reviewer_validation_v1"
_REQUIRED_FALSE_BOUNDARIES = {
"agent_identity_public_display_allowed",
"host_write_authorized",
"internal_ip_public_display_allowed",
"kali_active_scan_authorized",
"raw_wazuh_payload_storage_allowed",
"runtime_execution_authorized",
"secret_value_collection_allowed",
"wazuh_active_response_authorized",
"wazuh_agent_reenroll_authorized",
"wazuh_agent_restart_authorized",
"wazuh_api_live_query_authorized",
"wazuh_manager_restart_authorized",
}
def load_latest_iwooos_wazuh_manager_registry_reviewer_validation(
security_dir: Path | None = None,
) -> dict[str, Any]:
"""Load the public-safe Wazuh manager registry reviewer-validation 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")),
"required_owner_field_count": _int(summary.get("required_owner_field_count")),
"per_host_required_field_count": _int(summary.get("per_host_required_field_count")),
"reviewer_validation_check_count": _int(summary.get("reviewer_validation_check_count")),
"outcome_lane_count": _int(summary.get("outcome_lane_count")),
"evidence_slot_count": _int(summary.get("evidence_slot_count")),
"forbidden_payload_count": _int(summary.get("forbidden_payload_count")),
"forbidden_action_count": _int(summary.get("forbidden_action_count")),
"owner_registry_export_received_count": _int(summary.get("owner_registry_export_received_count")),
"owner_registry_export_accepted_count": _int(summary.get("owner_registry_export_accepted_count")),
"reviewer_validation_ready_count": _int(summary.get("reviewer_validation_ready_count")),
"reviewer_validation_passed_count": _int(summary.get("reviewer_validation_passed_count")),
"reviewer_validation_failed_count": _int(summary.get("reviewer_validation_failed_count")),
"reviewer_validation_quarantined_count": _int(summary.get("reviewer_validation_quarantined_count")),
"manager_registry_accepted_count": _int(summary.get("manager_registry_accepted_count")),
"post_enable_readback_passed_count": _int(summary.get("post_enable_readback_passed_count")),
"runtime_gate_count": _int(summary.get("runtime_gate_count")),
"host_write_authorized_count": _int(summary.get("host_write_authorized_count")),
"active_response_authorized_count": _int(summary.get("active_response_authorized_count")),
"secret_value_collection_allowed_count": _int(summary.get("secret_value_collection_allowed_count")),
}
return {
"schema_version": "iwooos_wazuh_manager_registry_reviewer_validation_readback_v1",
"source_schema_version": snapshot["schema_version"],
"status": snapshot.get("status", "waiting_owner_registry_export_for_reviewer_validation"),
"mode": "committed_validation_contract_readback_no_runtime_no_secret_collection",
"source_refs": [
f"docs/security/{_SNAPSHOT_FILE}",
"scripts/security/wazuh-manager-registry-reviewer-validation.py",
],
"summary": merged_summary,
"expected_scope_aliases": _strings(snapshot.get("expected_scope_aliases")),
"reviewer_validation_checks": _checks(snapshot.get("reviewer_validation_checks")),
"outcome_lanes": _strings(snapshot.get("outcome_lanes")),
"evidence_slots": _evidence_slots(snapshot.get("evidence_slots")),
"forbidden_payloads": _strings(snapshot.get("forbidden_payloads")),
"forbidden_actions": _strings(snapshot.get("forbidden_actions")),
"boundary_markers": _boundary_markers(merged_summary),
"boundaries": {
"wazuh_api_live_query_authorized": False,
"wazuh_agent_reenroll_authorized": False,
"wazuh_agent_restart_authorized": False,
"wazuh_manager_restart_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,
"kali_active_scan_authorized": False,
"runtime_execution_authorized": False,
"not_authorization": True,
},
"no_false_green_rules": _strings(snapshot.get("no_false_green_rules")),
}
def _load_snapshot(directory: Path) -> dict[str, Any]:
path = directory / _SNAPSHOT_FILE
if not path.is_file():
raise FileNotFoundError(f"{path}: Wazuh manager registry reviewer validation 快照不存在")
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 _checks(value: Any) -> list[dict[str, str]]:
if not isinstance(value, list):
return []
checks: list[dict[str, str]] = []
for item in value:
if not isinstance(item, dict):
continue
checks.append(
{
"check_id": str(item.get("check_id", "")),
"title": str(item.get("title", "")),
"required_evidence": str(item.get("required_evidence", "")),
"failure_lane": str(item.get("failure_lane", "")),
}
)
return checks
def _evidence_slots(value: Any) -> list[dict[str, Any]]:
if not isinstance(value, list):
return []
slots: list[dict[str, Any]] = []
for item in value:
if not isinstance(item, dict):
continue
slots.append(
{
"slot_id": str(item.get("slot_id", "")),
"title": str(item.get("title", "")),
"required_fields": _strings(item.get("required_fields")),
"received": item.get("received") is True,
"accepted": item.get("accepted") is True,
"quarantined": item.get("quarantined") is True,
"next_gate": str(item.get("next_gate", "")),
}
)
return slots
def _boundary_markers(summary: dict[str, int]) -> list[str]:
return [
"wazuh_manager_registry_reviewer_validation_visible=true",
f"wazuh_manager_registry_reviewer_validation_expected_scope_alias_count={summary['expected_scope_alias_count']}",
f"wazuh_manager_registry_reviewer_validation_required_owner_field_count={summary['required_owner_field_count']}",
f"wazuh_manager_registry_reviewer_validation_per_host_required_field_count={summary['per_host_required_field_count']}",
f"wazuh_manager_registry_reviewer_validation_check_count={summary['reviewer_validation_check_count']}",
f"wazuh_manager_registry_reviewer_validation_outcome_lane_count={summary['outcome_lane_count']}",
f"wazuh_manager_registry_reviewer_validation_evidence_slot_count={summary['evidence_slot_count']}",
f"wazuh_manager_registry_reviewer_validation_forbidden_payload_count={summary['forbidden_payload_count']}",
f"wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count={summary['owner_registry_export_received_count']}",
f"wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count={summary['owner_registry_export_accepted_count']}",
f"wazuh_manager_registry_reviewer_validation_passed_count={summary['reviewer_validation_passed_count']}",
f"wazuh_manager_registry_reviewer_validation_quarantined_count={summary['reviewer_validation_quarantined_count']}",
f"wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count={summary['manager_registry_accepted_count']}",
f"wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count={summary['post_enable_readback_passed_count']}",
f"wazuh_manager_registry_reviewer_validation_runtime_gate_count={summary['runtime_gate_count']}",
"wazuh_api_live_query_authorized=false",
"wazuh_agent_reenroll_authorized=false",
"wazuh_agent_restart_authorized=false",
"wazuh_active_response_authorized=false",
"host_write_authorized=false",
"raw_wazuh_payload_storage_allowed=false",
"secret_value_collection_allowed=false",
"not_authorization=true",
]
def _require_boundaries(payload: dict[str, Any]) -> None:
summary = _summary(payload)
for key in (
"owner_registry_export_received_count",
"owner_registry_export_accepted_count",
"reviewer_validation_ready_count",
"reviewer_validation_passed_count",
"reviewer_validation_failed_count",
"reviewer_validation_quarantined_count",
"manager_registry_accepted_count",
"post_enable_readback_passed_count",
"runtime_gate_count",
"host_write_authorized_count",
"active_response_authorized_count",
"secret_value_collection_allowed_count",
):
if _int(summary.get(key)) != 0:
raise ValueError(f"Wazuh manager registry reviewer validation summary.{key} 必須維持 0")
boundaries = payload.get("execution_boundaries")
if not isinstance(boundaries, dict):
raise ValueError("Wazuh manager registry reviewer validation execution_boundaries 缺失")
for key in _REQUIRED_FALSE_BOUNDARIES:
if boundaries.get(key) is not False:
raise ValueError(f"Wazuh manager registry reviewer validation execution_boundaries.{key} 必須維持 false")
if boundaries.get("not_authorization") is not True:
raise ValueError("Wazuh manager registry reviewer validation not_authorization 必須維持 true")

View File

@@ -0,0 +1,96 @@
from __future__ import annotations
from fastapi import FastAPI
from fastapi.testclient import TestClient
from src.api.v1.iwooos import router
from src.services.iwooos_wazuh_manager_registry_reviewer_validation import (
load_latest_iwooos_wazuh_manager_registry_reviewer_validation,
)
def _client() -> TestClient:
app = FastAPI()
app.include_router(router)
return TestClient(app)
def test_iwooos_wazuh_manager_registry_reviewer_validation_contract_is_waiting_only() -> None:
payload = load_latest_iwooos_wazuh_manager_registry_reviewer_validation()
assert payload["schema_version"] == "iwooos_wazuh_manager_registry_reviewer_validation_readback_v1"
assert payload["source_schema_version"] == "wazuh_manager_registry_reviewer_validation_v1"
assert payload["status"] == "waiting_owner_registry_export_for_reviewer_validation"
assert payload["mode"] == "committed_validation_contract_readback_no_runtime_no_secret_collection"
assert payload["summary"]["expected_scope_alias_count"] == 6
assert payload["summary"]["required_owner_field_count"] == 28
assert payload["summary"]["per_host_required_field_count"] == 9
assert payload["summary"]["reviewer_validation_check_count"] == 10
assert payload["summary"]["outcome_lane_count"] == 13
assert payload["summary"]["evidence_slot_count"] == 6
assert payload["summary"]["forbidden_payload_count"] == 27
assert payload["summary"]["owner_registry_export_received_count"] == 0
assert payload["summary"]["owner_registry_export_accepted_count"] == 0
assert payload["summary"]["reviewer_validation_passed_count"] == 0
assert payload["summary"]["reviewer_validation_quarantined_count"] == 0
assert payload["summary"]["manager_registry_accepted_count"] == 0
assert payload["summary"]["runtime_gate_count"] == 0
def test_iwooos_wazuh_manager_registry_reviewer_validation_evidence_slots_are_closed() -> None:
payload = load_latest_iwooos_wazuh_manager_registry_reviewer_validation()
assert [item["slot_id"] for item in payload["evidence_slots"]] == [
"manager_registry_agent_counts",
"per_host_agent_scope_matrix",
"dashboard_api_rbac_tls_repair_readback",
"readonly_credential_metadata_without_secret",
"owner_response_and_rollback_owner",
"post_enable_iwooos_readback",
]
assert all(item["received"] is False for item in payload["evidence_slots"])
assert all(item["accepted"] is False for item in payload["evidence_slots"])
assert all(item["quarantined"] is False for item in payload["evidence_slots"])
assert "managed_core_node_a" in payload["expected_scope_aliases"]
assert "manager_registry_agent_counts" in [item["slot_id"] for item in payload["evidence_slots"]]
def test_iwooos_wazuh_manager_registry_reviewer_validation_api_is_public_safe() -> None:
response = _client().get("/api/v1/iwooos/wazuh-manager-registry-reviewer-validation")
assert response.status_code == 200
data = response.json()
assert data["schema_version"] == "iwooos_wazuh_manager_registry_reviewer_validation_readback_v1"
assert data["summary"]["owner_registry_export_received_count"] == 0
assert data["summary"]["owner_registry_export_accepted_count"] == 0
assert data["summary"]["manager_registry_accepted_count"] == 0
assert data["summary"]["runtime_gate_count"] == 0
assert len(data["reviewer_validation_checks"]) == 10
assert len(data["evidence_slots"]) == 6
assert any(
marker == "wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=0"
for marker in data["boundary_markers"]
)
assert any(
marker == "wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=0"
for marker in data["boundary_markers"]
)
assert any(
marker == "wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0"
for marker in data["boundary_markers"]
)
assert any(
marker == "wazuh_manager_registry_reviewer_validation_runtime_gate_count=0"
for marker in data["boundary_markers"]
)
assert any(rule.startswith("reviewer validation contract 可見") for rule in data["no_false_green_rules"])
assert data["boundaries"]["runtime_execution_authorized"] is False
assert data["boundaries"]["wazuh_active_response_authorized"] is False
assert data["boundaries"]["host_write_authorized"] is False
assert data["boundaries"]["not_authorization"] is True
assert "192.168.0." not in response.text
assert "工作視窗" not in response.text
assert "批准!繼續" not in response.text
assert "source_thread_id" not in response.text
assert "owenhytsai/" not in response.text
assert "WAZUH_API_PASSWORD" not in response.text

View File

@@ -20788,6 +20788,52 @@
}
}
},
"wazuhManagerRegistryReviewerValidation": {
"eyebrow": "Wazuh manager registry reviewer validation",
"title": "Owner export 進來後,先由 reviewer 驗收脫敏清單",
"subtitle": "這張卡固定 Wazuh manager registry owner export 的驗收規則欄位、計數、公開別名矩陣、Dashboard API 修復讀回、唯讀 credential metadata、拒收內容與下一個 Gate 都先可視化;目前尚未收到、尚未接受,也不開 runtime。",
"loadingBoundary": "正在讀取 Wazuh manager registry reviewer validation API",
"slotReceivedLabel": "已收件",
"slotAcceptedLabel": "已接受",
"slotNextGateLabel": "下一關",
"slotsLoading": "正在讀取 evidence slots。",
"slotsFallback": "Evidence slots 尚未由正式 API 讀回,維持 fallback 停止線。",
"checksLoading": "正在讀取 reviewer checks。",
"checksFallback": "Reviewer checks 尚未由正式 API 讀回,維持 fallback 停止線。",
"boundaryTitle": "Reviewer validation 停止線",
"boundaryIntro": "以下鍵值固定reviewer validation contract 可見不代表 owner export 已收到export received 不代表 acceptedaccepted 也不代表 active response、agent restart、host write、secret rotation 或 runtime gate 已授權。",
"status": {
"loading": "正在讀取 Wazuh manager registry reviewer validation",
"failed": "Wazuh manager registry reviewer validation API 尚未部署或讀取失敗",
"ready": "Wazuh manager registry reviewer validation 已讀回owner export、accepted 與 runtime 仍為 0"
},
"summary": {
"aliases": {
"label": "公開別名",
"detail": "Owner export 必須剛好覆蓋 6 個公開節點別名。"
},
"checks": {
"label": "Reviewer checks",
"detail": "10 個檢查固定欄位、算術、矩陣、Dashboard API 與停止線。"
},
"slots": {
"label": "Evidence slots",
"detail": "6 個 slots 對應 manager counts、逐主機矩陣、Dashboard、credential、owner 與 postcheck。"
},
"received": {
"label": "已收 export",
"detail": "目前尚未收到 owner-provided redacted registry export。"
},
"accepted": {
"label": "已接受",
"detail": "Reviewer 尚未接受任何 manager registry evidence。"
},
"runtime": {
"label": "執行期",
"detail": "Runtime gate、active response、agent restart 與 host write 全部維持 0。"
}
}
},
"wazuhLiveMetadataEnvGate": {
"eyebrow": "Wazuh 即時中繼資料環境閘門",
"title": "Wazuh 查詢要等正式路由、負責人與機密中繼資料都過關",

View File

@@ -20788,6 +20788,52 @@
}
}
},
"wazuhManagerRegistryReviewerValidation": {
"eyebrow": "Wazuh manager registry reviewer validation",
"title": "Owner export 進來後,先由 reviewer 驗收脫敏清單",
"subtitle": "這張卡固定 Wazuh manager registry owner export 的驗收規則欄位、計數、公開別名矩陣、Dashboard API 修復讀回、唯讀 credential metadata、拒收內容與下一個 Gate 都先可視化;目前尚未收到、尚未接受,也不開 runtime。",
"loadingBoundary": "正在讀取 Wazuh manager registry reviewer validation API",
"slotReceivedLabel": "已收件",
"slotAcceptedLabel": "已接受",
"slotNextGateLabel": "下一關",
"slotsLoading": "正在讀取 evidence slots。",
"slotsFallback": "Evidence slots 尚未由正式 API 讀回,維持 fallback 停止線。",
"checksLoading": "正在讀取 reviewer checks。",
"checksFallback": "Reviewer checks 尚未由正式 API 讀回,維持 fallback 停止線。",
"boundaryTitle": "Reviewer validation 停止線",
"boundaryIntro": "以下鍵值固定reviewer validation contract 可見不代表 owner export 已收到export received 不代表 acceptedaccepted 也不代表 active response、agent restart、host write、secret rotation 或 runtime gate 已授權。",
"status": {
"loading": "正在讀取 Wazuh manager registry reviewer validation",
"failed": "Wazuh manager registry reviewer validation API 尚未部署或讀取失敗",
"ready": "Wazuh manager registry reviewer validation 已讀回owner export、accepted 與 runtime 仍為 0"
},
"summary": {
"aliases": {
"label": "公開別名",
"detail": "Owner export 必須剛好覆蓋 6 個公開節點別名。"
},
"checks": {
"label": "Reviewer checks",
"detail": "10 個檢查固定欄位、算術、矩陣、Dashboard API 與停止線。"
},
"slots": {
"label": "Evidence slots",
"detail": "6 個 slots 對應 manager counts、逐主機矩陣、Dashboard、credential、owner 與 postcheck。"
},
"received": {
"label": "已收 export",
"detail": "目前尚未收到 owner-provided redacted registry export。"
},
"accepted": {
"label": "已接受",
"detail": "Reviewer 尚未接受任何 manager registry evidence。"
},
"runtime": {
"label": "執行期",
"detail": "Runtime gate、active response、agent restart 與 host write 全部維持 0。"
}
}
},
"wazuhLiveMetadataEnvGate": {
"eyebrow": "Wazuh 即時中繼資料環境閘門",
"title": "Wazuh 查詢要等正式路由、負責人與機密中繼資料都過關",

View File

@@ -44,6 +44,7 @@ import {
type IwoooSSecurityControlCoverageResponse,
type IwoooSWazuhLiveMetadataGateItem,
type IwoooSWazuhLiveMetadataGateResponse,
type IwoooSWazuhManagerRegistryReviewerValidationResponse,
type IwoooSWazuhManagedHostCoverageResponse,
type IwoooSWazuhOwnerEvidencePreflightItem,
type IwoooSWazuhOwnerEvidencePreflightResponse,
@@ -2471,6 +2472,32 @@ const wazuhManagedHostCoverageBoundaries = [
'not_authorization=true',
] as const
const wazuhManagerRegistryReviewerValidationBoundaries = [
'wazuh_manager_registry_reviewer_validation_visible=true',
'wazuh_manager_registry_reviewer_validation_expected_scope_alias_count=6',
'wazuh_manager_registry_reviewer_validation_required_owner_field_count=28',
'wazuh_manager_registry_reviewer_validation_per_host_required_field_count=9',
'wazuh_manager_registry_reviewer_validation_check_count=10',
'wazuh_manager_registry_reviewer_validation_outcome_lane_count=13',
'wazuh_manager_registry_reviewer_validation_evidence_slot_count=6',
'wazuh_manager_registry_reviewer_validation_forbidden_payload_count=27',
'wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=0',
'wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=0',
'wazuh_manager_registry_reviewer_validation_passed_count=0',
'wazuh_manager_registry_reviewer_validation_quarantined_count=0',
'wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0',
'wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=0',
'wazuh_manager_registry_reviewer_validation_runtime_gate_count=0',
'wazuh_api_live_query_authorized=false',
'wazuh_agent_reenroll_authorized=false',
'wazuh_agent_restart_authorized=false',
'wazuh_active_response_authorized=false',
'host_write_authorized=false',
'raw_wazuh_payload_storage_allowed=false',
'secret_value_collection_allowed=false',
'not_authorization=true',
] as const
const securityOperatingSystemSummary = [
{ key: 'frameworks', value: '20', icon: ClipboardCheck, tone: 'steady' },
{ key: 'workstreams', value: '24', icon: ListChecks, tone: 'steady' },
@@ -9738,6 +9765,241 @@ function IwoooSWazuhManagedHostCoverageBoard() {
)
}
function IwoooSWazuhManagerRegistryReviewerValidationBoard() {
const t = useTranslations('iwooos.wazuhManagerRegistryReviewerValidation')
const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const }
const [data, setData] = useState<IwoooSWazuhManagerRegistryReviewerValidationResponse | null>(null)
const [loading, setLoading] = useState(true)
const [failed, setFailed] = useState(false)
useEffect(() => {
let mounted = true
async function loadValidation() {
setLoading(true)
setFailed(false)
try {
const payload = await apiClient.getIwoooSWazuhManagerRegistryReviewerValidation()
if (mounted) {
setData(payload)
}
} catch {
if (mounted) {
setData(null)
setFailed(true)
}
} finally {
if (mounted) {
setLoading(false)
}
}
}
loadValidation()
return () => {
mounted = false
}
}, [])
const summary = data?.summary
const summaryItems = [
{
key: 'aliases',
value: summary ? String(summary.expected_scope_alias_count) : loading ? '...' : '6',
icon: Server,
tone: 'warn',
},
{
key: 'checks',
value: summary ? String(summary.reviewer_validation_check_count) : loading ? '...' : '10',
icon: ListChecks,
tone: 'steady',
},
{
key: 'slots',
value: summary ? String(summary.evidence_slot_count) : loading ? '...' : '6',
icon: ClipboardCheck,
tone: 'warn',
},
{
key: 'received',
value: summary ? String(summary.owner_registry_export_received_count) : loading ? '...' : '0',
icon: FileWarning,
tone: 'locked',
},
{
key: 'accepted',
value: summary ? String(summary.owner_registry_export_accepted_count) : loading ? '...' : '0',
icon: Lock,
tone: 'locked',
},
{
key: 'runtime',
value: summary ? String(summary.runtime_gate_count) : loading ? '...' : '0',
icon: ShieldCheck,
tone: 'locked',
},
] as const
const boundaryMarkers = data?.boundary_markers?.length
? data.boundary_markers
: loading
? [t('loadingBoundary')]
: wazuhManagerRegistryReviewerValidationBoundaries
const evidenceSlots = data?.evidence_slots ?? []
const visibleChecks = data?.reviewer_validation_checks?.slice(0, 4) ?? []
const statusText = loading ? t('status.loading') : failed ? t('status.failed') : t('status.ready')
const statusTone: 'steady' | 'warn' | 'locked' = loading || failed ? 'warn' : 'locked'
return (
<section
style={{ marginBottom: 14, maxWidth: '100%', overflow: 'hidden' }}
data-testid="iwooos-wazuh-manager-registry-reviewer-validation-board"
>
<div style={{ ...band, padding: 16, background: '#f9fbfb', borderColor: '#c8d8d9' }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 300px), 1fr))', gap: 14 }}>
<div style={{ minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: '#2f6265', fontSize: 12, fontWeight: 700 }}>
<SearchCheck size={17} color="#2d7478" />
{t('eyebrow')}
</div>
<h2 style={{ fontSize: 17, margin: '8px 0 0', color: '#141413' }}>{t('title')}</h2>
<p style={{ fontSize: 12, color: '#45686a', 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 }}>
{summaryItems.map(item => {
const Icon = item.icon
return (
<div key={item.key} style={{ border: '0.5px solid #c8d8d9', borderRadius: 8, padding: 12, background: '#fff' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ fontSize: 11, color: '#5e7779' }}>{t(`summary.${item.key}.label` as never)}</span>
<Icon size={16} color={toneColors[item.tone]} />
</div>
<div style={{ fontSize: 19, fontWeight: 700, color: toneColors[item.tone], lineHeight: 1.1, marginTop: 8, ...textWrap }}>
{item.value}
</div>
<p style={{ fontSize: 11, color: '#45686a', lineHeight: 1.45, margin: '8px 0 0', ...textWrap }}>
{t(`summary.${item.key}.detail` as never)}
</p>
</div>
)
})}
</div>
</div>
<div
data-testid="iwooos-wazuh-manager-registry-reviewer-validation-slots"
style={{
marginTop: 14,
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 220px), 1fr))',
gap: 8,
}}
>
{evidenceSlots.length ? evidenceSlots.map(slot => (
<div
key={slot.slot_id}
style={{
border: '0.5px solid #c8d8d9',
borderRadius: 8,
background: '#fff',
padding: 11,
minWidth: 0,
...textWrap,
}}
>
<code style={{ fontSize: 11, color: '#2f6265', fontWeight: 700, overflowWrap: 'anywhere' }}>{slot.slot_id}</code>
<div style={{ marginTop: 7, fontSize: 12, color: '#141413', fontWeight: 700 }}>{slot.title}</div>
<div style={{ marginTop: 7, display: 'grid', gap: 4, fontSize: 11, color: '#45686a' }}>
<span>{t('slotReceivedLabel')}0</span>
<span>{t('slotAcceptedLabel')}0</span>
<span>{t('slotNextGateLabel')}{slot.next_gate}</span>
</div>
</div>
)) : (
<div style={{ border: '0.5px solid #c8d8d9', borderRadius: 8, background: '#fff', padding: 11, color: '#45686a', fontSize: 12 }}>
{loading ? t('slotsLoading') : t('slotsFallback')}
</div>
)}
</div>
<div
data-testid="iwooos-wazuh-manager-registry-reviewer-validation-checks"
style={{
marginTop: 12,
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))',
gap: 8,
}}
>
{visibleChecks.length ? visibleChecks.map(check => (
<div key={check.check_id} style={{ border: '0.5px solid #c8d8d9', borderRadius: 8, background: '#f9fbfb', padding: 11, minWidth: 0, ...textWrap }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ color: '#2f6265', fontSize: 12, fontWeight: 700 }}>{check.check_id}</span>
<ListChecks size={16} color="#2d7478" />
</div>
<div style={{ marginTop: 7, fontSize: 12, color: '#141413', fontWeight: 700 }}>{check.title}</div>
<p style={{ margin: '6px 0 0', fontSize: 11, color: '#45686a', lineHeight: 1.45, ...textWrap }}>
{check.required_evidence}
</p>
<code style={{ display: 'block', marginTop: 7, fontSize: 10.5, color: '#2f6265', overflowWrap: 'anywhere' }}>
{check.failure_lane}
</code>
</div>
)) : (
<div style={{ border: '0.5px solid #c8d8d9', borderRadius: 8, background: '#f9fbfb', padding: 11, color: '#45686a', fontSize: 12 }}>
{loading ? t('checksLoading') : t('checksFallback')}
</div>
)}
</div>
<details
data-testid="iwooos-wazuh-manager-registry-reviewer-validation-boundaries"
style={{
marginTop: 12,
border: '0.5px solid #c8d8d9',
borderRadius: 8,
background: '#fff',
padding: '8px 10px',
}}
>
<summary style={{ cursor: 'pointer', fontSize: 12, fontWeight: 700, color: '#2f6265' }}>
{t('boundaryTitle')}
</summary>
<p style={{ fontSize: 11, color: '#45686a', lineHeight: 1.5, margin: '8px 0', ...textWrap }}>
{t('boundaryIntro')}
</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(230px, 1fr))', gap: 6 }}>
{boundaryMarkers.map(item => (
<code
key={item}
style={{
border: '0.5px solid #c8d8d9',
borderRadius: 8,
padding: '6px 8px',
color: '#45686a',
fontSize: 11,
lineHeight: 1.4,
background: '#f9fbfb',
overflowWrap: 'anywhere',
}}
>
{item}
</code>
))}
</div>
</details>
</div>
</section>
)
}
function IwoooSSecurityOperatingSystemBoard() {
const t = useTranslations('iwooos.securityOperatingSystem')
const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const }
@@ -23219,6 +23481,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) {
<IwoooSWazuhReleaseGateBoard />
<IwoooSWazuhOwnerEvidencePreflightBoard />
<IwoooSWazuhManagedHostCoverageBoard />
<IwoooSWazuhManagerRegistryReviewerValidationBoard />
<IwoooSWazuhLiveMetadataEnvGateBoard />
<IwoooSSecurityOperatingSystemBoard />
<IwoooSSocSiemKaliWazuhIntegrationBoard />

View File

@@ -318,6 +318,62 @@ export interface IwoooSWazuhManagedHostCoverageResponse {
no_false_green_rules: string[]
}
export interface IwoooSWazuhManagerRegistryReviewerValidationCheck {
check_id: string
title: string
required_evidence: string
failure_lane: string
}
export interface IwoooSWazuhManagerRegistryReviewerValidationSlot {
slot_id: string
title: string
required_fields: string[]
received: boolean
accepted: boolean
quarantined: boolean
next_gate: string
}
export interface IwoooSWazuhManagerRegistryReviewerValidationResponse {
schema_version: 'iwooos_wazuh_manager_registry_reviewer_validation_readback_v1'
source_schema_version: 'wazuh_manager_registry_reviewer_validation_v1'
status: string
mode: string
source_refs: string[]
summary: {
expected_scope_alias_count: number
required_owner_field_count: number
per_host_required_field_count: number
reviewer_validation_check_count: number
outcome_lane_count: number
evidence_slot_count: number
forbidden_payload_count: number
forbidden_action_count: number
owner_registry_export_received_count: number
owner_registry_export_accepted_count: number
reviewer_validation_ready_count: number
reviewer_validation_passed_count: number
reviewer_validation_failed_count: number
reviewer_validation_quarantined_count: number
manager_registry_accepted_count: number
post_enable_readback_passed_count: number
runtime_gate_count: number
host_write_authorized_count: number
active_response_authorized_count: number
secret_value_collection_allowed_count: number
}
expected_scope_aliases: string[]
reviewer_validation_checks: IwoooSWazuhManagerRegistryReviewerValidationCheck[]
outcome_lanes: string[]
evidence_slots: IwoooSWazuhManagerRegistryReviewerValidationSlot[]
forbidden_payloads: string[]
forbidden_actions: string[]
boundary_markers: string[]
boundaries: Record<string, boolean>
no_false_green_rules: string[]
}
export interface IwoooSSecurityControlCoverageDomain {
domain_id:
| 'high_value_asset_control'
@@ -563,6 +619,11 @@ export const apiClient = {
return handleResponse<IwoooSWazuhManagedHostCoverageResponse>(res)
},
async getIwoooSWazuhManagerRegistryReviewerValidation() {
const res = await fetch(`${API_BASE_URL}/iwooos/wazuh-manager-registry-reviewer-validation`, { cache: 'no-store' })
return handleResponse<IwoooSWazuhManagerRegistryReviewerValidationResponse>(res)
},
async getIwoooSSecurityControlCoverage() {
const res = await fetch(`${API_BASE_URL}/iwooos/security-control-coverage`, { cache: 'no-store' })
return handleResponse<IwoooSSecurityControlCoverageResponse>(res)

View File

@@ -1,3 +1,43 @@
## 2026-06-27IwoooS Wazuh manager registry reviewer validation 本地完成
**背景**Wazuh 受管主機覆蓋已能由正式 API / 前台讀回,但 `manager_registry_accepted_count` 仍為 `0`。本段補上 owner-provided redacted manager registry export 進來後的 reviewer validation contract避免把 Dashboard 可開、index pattern 正常、HTTP 200、agent transport observed 或前台卡片可見誤判為全主機已納管。
**完成內容**
- 新增 committed snapshot`docs/security/wazuh-manager-registry-reviewer-validation.snapshot.json`
- 新增只讀 guard`scripts/security/wazuh-manager-registry-reviewer-validation.py`,驗證 6 個公開別名、28 個 owner 欄位、9 個逐主機欄位、10 個 reviewer checks、13 條 outcome lanes、6 個 evidence slots 與 27 類 forbidden payload。
- 新增 API`GET /api/v1/iwooos/wazuh-manager-registry-reviewer-validation`schema 為 `iwooos_wazuh_manager_registry_reviewer_validation_readback_v1`
- `/zh-TW/iwooos` 新增 reviewer validation 卡,讀取 API 後顯示 evidence slots、reviewer checks 與 no-false-green boundary markersAPI 未回時只顯示保守 fallback。
- `security-mirror-progress-guard.py` 已納入 API route、service schema、client method、前台測試 id、i18n 文案與 `owner_registry_export_received_count=0``owner_registry_export_accepted_count=0``manager_registry_accepted_count=0``runtime_gate_count=0` source markers。
- `zh-TW` 與目前鏡像訊息皆為繁體中文;未放入工作視窗對話、個人 namespace、內網位址、agent 原名或 secret。
**本地驗證結果**
- `python3 -m py_compile scripts/security/wazuh-manager-registry-reviewer-validation.py apps/api/src/services/iwooos_wazuh_manager_registry_reviewer_validation.py apps/api/src/api/v1/iwooos.py scripts/security/security-mirror-progress-guard.py`:通過。
- `DATABASE_URL=sqlite:///test.db python3.11 -m pytest apps/api/tests/test_iwooos_wazuh_manager_registry_reviewer_validation.py apps/api/tests/test_iwooos_wazuh_managed_host_coverage.py apps/api/tests/test_iwooos_runtime_security_readback.py apps/api/tests/test_iwooos_wazuh_api.py -q``18 passed`
- `node -e "JSON.parse(...zh-TW.json); JSON.parse(...en.json)"`:通過。
- `python3 scripts/security/wazuh-manager-registry-reviewer-validation.py --root .``WAZUH_MANAGER_REGISTRY_REVIEWER_VALIDATION_OK aliases=6 checks=10 slots=6 received=0 accepted=0 runtime_gate=0`
- `python3 scripts/security/iwooos-frontend-display-redaction-guard.py --root .``IWOOOS_FRONTEND_DISPLAY_REDACTION_GUARD_OK`
- `python3 scripts/security/security-mirror-progress-guard.py --root .``SECURITY_MIRROR_PROGRESS_GUARD_OK`
- `pnpm --dir apps/web typecheck`:通過。
- `git diff --check`:通過。
**完成度 / 邊界**
- 本段「Wazuh manager registry reviewer validation contract」本地`0% -> 85%`。尚待 rebase、commit、push、CD、production API readback 與 desktop / mobile browser smoke。
- IwoooS 整體暫保守維持 `68%`,等正式讀回完成後再上修。
- Wazuh manager registry accepted`35% -> 55%`,目前只是 reviewer validation contract 與前台讀回準備完成,不代表 owner export received / accepted 或所有主機已納管。
**仍維持 0 / false**
- `owner_registry_export_received_count=0``owner_registry_export_accepted_count=0``reviewer_validation_passed_count=0``reviewer_validation_quarantined_count=0``manager_registry_accepted_count=0``post_enable_readback_passed_count=0``runtime_gate_count=0`
- `wazuh_api_live_query_authorized=false``wazuh_agent_reenroll_authorized=false``wazuh_agent_restart_authorized=false``wazuh_manager_restart_authorized=false``wazuh_active_response_authorized=false``host_write_authorized=false``secret_value_collection_allowed=false``raw_wazuh_payload_storage_allowed=false``kali_active_scan_authorized=false``runtime_execution_authorized=false``not_authorization=true`
**做過的命令類型**
- 寫入repo API / test / frontend / i18n / guard / snapshot / LOGBOOK。
- 只讀git fetch / diff、repo snapshot 驗證、本地測試與型別檢查。
- 未做:沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有讀 secret 明文;沒有重新註冊 agent沒有 Wazuh restart沒有 Wazuh active response沒有 Kali active scan沒有 force push。
**下一步**
- rebase 到最新 `gitea/main` 後 commit / push等待 Gitea CD。
- production 驗證 `GET /api/v1/iwooos/wazuh-manager-registry-reviewer-validation``/zh-TW/iwooos` desktop / mobile、forbidden hits、console errors 與水平溢出。
## 2026-06-27D1M 正式站驗證:修復候選已寫入 check-mode worker 可 claim receipt
**正式部署基準 / Runs**

View File

@@ -0,0 +1,308 @@
{
"evidence_slots": [
{
"accepted": false,
"next_gate": "owner_provided_redacted_export",
"quarantined": false,
"received": false,
"required_fields": [
"agent_total",
"agent_active",
"agent_disconnected",
"agent_never_connected",
"registry_export_summary_ref"
],
"slot_id": "manager_registry_agent_counts",
"title": "Manager registry agent counts"
},
{
"accepted": false,
"next_gate": "owner_provided_redacted_export",
"quarantined": false,
"received": false,
"required_fields": [
"registry_export_scope_aliases",
"per_host_registry_matrix",
"registry_gap_reason_by_alias"
],
"slot_id": "per_host_agent_scope_matrix",
"title": "逐主機 agent scope matrix"
},
{
"accepted": false,
"next_gate": "owner_provided_redacted_export",
"quarantined": false,
"received": false,
"required_fields": [
"dashboard_api_connection_check_status",
"dashboard_api_version_check_status",
"dashboard_index_pattern_statuses",
"dashboard_api_degradation_root_cause",
"dashboard_api_repair_postcheck_ref"
],
"slot_id": "dashboard_api_rbac_tls_repair_readback",
"title": "Dashboard API / RBAC / TLS 修復讀回"
},
{
"accepted": false,
"next_gate": "owner_provided_redacted_export",
"quarantined": false,
"received": false,
"required_fields": [
"collection_method",
"manager_health_ref",
"redacted_evidence_refs"
],
"slot_id": "readonly_credential_metadata_without_secret",
"title": "唯讀 credential metadata不含 secret"
},
{
"accepted": false,
"next_gate": "owner_provided_redacted_export",
"quarantined": false,
"received": false,
"required_fields": [
"owner_role",
"team",
"decision",
"decision_reason",
"followup_owner",
"rollback_owner"
],
"slot_id": "owner_response_and_rollback_owner",
"title": "Owner response / rollback owner"
},
{
"accepted": false,
"next_gate": "owner_provided_redacted_export",
"quarantined": false,
"received": false,
"required_fields": [
"postcheck_plan",
"redacted_evidence_refs"
],
"slot_id": "post_enable_iwooos_readback",
"title": "Post-enable IwoooS readback"
}
],
"execution_boundaries": {
"agent_identity_public_display_allowed": false,
"host_write_authorized": false,
"internal_ip_public_display_allowed": false,
"kali_active_scan_authorized": false,
"not_authorization": true,
"raw_wazuh_payload_storage_allowed": false,
"runtime_execution_authorized": false,
"secret_value_collection_allowed": false,
"wazuh_active_response_authorized": false,
"wazuh_agent_reenroll_authorized": false,
"wazuh_agent_restart_authorized": false,
"wazuh_api_live_query_authorized": false,
"wazuh_manager_restart_authorized": false
},
"expected_scope_aliases": [
"managed_core_node_a",
"managed_core_node_b",
"managed_dev_node_a",
"managed_dev_node_b",
"managed_control_node_a",
"managed_control_node_b"
],
"forbidden_actions": [
"wazuh_api_live_query",
"wazuh_agent_reenroll",
"wazuh_agent_restart",
"wazuh_manager_restart",
"wazuh_active_response",
"wazuh_dashboard_secret_patch",
"host_write",
"firewall_change",
"nginx_reload",
"k8s_or_argocd_change",
"kali_active_scan"
],
"forbidden_payloads": [
"raw_wazuh_payload",
"raw_log",
"full_journal",
"full_cli_output",
"unredacted_screenshot",
"agent_name",
"agent_id_plaintext",
"internal_ip",
"hostname",
"authorization_header",
"bearer_token",
"basic_auth",
"password",
"token",
"cookie",
"private_key",
"client_keys",
"raw_dashboard_request",
"dashboard_api_secret",
"stored_api_password",
"api_token",
"active_response_enable",
"agent_reenroll",
"agent_restart",
"host_write",
"firewall_change",
"nginx_reload"
],
"generated_at": "2026-06-27T15:24:00+08:00",
"mode": "committed_validation_contract_no_runtime_no_secret_collection",
"no_false_green_rules": [
"reviewer validation contract 可見不代表 owner registry export 已收到。",
"owner registry export received 不代表 manager_registry_accepted_count 可增加。",
"Dashboard 可見、index pattern 三綠勾、HTTP 200 或 transport observed 不可替代 manager registry counts。",
"reviewer accepted 只可更新只讀 postureactive response、agent restart、reenroll、host write、secret rotation 或掃描仍需獨立 runtime gate。"
],
"outcome_lanes": [
"waiting_owner_registry_export",
"request_missing_fields",
"request_counts_arithmetic_fix",
"request_alias_scope_parity_fix",
"request_per_host_matrix_supplement",
"request_dashboard_api_repair_postcheck",
"request_readonly_credential_metadata",
"request_owner_accountability_supplement",
"quarantine_sensitive_payload",
"reject_runtime_action_request",
"ready_for_reviewer_validation",
"accepted_for_readonly_posture_only",
"waiting_post_enable_iwooos_readback"
],
"per_host_required_fields": [
"node_alias",
"scope_role",
"registry_presence",
"agent_status_bucket",
"last_seen_state",
"manager_group_ref",
"agent_id_redacted_ref",
"gap_reason",
"redacted_evidence_ref"
],
"required_owner_fields": [
"owner_role",
"team",
"decision",
"decision_reason",
"affected_scope",
"collection_method",
"agent_total",
"agent_active",
"agent_disconnected",
"agent_never_connected",
"last_seen_window_start",
"last_seen_window_end",
"registry_collected_at",
"registry_export_scope_aliases",
"per_host_registry_matrix",
"registry_gap_reason_by_alias",
"registry_export_summary_ref",
"manager_health_ref",
"dashboard_api_status_ref",
"dashboard_api_connection_check_status",
"dashboard_api_version_check_status",
"dashboard_index_pattern_statuses",
"dashboard_api_degradation_root_cause",
"dashboard_api_repair_postcheck_ref",
"redacted_evidence_refs",
"followup_owner",
"rollback_owner",
"postcheck_plan"
],
"reviewer_validation_checks": [
{
"check_id": "RV-01",
"failure_lane": "request_missing_fields",
"required_evidence": "owner_role、team、decision、decision_reason、affected_scope、collection_method 與 redacted evidence refs 必須存在。",
"title": "Export envelope 欄位齊全"
},
{
"check_id": "RV-02",
"failure_lane": "request_counts_arithmetic_fix",
"required_evidence": "agent_total 不得小於 active + disconnected + never_connected且需有 registry_export_summary_ref。",
"title": "Registry counts 算術一致"
},
{
"check_id": "RV-03",
"failure_lane": "request_alias_scope_parity_fix",
"required_evidence": "registry_export_scope_aliases 必須剛好覆蓋 6 個公開別名,不得加入真實主機名或內網識別。",
"title": "Alias scope 與 IwoooS 覆蓋矩陣一致"
},
{
"check_id": "RV-04",
"failure_lane": "request_per_host_matrix_supplement",
"required_evidence": "每個公開節點別名都要有 9 個 per-host 欄位與 gap reason。",
"title": "逐主機矩陣欄位完整"
},
{
"check_id": "RV-05",
"failure_lane": "request_dashboard_api_repair_postcheck",
"required_evidence": "API connection、API version、index pattern、degradation root cause 與 repair postcheck 需分欄。",
"title": "Dashboard API 狀態不可用 index pattern 代替"
},
{
"check_id": "RV-06",
"failure_lane": "request_readonly_credential_metadata",
"required_evidence": "manager_health_ref 與 readonly credential metadata 只能是脫敏來源資訊,不得含 secret value。",
"title": "Manager health 與 readonly credential metadata 可追溯"
},
{
"check_id": "RV-07",
"failure_lane": "quarantine_sensitive_payload",
"required_evidence": "不得含 raw Wazuh payload、完整 CLI output、未脫敏截圖、agent 原名、內網位址、token、密碼或 client key。",
"title": "Forbidden payload 一律隔離"
},
{
"check_id": "RV-08",
"failure_lane": "request_owner_accountability_supplement",
"required_evidence": "followup_owner、rollback_owner、維護窗口與 postcheck plan 必須能被 reviewer 追蹤。",
"title": "Owner / followup / rollback 責任可讀"
},
{
"check_id": "RV-09",
"failure_lane": "reject_runtime_action_request",
"required_evidence": "owner decision 不可夾帶 active response、agent restart、reenroll、host write、Nginx、firewall、K8s 或 secret rotation。",
"title": "收件不等於 runtime 授權"
},
{
"check_id": "RV-10",
"failure_lane": "waiting_post_enable_iwooos_readback",
"required_evidence": "即使 reviewer 未來接受 evidence也只能進 read-only posture必須另有 post-enable readback 才能更新 runtime truth。",
"title": "Post-enable IwoooS readback 仍是下一關"
}
],
"schema_version": "wazuh_manager_registry_reviewer_validation_v1",
"scope": "wazuh_manager_registry_owner_export_reviewer_validation",
"source_refs": [
"docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json",
"docs/security/wazuh-managed-host-coverage-gate.snapshot.json"
],
"status": "waiting_owner_registry_export_for_reviewer_validation",
"summary": {
"active_response_authorized_count": 0,
"evidence_slot_count": 6,
"expected_scope_alias_count": 6,
"forbidden_action_count": 11,
"forbidden_payload_count": 27,
"host_write_authorized_count": 0,
"manager_registry_accepted_count": 0,
"outcome_lane_count": 13,
"owner_registry_export_accepted_count": 0,
"owner_registry_export_received_count": 0,
"per_host_required_field_count": 9,
"post_enable_readback_passed_count": 0,
"required_owner_field_count": 28,
"reviewer_validation_check_count": 10,
"reviewer_validation_failed_count": 0,
"reviewer_validation_passed_count": 0,
"reviewer_validation_quarantined_count": 0,
"reviewer_validation_ready_count": 0,
"runtime_gate_count": 0,
"secret_value_collection_allowed_count": 0
}
}

View File

@@ -127,6 +127,10 @@ def validate(root: Path) -> None:
str(root / "scripts" / "security" / "wazuh-managed-host-coverage-gate.py")
)
wazuh_managed_host_coverage_gate["validate"](root)
wazuh_manager_registry_reviewer_validation = runpy.run_path(
str(root / "scripts" / "security" / "wazuh-manager-registry-reviewer-validation.py")
)
wazuh_manager_registry_reviewer_validation["validate"](root)
telegram_alert_readability_guard = runpy.run_path(
str(root / "scripts" / "security" / "telegram-alert-readability-guard.py")
)
@@ -345,6 +349,15 @@ def validate(root: Path) -> None:
iwooos_wazuh_managed_host_coverage_test = (
root / "apps" / "api" / "tests" / "test_iwooos_wazuh_managed_host_coverage.py"
).read_text(encoding="utf-8")
iwooos_wazuh_manager_registry_reviewer_validation_service = (
root / "apps" / "api" / "src" / "services" / "iwooos_wazuh_manager_registry_reviewer_validation.py"
).read_text(encoding="utf-8")
iwooos_wazuh_manager_registry_reviewer_validation_test = (
root / "apps" / "api" / "tests" / "test_iwooos_wazuh_manager_registry_reviewer_validation.py"
).read_text(encoding="utf-8")
wazuh_manager_registry_reviewer_validation_script = (
root / "scripts" / "security" / "wazuh-manager-registry-reviewer-validation.py"
).read_text(encoding="utf-8")
tenants_api_contract = (
root / "apps" / "api" / "src" / "api" / "v1" / "platform" / "tenants.py"
).read_text(encoding="utf-8")
@@ -29529,6 +29542,9 @@ def validate(root: Path) -> None:
iwooos_api_client,
iwooos_wazuh_managed_host_coverage_service,
iwooos_wazuh_managed_host_coverage_test,
iwooos_wazuh_manager_registry_reviewer_validation_service,
iwooos_wazuh_manager_registry_reviewer_validation_test,
wazuh_manager_registry_reviewer_validation_script,
]
)
for expected in [
@@ -29551,6 +29567,16 @@ def validate(root: Path) -> None:
"Wazuh 主機覆蓋只讀 API 已接上",
"wazuh_managed_host_coverage_manager_registry_accepted_count=0",
"wazuh_managed_host_coverage_runtime_gate_count=0",
"iwooos-wazuh-manager-registry-reviewer-validation-board",
"iwooos-wazuh-manager-registry-reviewer-validation-slots",
"wazuhManagerRegistryReviewerValidation",
"getIwoooSWazuhManagerRegistryReviewerValidation",
"apiClient.getIwoooSWazuhManagerRegistryReviewerValidation",
"Wazuh manager registry reviewer validation 已讀回",
"wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=0",
"wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=0",
"wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0",
"wazuh_manager_registry_reviewer_validation_runtime_gate_count=0",
]:
assert_text_contains("iwooos_frontend_product_text.wazuh_managed_host_coverage", frontend_product_text, expected)
for expected in [
@@ -29565,6 +29591,14 @@ def validate(root: Path) -> None:
"wazuh_managed_host_coverage_required_evidence_accepted_count=0",
"wazuh_agent_reenroll_authorized=false",
"wazuh_agent_restart_authorized=false",
"/api/v1/iwooos/wazuh-manager-registry-reviewer-validation",
"wazuh_manager_registry_reviewer_validation_v1",
"iwooos_wazuh_manager_registry_reviewer_validation_readback_v1",
"test_iwooos_wazuh_manager_registry_reviewer_validation_api_is_public_safe",
"wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=0",
"wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=0",
"wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0",
"wazuh_manager_registry_reviewer_validation_runtime_gate_count=0",
]:
assert_text_contains(
"iwooos_wazuh_managed_host_coverage_source",

View File

@@ -0,0 +1,458 @@
#!/usr/bin/env python3
"""
Wazuh manager registry reviewer validation gate.
本工具只驗證 repo 內 committed snapshot不查 Wazuh、不讀 host、不收
secret、不保存 raw payload、不重新註冊 agent、不重啟服務也不啟用
active response。
"""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
from typing import Any
SNAPSHOT_PATH = Path("docs/security/wazuh-manager-registry-reviewer-validation.snapshot.json")
SCHEMA_VERSION = "wazuh_manager_registry_reviewer_validation_v1"
EXPECTED_SCOPE_ALIASES = [
"managed_core_node_a",
"managed_core_node_b",
"managed_dev_node_a",
"managed_dev_node_b",
"managed_control_node_a",
"managed_control_node_b",
]
REQUIRED_OWNER_FIELDS = [
"owner_role",
"team",
"decision",
"decision_reason",
"affected_scope",
"collection_method",
"agent_total",
"agent_active",
"agent_disconnected",
"agent_never_connected",
"last_seen_window_start",
"last_seen_window_end",
"registry_collected_at",
"registry_export_scope_aliases",
"per_host_registry_matrix",
"registry_gap_reason_by_alias",
"registry_export_summary_ref",
"manager_health_ref",
"dashboard_api_status_ref",
"dashboard_api_connection_check_status",
"dashboard_api_version_check_status",
"dashboard_index_pattern_statuses",
"dashboard_api_degradation_root_cause",
"dashboard_api_repair_postcheck_ref",
"redacted_evidence_refs",
"followup_owner",
"rollback_owner",
"postcheck_plan",
]
PER_HOST_REQUIRED_FIELDS = [
"node_alias",
"scope_role",
"registry_presence",
"agent_status_bucket",
"last_seen_state",
"manager_group_ref",
"agent_id_redacted_ref",
"gap_reason",
"redacted_evidence_ref",
]
REVIEWER_VALIDATION_CHECKS = [
{
"check_id": "RV-01",
"title": "Export envelope 欄位齊全",
"required_evidence": "owner_role、team、decision、decision_reason、affected_scope、collection_method 與 redacted evidence refs 必須存在。",
"failure_lane": "request_missing_fields",
},
{
"check_id": "RV-02",
"title": "Registry counts 算術一致",
"required_evidence": "agent_total 不得小於 active + disconnected + never_connected且需有 registry_export_summary_ref。",
"failure_lane": "request_counts_arithmetic_fix",
},
{
"check_id": "RV-03",
"title": "Alias scope 與 IwoooS 覆蓋矩陣一致",
"required_evidence": "registry_export_scope_aliases 必須剛好覆蓋 6 個公開別名,不得加入真實主機名或內網識別。",
"failure_lane": "request_alias_scope_parity_fix",
},
{
"check_id": "RV-04",
"title": "逐主機矩陣欄位完整",
"required_evidence": "每個公開節點別名都要有 9 個 per-host 欄位與 gap reason。",
"failure_lane": "request_per_host_matrix_supplement",
},
{
"check_id": "RV-05",
"title": "Dashboard API 狀態不可用 index pattern 代替",
"required_evidence": "API connection、API version、index pattern、degradation root cause 與 repair postcheck 需分欄。",
"failure_lane": "request_dashboard_api_repair_postcheck",
},
{
"check_id": "RV-06",
"title": "Manager health 與 readonly credential metadata 可追溯",
"required_evidence": "manager_health_ref 與 readonly credential metadata 只能是脫敏來源資訊,不得含 secret value。",
"failure_lane": "request_readonly_credential_metadata",
},
{
"check_id": "RV-07",
"title": "Forbidden payload 一律隔離",
"required_evidence": "不得含 raw Wazuh payload、完整 CLI output、未脫敏截圖、agent 原名、內網位址、token、密碼或 client key。",
"failure_lane": "quarantine_sensitive_payload",
},
{
"check_id": "RV-08",
"title": "Owner / followup / rollback 責任可讀",
"required_evidence": "followup_owner、rollback_owner、維護窗口與 postcheck plan 必須能被 reviewer 追蹤。",
"failure_lane": "request_owner_accountability_supplement",
},
{
"check_id": "RV-09",
"title": "收件不等於 runtime 授權",
"required_evidence": "owner decision 不可夾帶 active response、agent restart、reenroll、host write、Nginx、firewall、K8s 或 secret rotation。",
"failure_lane": "reject_runtime_action_request",
},
{
"check_id": "RV-10",
"title": "Post-enable IwoooS readback 仍是下一關",
"required_evidence": "即使 reviewer 未來接受 evidence也只能進 read-only posture必須另有 post-enable readback 才能更新 runtime truth。",
"failure_lane": "waiting_post_enable_iwooos_readback",
},
]
OUTCOME_LANES = [
"waiting_owner_registry_export",
"request_missing_fields",
"request_counts_arithmetic_fix",
"request_alias_scope_parity_fix",
"request_per_host_matrix_supplement",
"request_dashboard_api_repair_postcheck",
"request_readonly_credential_metadata",
"request_owner_accountability_supplement",
"quarantine_sensitive_payload",
"reject_runtime_action_request",
"ready_for_reviewer_validation",
"accepted_for_readonly_posture_only",
"waiting_post_enable_iwooos_readback",
]
EVIDENCE_SLOTS = [
{
"slot_id": "manager_registry_agent_counts",
"title": "Manager registry agent counts",
"required_fields": ["agent_total", "agent_active", "agent_disconnected", "agent_never_connected", "registry_export_summary_ref"],
},
{
"slot_id": "per_host_agent_scope_matrix",
"title": "逐主機 agent scope matrix",
"required_fields": ["registry_export_scope_aliases", "per_host_registry_matrix", "registry_gap_reason_by_alias"],
},
{
"slot_id": "dashboard_api_rbac_tls_repair_readback",
"title": "Dashboard API / RBAC / TLS 修復讀回",
"required_fields": [
"dashboard_api_connection_check_status",
"dashboard_api_version_check_status",
"dashboard_index_pattern_statuses",
"dashboard_api_degradation_root_cause",
"dashboard_api_repair_postcheck_ref",
],
},
{
"slot_id": "readonly_credential_metadata_without_secret",
"title": "唯讀 credential metadata不含 secret",
"required_fields": ["collection_method", "manager_health_ref", "redacted_evidence_refs"],
},
{
"slot_id": "owner_response_and_rollback_owner",
"title": "Owner response / rollback owner",
"required_fields": ["owner_role", "team", "decision", "decision_reason", "followup_owner", "rollback_owner"],
},
{
"slot_id": "post_enable_iwooos_readback",
"title": "Post-enable IwoooS readback",
"required_fields": ["postcheck_plan", "redacted_evidence_refs"],
},
]
FORBIDDEN_PAYLOADS = [
"raw_wazuh_payload",
"raw_log",
"full_journal",
"full_cli_output",
"unredacted_screenshot",
"agent_name",
"agent_id_plaintext",
"internal_ip",
"hostname",
"authorization_header",
"bearer_token",
"basic_auth",
"password",
"token",
"cookie",
"private_key",
"client_keys",
"raw_dashboard_request",
"dashboard_api_secret",
"stored_api_password",
"api_token",
"active_response_enable",
"agent_reenroll",
"agent_restart",
"host_write",
"firewall_change",
"nginx_reload",
]
FORBIDDEN_ACTIONS = [
"wazuh_api_live_query",
"wazuh_agent_reenroll",
"wazuh_agent_restart",
"wazuh_manager_restart",
"wazuh_active_response",
"wazuh_dashboard_secret_patch",
"host_write",
"firewall_change",
"nginx_reload",
"k8s_or_argocd_change",
"kali_active_scan",
]
FORBIDDEN_TEXT_PATTERNS = [
re.compile(r"\b(?:10|127|172\.(?:1[6-9]|2\d|3[01])|192\.168)\.\d{1,3}\.\d{1,3}\b"),
re.compile(r"Authorization\s*:", re.IGNORECASE),
re.compile(r"Bearer\s+[A-Za-z0-9._-]{10,}", re.IGNORECASE),
re.compile(r"Basic\s+[A-Za-z0-9+/=]{10,}", re.IGNORECASE),
re.compile(r"password\s*[:=]\s*['\"][^'\"]+['\"]", re.IGNORECASE),
re.compile(r"token\s*[:=]\s*['\"][^'\"]+['\"]", re.IGNORECASE),
re.compile(r"cookie\s*[:=]\s*['\"][^'\"]+['\"]", re.IGNORECASE),
re.compile(r"client\.keys", re.IGNORECASE),
re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
]
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def assert_equal(label: str, actual: Any, expected: Any) -> None:
if actual != expected:
raise SystemExit(f"BLOCKED {label}: expected {expected!r}, got {actual!r}")
def assert_false(label: str, actual: Any) -> None:
assert_equal(label, actual, False)
def assert_zero(label: str, actual: Any) -> None:
assert_equal(label, actual, 0)
def collect_string_values(value: Any) -> list[str]:
if isinstance(value, str):
return [value]
if isinstance(value, list):
values: list[str] = []
for item in value:
values.extend(collect_string_values(item))
return values
if isinstance(value, dict):
values = []
for item in value.values():
values.extend(collect_string_values(item))
return values
return []
def validate_no_forbidden_text(snapshot: dict[str, Any]) -> None:
for text in collect_string_values(snapshot):
for pattern in FORBIDDEN_TEXT_PATTERNS:
if pattern.search(text):
raise SystemExit(
"BLOCKED wazuh_manager_registry_reviewer_validation: snapshot contains forbidden sensitive text"
)
def build_snapshot(generated_at: str) -> dict[str, Any]:
return {
"schema_version": SCHEMA_VERSION,
"generated_at": generated_at,
"status": "waiting_owner_registry_export_for_reviewer_validation",
"mode": "committed_validation_contract_no_runtime_no_secret_collection",
"scope": "wazuh_manager_registry_owner_export_reviewer_validation",
"source_refs": [
"docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json",
"docs/security/wazuh-managed-host-coverage-gate.snapshot.json",
],
"summary": {
"expected_scope_alias_count": len(EXPECTED_SCOPE_ALIASES),
"required_owner_field_count": len(REQUIRED_OWNER_FIELDS),
"per_host_required_field_count": len(PER_HOST_REQUIRED_FIELDS),
"reviewer_validation_check_count": len(REVIEWER_VALIDATION_CHECKS),
"outcome_lane_count": len(OUTCOME_LANES),
"evidence_slot_count": len(EVIDENCE_SLOTS),
"forbidden_payload_count": len(FORBIDDEN_PAYLOADS),
"forbidden_action_count": len(FORBIDDEN_ACTIONS),
"owner_registry_export_received_count": 0,
"owner_registry_export_accepted_count": 0,
"reviewer_validation_ready_count": 0,
"reviewer_validation_passed_count": 0,
"reviewer_validation_failed_count": 0,
"reviewer_validation_quarantined_count": 0,
"manager_registry_accepted_count": 0,
"post_enable_readback_passed_count": 0,
"runtime_gate_count": 0,
"host_write_authorized_count": 0,
"active_response_authorized_count": 0,
"secret_value_collection_allowed_count": 0,
},
"expected_scope_aliases": EXPECTED_SCOPE_ALIASES,
"required_owner_fields": REQUIRED_OWNER_FIELDS,
"per_host_required_fields": PER_HOST_REQUIRED_FIELDS,
"reviewer_validation_checks": REVIEWER_VALIDATION_CHECKS,
"outcome_lanes": OUTCOME_LANES,
"evidence_slots": [
{
**slot,
"received": False,
"accepted": False,
"quarantined": False,
"next_gate": "owner_provided_redacted_export",
}
for slot in EVIDENCE_SLOTS
],
"forbidden_payloads": FORBIDDEN_PAYLOADS,
"forbidden_actions": FORBIDDEN_ACTIONS,
"execution_boundaries": {
"wazuh_api_live_query_authorized": False,
"wazuh_agent_reenroll_authorized": False,
"wazuh_agent_restart_authorized": False,
"wazuh_manager_restart_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,
"kali_active_scan_authorized": False,
"runtime_execution_authorized": False,
"not_authorization": True,
},
"no_false_green_rules": [
"reviewer validation contract 可見不代表 owner registry export 已收到。",
"owner registry export received 不代表 manager_registry_accepted_count 可增加。",
"Dashboard 可見、index pattern 三綠勾、HTTP 200 或 transport observed 不可替代 manager registry counts。",
"reviewer accepted 只可更新只讀 postureactive response、agent restart、reenroll、host write、secret rotation 或掃描仍需獨立 runtime gate。",
],
}
def validate(root: Path) -> None:
snapshot = load_json(root / SNAPSHOT_PATH)
assert_equal("schema_version", snapshot.get("schema_version"), SCHEMA_VERSION)
assert_equal("status", snapshot.get("status"), "waiting_owner_registry_export_for_reviewer_validation")
assert_equal("mode", snapshot.get("mode"), "committed_validation_contract_no_runtime_no_secret_collection")
assert_equal("scope", snapshot.get("scope"), "wazuh_manager_registry_owner_export_reviewer_validation")
assert_equal("expected_scope_aliases", snapshot.get("expected_scope_aliases"), EXPECTED_SCOPE_ALIASES)
assert_equal("required_owner_fields", snapshot.get("required_owner_fields"), REQUIRED_OWNER_FIELDS)
assert_equal("per_host_required_fields", snapshot.get("per_host_required_fields"), PER_HOST_REQUIRED_FIELDS)
assert_equal("reviewer_validation_checks", snapshot.get("reviewer_validation_checks"), REVIEWER_VALIDATION_CHECKS)
assert_equal("outcome_lanes", snapshot.get("outcome_lanes"), OUTCOME_LANES)
assert_equal("forbidden_payloads", snapshot.get("forbidden_payloads"), FORBIDDEN_PAYLOADS)
assert_equal("forbidden_actions", snapshot.get("forbidden_actions"), FORBIDDEN_ACTIONS)
summary = snapshot.get("summary", {})
assert_equal("summary.expected_scope_alias_count", summary.get("expected_scope_alias_count"), len(EXPECTED_SCOPE_ALIASES))
assert_equal("summary.required_owner_field_count", summary.get("required_owner_field_count"), len(REQUIRED_OWNER_FIELDS))
assert_equal("summary.per_host_required_field_count", summary.get("per_host_required_field_count"), len(PER_HOST_REQUIRED_FIELDS))
assert_equal(
"summary.reviewer_validation_check_count",
summary.get("reviewer_validation_check_count"),
len(REVIEWER_VALIDATION_CHECKS),
)
assert_equal("summary.outcome_lane_count", summary.get("outcome_lane_count"), len(OUTCOME_LANES))
assert_equal("summary.evidence_slot_count", summary.get("evidence_slot_count"), len(EVIDENCE_SLOTS))
assert_equal("summary.forbidden_payload_count", summary.get("forbidden_payload_count"), len(FORBIDDEN_PAYLOADS))
assert_equal("summary.forbidden_action_count", summary.get("forbidden_action_count"), len(FORBIDDEN_ACTIONS))
for key in [
"owner_registry_export_received_count",
"owner_registry_export_accepted_count",
"reviewer_validation_ready_count",
"reviewer_validation_passed_count",
"reviewer_validation_failed_count",
"reviewer_validation_quarantined_count",
"manager_registry_accepted_count",
"post_enable_readback_passed_count",
"runtime_gate_count",
"host_write_authorized_count",
"active_response_authorized_count",
"secret_value_collection_allowed_count",
]:
assert_zero(f"summary.{key}", summary.get(key))
evidence_slots = snapshot.get("evidence_slots", [])
assert_equal("evidence_slots.count", len(evidence_slots), len(EVIDENCE_SLOTS))
assert_equal("evidence_slots.ids", [slot.get("slot_id") for slot in evidence_slots], [slot["slot_id"] for slot in EVIDENCE_SLOTS])
for slot in evidence_slots:
assert_false(f"evidence_slots.{slot.get('slot_id')}.received", slot.get("received"))
assert_false(f"evidence_slots.{slot.get('slot_id')}.accepted", slot.get("accepted"))
assert_false(f"evidence_slots.{slot.get('slot_id')}.quarantined", slot.get("quarantined"))
boundaries = snapshot.get("execution_boundaries", {})
for key, value in boundaries.items():
if key == "not_authorization":
assert_equal(f"execution_boundaries.{key}", value, True)
else:
assert_false(f"execution_boundaries.{key}", value)
validate_no_forbidden_text(snapshot)
def main() -> None:
parser = argparse.ArgumentParser(description="Wazuh manager registry reviewer validation gate")
parser.add_argument("--root", type=Path, default=Path.cwd())
parser.add_argument("--output", type=Path)
parser.add_argument("--generated-at", default="2026-06-27T15:24:00+08:00")
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
root = args.root.resolve()
if args.output:
payload = build_snapshot(args.generated_at)
output_path = args.output if args.output.is_absolute() else root / args.output
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print(f"Wrote {output_path}")
elif args.json:
print(json.dumps(build_snapshot(args.generated_at), ensure_ascii=False, indent=2, sort_keys=True))
else:
validate(root)
snapshot = load_json(root / SNAPSHOT_PATH)
summary = snapshot["summary"]
print(
"WAZUH_MANAGER_REGISTRY_REVIEWER_VALIDATION_OK "
f"aliases={summary['expected_scope_alias_count']} "
f"checks={summary['reviewer_validation_check_count']} "
f"slots={summary['evidence_slot_count']} "
f"received={summary['owner_registry_export_received_count']} "
f"accepted={summary['owner_registry_export_accepted_count']} "
f"runtime_gate={summary['runtime_gate_count']}"
)
if __name__ == "__main__":
main()