#!/usr/bin/env python3 """ IwoooS Wazuh / 主機入侵 read-only 接入計畫產生器。 本工具只產生 repo 內 snapshot,將已建置的 Wazuh 平台、host-110 / host-188 入侵跡象、外部 Agent 高風險操作宣稱與後續 containment / recovery proof 轉成 IwoooS 可收件、可拒收、可補件的脫敏欄位。 它不連 Wazuh API、不讀主機、不 SSH、不啟動 Wazuh active response、 不隔離主機、不封鎖端口、不重啟服務、不收 secret / private key / runner token / webhook secret,也不把外部 Agent 的口頭宣稱視為已修復。 """ from __future__ import annotations import argparse import json import subprocess import sys from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Any TAIPEI = timezone(timedelta(hours=8)) READBACK_FIELDS = [ "readback_candidate_id", "scope_alias", "label", "control_tier", "priority", "source_kind", "wazuh_platform_reported_ready", "requires_owner_evidence", "requires_wazuh_event_refs", "requires_host_forensics_refs", "requires_cross_project_sync", "wazuh_manager_health_ref", "wazuh_agent_status_ref", "wazuh_alert_rule_refs", "wazuh_event_refs", "host_auth_log_refs", "process_tree_refs", "network_connection_refs", "file_integrity_refs", "package_inventory_refs", "persistence_mechanism_refs", "runner_or_gateway_config_diff_refs", "actor_attribution_ref", "incident_time_window_ref", "affected_scope", "impact_summary_ref", "containment_decision", "containment_reason_ref", "recovery_proof_refs", "postcheck_readback_ref", "recurrence_guard_ref", "cross_project_sync_ref", "rollback_owner", "followup_owner", "reviewer_outcome", "not_authorization", ] REQUIRED_READBACK_FIELDS = [ "wazuh_manager_health_ref", "wazuh_agent_status_ref", "wazuh_event_refs", "host_auth_log_refs", "process_tree_refs", "network_connection_refs", "file_integrity_refs", "package_inventory_refs", "persistence_mechanism_refs", "actor_attribution_ref", "incident_time_window_ref", "affected_scope", "impact_summary_ref", "containment_decision", "containment_reason_ref", "recovery_proof_refs", "postcheck_readback_ref", "recurrence_guard_ref", "cross_project_sync_ref", "rollback_owner", "followup_owner", "redacted_evidence_refs", "no_secret_value_attestation", "no_raw_log_attestation", "no_raw_wazuh_payload_attestation", "no_private_key_or_runner_token_attestation", "no_false_green_attestation", "external_agent_claim_not_accepted_attestation", "active_response_not_enabled_attestation", "independent_reviewer_attestation", ] REVIEWER_CHECKS = [ {"check_id": "wazuh_platform_ready_is_metadata_only", "instruction": "Wazuh 已建置只能當作平台存在訊號,不能直接視為 IwoooS 已完成事件接入。"}, {"check_id": "manager_health_ref_present", "instruction": "必須有脫敏 manager / indexer / dashboard / API health ref。"}, {"check_id": "agent_status_ref_present", "instruction": "host-110、host-188 或其他受影響 host 必須有 agent 狀態 ref。"}, {"check_id": "event_refs_present", "instruction": "必須有 Wazuh event / alert refs,不能只寫已發現或已阻擋。"}, {"check_id": "host_forensics_refs_present", "instruction": "需要 auth log、process tree、network connection、file integrity、package inventory 或 persistence refs。"}, {"check_id": "actor_attribution_present", "instruction": "需要 actor role / team / tool attribution;外部 Agent 操作不能匿名通過。"}, {"check_id": "time_window_present", "instruction": "需要 incident / operation / recovery time window,供事件與變更對齊。"}, {"check_id": "affected_scope_present", "instruction": "需列 host alias、服務 alias、產品 alias 與受影響配置類別,不顯示原始內部命名空間。"}, {"check_id": "containment_decision_present", "instruction": "需有 containment decision 與理由;未決定時維持 waiting_containment_decision。"}, {"check_id": "external_agent_claim_not_trusted", "instruction": "外部 Agent 宣稱已清除、已修復、已阻擋或已 Zero-Trust 不能直接 accepted。"}, {"check_id": "runner_gateway_change_reviewed", "instruction": "runner label、runner config、gateway、firewall、workflow 或 secret 相關事件需串到既有高價值配置 gate。"}, {"check_id": "secret_material_absent", "instruction": "不得收 private key、password、runner token、webhook secret、cookie、env dump、hash 或 partial token。"}, {"check_id": "raw_payload_absent", "instruction": "不得保存 raw Wazuh payload、raw syslog、raw journal、完整截圖或未脫敏 command output。"}, {"check_id": "hardcoded_wazuh_credential_rejected", "instruction": "硬編碼 Wazuh URL、使用者、密碼或關閉 TLS 驗證的程式碼必須拒收或隔離。"}, {"check_id": "readonly_api_disabled_by_default", "instruction": "IwoooS Wazuh API code path 必須預設關閉,沒有 owner gate 與 server-side env 時不得查詢。"}, {"check_id": "host_write_not_opened", "instruction": "不得因 Wazuh 告警就自動 SSH、kill process、封鎖端口、restart 或修改主機。"}, {"check_id": "active_response_not_opened", "instruction": "Wazuh active response、firewall drop、isolation 或 remediation 必須另有 break-glass 與人工批准。"}, {"check_id": "cross_project_sync_present", "instruction": "若事件影響 AWOOOI、AwoooP、IwoooS、任務媒合、股票研究、公開網站或監控,需有同步 ref。"}, {"check_id": "recovery_proof_present", "instruction": "需要 recovery proof refs;服務 route 200 或 dashboard 可見不能單獨當恢復證據。"}, {"check_id": "postcheck_independent", "instruction": "postcheck 必須獨立於操作人與外部 Agent。"}, {"check_id": "recurrence_guard_present", "instruction": "需要防再發 guard,例如 credential hygiene、runner config control、firewall diff control、Wazuh rule coverage。"}, {"check_id": "owner_response_not_auto_accepted", "instruction": "owner response received / accepted count 只能由 reviewer record 推進。"}, {"check_id": "no_false_green", "instruction": "Wazuh online、agent active、route 200、CD success、UI 可見都不能單獨當事件驗收。"}, {"check_id": "runtime_stays_zero", "instruction": "readback plan 不得打開 active response、host write、workflow change、secret rotation 或 action button。"}, ] OUTCOME_LANES = [ {"lane_id": "waiting_wazuh_owner_evidence", "meaning": "Wazuh 已建置但尚未收到 IwoooS 可接受的脫敏事件 refs。"}, {"lane_id": "request_manager_health_supplement", "meaning": "缺 Wazuh manager / indexer / dashboard / API health ref 時要求補件。"}, {"lane_id": "request_agent_status_supplement", "meaning": "缺 host agent 狀態、agent id 對照或 last seen ref 時要求補件。"}, {"lane_id": "request_host_forensics_supplement", "meaning": "缺 auth / process / network / FIM / package / persistence refs 時要求補件。"}, {"lane_id": "request_actor_or_time_supplement", "meaning": "缺 actor attribution、工具來源或事件時間窗時要求補件。"}, {"lane_id": "request_containment_decision", "meaning": "缺 containment decision、理由、owner 或 rollback owner 時要求補件。"}, {"lane_id": "quarantine_secret_or_raw_payload", "meaning": "收到 secret、private key、runner token、raw payload、raw log 或未脫敏截圖時隔離。"}, {"lane_id": "reject_external_agent_claim_only", "meaning": "只有外部 Agent 口頭宣稱已修復、已阻擋或已清除時拒收。"}, {"lane_id": "route_to_high_value_config_gate", "meaning": "涉及 runner、workflow、secret、gateway、firewall、systemd、Docker 或 K8s 時串到高價值配置 gate。"}, {"lane_id": "ready_for_intrusion_reviewer_review", "meaning": "metadata 合格後只能進 reviewer review,不自動執行。"}, {"lane_id": "recurrence_guard_backfill_required", "meaning": "需補 Wazuh rule coverage、runner/gateway/secret guard、credential hygiene 或 change freeze。"}, {"lane_id": "waiting_runtime_gate", "meaning": "即使事件 refs accepted,active response 與 host containment 仍需獨立人工批准。"}, ] BLOCKED_ACTIONS = [ "wazuh_active_response_enable", "wazuh_agent_install", "wazuh_agent_restart", "wazuh_manager_restart", "wazuh_rule_change", "wazuh_decoder_change", "wazuh_api_live_query_without_owner_gate", "hardcode_wazuh_url", "hardcode_wazuh_username", "hardcode_wazuh_password", "disable_tls_verification", "store_raw_wazuh_payload", "store_raw_syslog", "store_raw_journal", "store_raw_command_output", "store_unredacted_screenshot", "collect_password", "collect_private_key", "collect_runner_token", "collect_webhook_secret", "collect_cookie_or_session", "collect_env_dump", "ssh_read", "ssh_write", "sudo_action", "host_file_write", "kill_process", "quarantine_host", "firewall_drop", "port_close", "port_open", "docker_restart", "docker_kill", "systemctl_restart", "nginx_reload", "workflow_modification", "runner_label_change", "runner_config_change", "repo_secret_change", "secret_rotation", "active_scan", "production_write", "mark_malware_removed_without_evidence", "mark_rce_fixed_without_evidence", "accept_external_agent_claim_without_wazuh_refs", "accept_route_200_as_recovery", "accept_agent_active_as_incident_closed", "open_runtime_gate", "add_action_button", ] READBACK_CANDIDATES = [ { "readback_candidate_id": "wazuh_intrusion_readback:wazuh_control_plane", "scope_alias": "wazuh-control-plane", "label": "Wazuh manager / indexer / dashboard / API", "control_tier": "C0", "priority": "P0", "source_kind": "wazuh_platform", "requires_wazuh_event_refs": False, "requires_host_forensics_refs": False, "requires_cross_project_sync": True, }, { "readback_candidate_id": "wazuh_intrusion_readback:host110_intrusion_signal", "scope_alias": "host-110", "label": "host-110 入侵 / gateway / runner / service 異常訊號", "control_tier": "C0", "priority": "P0", "source_kind": "host_intrusion", "requires_wazuh_event_refs": True, "requires_host_forensics_refs": True, "requires_cross_project_sync": True, }, { "readback_candidate_id": "wazuh_intrusion_readback:host188_intrusion_signal", "scope_alias": "host-188", "label": "host-188 入侵 / 惡意程式 / RCE 宣稱回讀", "control_tier": "C0", "priority": "P0", "source_kind": "host_intrusion", "requires_wazuh_event_refs": True, "requires_host_forensics_refs": True, "requires_cross_project_sync": True, }, { "readback_candidate_id": "wazuh_intrusion_readback:external_agent_live_change_claim", "scope_alias": "external-agent-handoff", "label": "外部 Agent live 變更與修復宣稱驗證", "control_tier": "C0", "priority": "P0", "source_kind": "external_agent_claim", "requires_wazuh_event_refs": True, "requires_host_forensics_refs": True, "requires_cross_project_sync": True, }, { "readback_candidate_id": "wazuh_intrusion_readback:runner_secret_gateway_control", "scope_alias": "runner-secret-gateway", "label": "Runner / secret / gateway 高價值配置連動", "control_tier": "C0", "priority": "P0", "source_kind": "high_value_config_link", "requires_wazuh_event_refs": True, "requires_host_forensics_refs": True, "requires_cross_project_sync": True, }, { "readback_candidate_id": "wazuh_intrusion_readback:containment_recovery_postcheck", "scope_alias": "containment-recovery", "label": "Containment / recovery proof / postcheck", "control_tier": "C0", "priority": "P0", "source_kind": "incident_response", "requires_wazuh_event_refs": True, "requires_host_forensics_refs": True, "requires_cross_project_sync": True, }, ] def git_short_sha(root: Path) -> str: try: result = subprocess.run( ["git", "rev-parse", "--short", "HEAD"], cwd=root, check=True, capture_output=True, text=True, ) return result.stdout.strip() except Exception: return "unknown" def build_candidate(source: dict[str, Any]) -> dict[str, Any]: return { **source, "status": "waiting_wazuh_owner_evidence", "wazuh_platform_reported_ready": True, "requires_owner_evidence": True, "wazuh_manager_health_ref": None, "wazuh_agent_status_ref": None, "wazuh_alert_rule_refs": [], "wazuh_event_refs": [], "host_auth_log_refs": [], "process_tree_refs": [], "network_connection_refs": [], "file_integrity_refs": [], "package_inventory_refs": [], "persistence_mechanism_refs": [], "runner_or_gateway_config_diff_refs": [], "actor_attribution_ref": None, "incident_time_window_ref": None, "affected_scope": "pending_redacted_scope", "impact_summary_ref": None, "containment_decision": "waiting_containment_decision", "containment_reason_ref": None, "recovery_proof_refs": [], "postcheck_readback_ref": None, "recurrence_guard_ref": None, "cross_project_sync_ref": None, "rollback_owner": "pending_owner_response", "followup_owner": "pending_owner_response", "reviewer_outcome": "waiting_wazuh_intrusion_review", "not_approval": True, "runtime_gate": False, "action_buttons_allowed": False, "active_response_authorized": False, "host_write_authorized": False, "secret_value_collection_allowed": False, "blocked_actions": BLOCKED_ACTIONS, "readback_fields": READBACK_FIELDS, "required_readback_fields": REQUIRED_READBACK_FIELDS, "reviewer_checks": [item["check_id"] for item in REVIEWER_CHECKS], "outcome_lanes": [item["lane_id"] for item in OUTCOME_LANES], } def build_report(root: Path, generated_at: str | None) -> dict[str, Any]: report_time = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds") candidates = [build_candidate(item) for item in READBACK_CANDIDATES] return { "schema_version": "wazuh_iwooos_intrusion_readback_plan_v1", "generated_at": report_time, "git_commit": git_short_sha(root), "status": "wazuh_intrusion_readback_plan_ready_no_runtime_action", "mode": "metadata_only_no_wazuh_api_no_host_read_no_active_response", "source_context": { "wazuh_platform_reported_ready": True, "iwooos_runtime_ingestion_accepted": False, "affected_host_aliases": ["host-110", "host-188"], "external_agent_transcript_published_to_frontend": False, "raw_secret_or_private_key_collection_allowed": False, }, "summary": { "wazuh_platform_reported_count": 1, "readback_candidate_count": len(candidates), "c0_readback_candidate_count": sum(1 for item in candidates if item["control_tier"] == "C0"), "affected_host_alias_count": 2, "wazuh_event_required_candidate_count": sum(1 for item in candidates if item["requires_wazuh_event_refs"]), "host_forensics_required_candidate_count": sum(1 for item in candidates if item["requires_host_forensics_refs"]), "cross_project_sync_required_candidate_count": sum(1 for item in candidates if item["requires_cross_project_sync"]), "readback_field_count": len(READBACK_FIELDS), "required_readback_field_count": len(REQUIRED_READBACK_FIELDS), "reviewer_check_count": len(REVIEWER_CHECKS), "outcome_lane_count": len(OUTCOME_LANES), "blocked_action_count": len(BLOCKED_ACTIONS), "wazuh_manager_health_ref_received_count": 0, "wazuh_agent_status_ref_received_count": 0, "wazuh_event_ref_received_count": 0, "host_forensics_ref_received_count": 0, "containment_decision_received_count": 0, "recovery_proof_received_count": 0, "postcheck_readback_received_count": 0, "recurrence_guard_received_count": 0, "cross_project_sync_received_count": 0, "owner_response_received_count": 0, "owner_response_accepted_count": 0, "wazuh_event_accepted_count": 0, "host_forensics_accepted_count": 0, "containment_decision_accepted_count": 0, "recovery_proof_accepted_count": 0, "postcheck_readback_accepted_count": 0, "recurrence_guard_accepted_count": 0, "readonly_api_code_path_present_count": 1, "readonly_api_enabled_count": 0, "active_response_authorized_count": 0, "host_write_authorized_count": 0, "secret_value_collection_allowed_count": 0, "runtime_gate_count": 0, "action_button_count": 0, "coverage_percent_after_readback_plan": 72, }, "execution_boundaries": { "runtime_execution_authorized": False, "wazuh_api_live_query_authorized": False, "wazuh_active_response_authorized": False, "wazuh_rule_change_authorized": False, "wazuh_agent_install_authorized": False, "host_read_authorized": False, "host_write_authorized": False, "ssh_read_authorized": False, "ssh_write_authorized": False, "sudo_action_authorized": False, "firewall_change_authorized": False, "process_kill_authorized": False, "host_isolation_authorized": False, "runner_change_authorized": False, "workflow_modification_authorized": False, "repo_secret_change_authorized": False, "secret_value_collection_allowed": False, "raw_wazuh_payload_storage_allowed": False, "raw_log_storage_allowed": False, "private_key_collection_allowed": False, "active_scan_authorized": False, "production_write_authorized": False, "frontend_raw_transcript_display_allowed": False, "action_buttons_allowed": False, "not_authorization": True, }, "readback_candidates": candidates, "reviewer_checks": REVIEWER_CHECKS, "outcome_lanes": OUTCOME_LANES, "blocked_actions": BLOCKED_ACTIONS, "operator_interpretation": [ "Wazuh 已建置是偵測平台存在訊號,不代表 IwoooS 已驗收 110 / 188 入侵事件。", "IwoooS 只能接收脫敏 refs、hash、ticket、artifact pointer 與 owner response,不收 secret、private key、runner token 或 raw payload。", "外部 Agent 對木馬、RCE、Zero-Trust、清除或封鎖的宣稱必須用 Wazuh event refs 與 host forensic refs 交叉驗證。", "active response、host containment、firewall drop、restart、runner change、secret rotation 與 production write 仍全部需要獨立人工批准。", ], } def main() -> int: parser = argparse.ArgumentParser(description="IwoooS Wazuh / 主機入侵 read-only 接入計畫產生器") parser.add_argument("--root", default=".", help="repo root") parser.add_argument("--output", help="寫出 JSON 報告") parser.add_argument("--generated-at", help="固定報告時間,供 committed snapshot 使用") args = parser.parse_args() root = Path(args.root).resolve() report = build_report(root, args.generated_at) payload = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) if args.output: output = Path(args.output) output.parent.mkdir(parents=True, exist_ok=True) output.write_text(payload + "\n", encoding="utf-8") else: print(payload) summary = report["summary"] print( "WAZUH_IWOOOS_INTRUSION_READBACK_PLAN_OK " f"candidates={summary['readback_candidate_count']} " f"hosts={summary['affected_host_alias_count']} " f"checks={summary['reviewer_check_count']} " f"blocked={summary['blocked_action_count']} " f"runtime_gate={summary['runtime_gate_count']}", file=sys.stderr, ) return 0 if __name__ == "__main__": sys.exit(main())