diff --git a/apps/api/src/services/iwooos_security_control_coverage.py b/apps/api/src/services/iwooos_security_control_coverage.py index 8d152f335..06c98e8ae 100644 --- a/apps/api/src/services/iwooos_security_control_coverage.py +++ b/apps/api/src/services/iwooos_security_control_coverage.py @@ -17,6 +17,17 @@ from src.services.snapshot_paths import default_evaluations_dir, default_securit _DEFAULT_SECURITY_DIR = default_security_dir(Path(__file__)) _DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SCHEMA_VERSION = "iwooos_security_control_coverage_v1" +_REQUIRED_CONTROLLED_APPLY_STAGES = [ + "sensor_source_receipt", + "normalized_asset_identity", + "source_truth_diff", + "ai_decision_candidate_action", + "risk_policy_decision", + "check_mode_dry_run_receipt", + "bounded_execution_receipt", + "independent_post_verifier", + "km_rag_mcp_playbook_write_ack", +] _SECURITY_SNAPSHOTS = { "asset_ledger": "security-asset-control-ledger.snapshot.json", @@ -88,7 +99,7 @@ def load_latest_iwooos_security_control_coverage( "no_false_green_rules": [ "納管覆蓋總表只代表 committed snapshot 可讀,不代表所有主機已被 Wazuh manager registry 驗收。", "route 200、transport observed、UI 可見、一般工作批准都不能當成 runtime 授權。", - "IwoooS ledger 的 owner response received / accepted、live evidence accepted、active scan、active response、Telegram send、host write 仍維持 0 / false;這不阻擋 AwoooP allowlisted controlled apply。", + "IwoooS ledger 的 owner response received / accepted、live evidence accepted、active scan、active response、Telegram send、host write 仍維持 0 / false;這不阻擋 AwoooP allowlisted controlled apply 執行候選,但任何閉環宣稱都必須寫回 IWOOOS / AISOC ledger receipt。", "Nginx、Firewall、Workflow、Secret、K8s、Docker、systemd、AI Agent provider 變更都必須先進 execution packet、check-mode、rollback、verifier 與 KM / PlayBook trust,不得繞過受控路由。", ], "source_refs": [ @@ -378,75 +389,111 @@ def _build_summary(snapshots: dict[str, dict[str, Any]], domains: list[dict[str, "agent_bounty_product_surface_count": len(snapshots["agent_bounty"].get("product_surfaces") or []), "ai_agent_asset_count": len(snapshots["ai_agent_automation"].get("assets") or []), "all_scope_runtime_controlled": False, - "allowlisted_controlled_apply_bypasses_iwooos_ledger": True, + "allowlisted_controlled_apply_bypasses_iwooos_ledger": False, + "allowlisted_controlled_apply_requires_iwooos_ledger_receipt": True, + "controlled_apply_ledger_contract": "same_run_iwooos_aisoc_trace_required", "controlled_apply_policy": "low_medium_high_allowed_after_allowlist_check_mode_rollback_verifier_km", "critical_break_glass_required": True, } -def _build_p0_next_actions() -> list[dict[str, str]]: - return [ +def _build_p0_next_actions() -> list[dict[str, Any]]: + actions: list[dict[str, Any]] = [ { "priority": "P0-01", "title": "全域資安完成口徑與 owner closure", "required_evidence": "每個主機、服務、產品、網站、工具、套件與 AI Agent 都要有 scope、owner、risk、verifier、runtime acceptance 欄位;UI / API / CD 綠燈不得算完成。", + "asset_scope_ids": ["hosts", "services", "products", "websites", "tools", "packages", "ai_agents"], + "next_executable_action": "bind_security_program_queue_to_same_run_runtime_receipt_contract", }, { "priority": "P0-02", "title": "全資產 / 全主機 / 全服務 scope truth", "required_evidence": "Gitea source truth、runtime surface inventory、host/service live hash、公開入口、runner、backup、AI tool 權限與缺口清單;不得用單一 dashboard 代表全域。", + "asset_scope_ids": ["hosts", "services", "products", "websites"], + "next_executable_action": "reconcile_committed_inventory_with_production_readback_and_create_gap_work_items", }, { "priority": "P0-03", "title": "Wazuh manager registry、FIM、弱點與告警 receipt", "required_evidence": "manager registry parity、agent active/disconnected rollup、FIM baseline、vulnerability detection rollup、alert sample receipt、TLS/RBAC readback、缺席主機 decision。", + "asset_scope_ids": ["hosts", "services", "tools", "observability_and_security"], + "next_executable_action": "attach_wazuh_fim_vulnerability_alert_case_and_post_verifier_receipts", }, { "priority": "P0-04", "title": "Host / Docker / systemd 入侵與鑑識基線", "required_evidence": "auth、sudo、process、network、persistence、package、service、Docker event 的脫敏 readback;所有 remediation 先進 check-mode、rollback、post-verifier。", + "asset_scope_ids": ["hosts", "services", "packages"], + "next_executable_action": "build_public_safe_host_service_forensics_baseline_and_ansible_check_mode_candidates", }, { "priority": "P0-05", "title": "Nginx / Gateway / SSH / Firewall / K8s network", "required_evidence": "source-to-live diff、nginx -t 或等價檢核、route smoke、before/after actor、NetworkPolicy / firewall diff、rollback owner。", + "asset_scope_ids": ["hosts", "services", "websites"], + "next_executable_action": "stage_network_gateway_source_live_diff_check_mode_and_rollback_verifiers", }, { "priority": "P0-06", "title": "Web / API / AppSec / webhook 防護", "required_evidence": "auth、authorization、session、rate limit、CORS、security headers、webhook abuse case、ASVS mapping、desktop/mobile smoke 與 no sensitive copy。", + "asset_scope_ids": ["products", "websites", "services"], + "next_executable_action": "create_bounded_appsec_canary_and_webhook_abuse_regression_receipts", }, { "priority": "P0-07", "title": "套件 / container / workflow supply-chain", "required_evidence": "SBOM、lockfile diff、image scan、KEV/EPSS priority、runner/workflow diff、artifact provenance、signature verification、package SLA。", + "asset_scope_ids": ["products", "packages", "tools"], + "next_executable_action": "attach_sbom_image_scan_workflow_provenance_and_no_secret_output_receipts", }, { "priority": "P0-08", "title": "SIEM / SOAR / Monitoring 告警閉環", "required_evidence": "Wazuh / Alertmanager / Telegram / OCSF / Sigma normalized event、dedupe、noise budget、case id、receiver receipt、operator acknowledgment。", + "asset_scope_ids": ["observability_and_security", "tools", "ai_agents"], + "next_executable_action": "normalize_alert_events_to_cases_and_queue_controlled_response_candidates", }, { "priority": "P0-09", "title": "Backup / restore / forensic retention", "required_evidence": "restore drill、offsite/escrow、chain of custody、retention policy、backup freshness、rollback proof、post-restore smoke。", + "asset_scope_ids": ["data_and_backup", "products", "services"], + "next_executable_action": "bind_backup_restore_drill_receipts_to_security_incident_closure", }, { "priority": "P0-10", "title": "AI Agent / MCP / RAG permission gate", "required_evidence": "tool allowlist、prompt redaction、secret boundary、RAG source trust、MCP connector scope、cost cap、replay/shadow/canary、KM / PlayBook writeback。", + "asset_scope_ids": ["ai_agents", "tools", "data_and_backup"], + "next_executable_action": "build_cross_product_mcp_rag_capability_registry_and_runtime_receipt_gate", }, { "priority": "P0-11", "title": "AISOC 管理者 UI / UX 控制室", "required_evidence": "單一風險地圖、目前 P0、blocked reason、next executable action、tool coverage、host/service/product/package drilldown;避免以大段文字取代狀態。", + "asset_scope_ids": ["websites", "products", "observability_and_security"], + "next_executable_action": "replace_text_wall_with_first_viewport_risk_map_queue_and_drilldown_cockpit", }, { "priority": "P0-12", "title": "Controlled apply deployment 與 production readback", "required_evidence": "target selector、source-of-truth diff、check-mode/dry-run、rollback、post-apply verifier、runtime readback、KM / PlayBook trust、deploy marker。", + "asset_scope_ids": ["hosts", "services", "products", "tools", "ai_agents"], + "next_executable_action": "run_allowlisted_same_run_controlled_apply_chain_and_write_iwooos_ledger_receipt", }, ] + for action in actions: + action["work_item_id"] = f"IWOOOS-SEC-{action['priority']}" + action["status"] = "runtime_closure_required" + action["source_truth_layer"] = "production_runtime_receipt_first" + action["runtime_credit_policy"] = "no_credit_until_same_run_iwooos_aisoc_receipts_exist" + action["requires_iwooos_ledger_receipt"] = True + action["required_controlled_apply_stages"] = list(_REQUIRED_CONTROLLED_APPLY_STAGES) + action["critical_break_glass_required"] = True + action["controlled_apply_allowed_risk_levels"] = ["low", "medium", "high"] + return actions def _build_professional_security_governance_review( diff --git a/apps/api/src/services/iwooos_security_tool_closure_readback.py b/apps/api/src/services/iwooos_security_tool_closure_readback.py index c6c1a4016..9b3055ef3 100644 --- a/apps/api/src/services/iwooos_security_tool_closure_readback.py +++ b/apps/api/src/services/iwooos_security_tool_closure_readback.py @@ -548,9 +548,16 @@ def _next_executable_queue( "track_id": "wazuh_detection_response", "state": registry_gate, "next_action": wazuh_next_action, + "next_executable_action": wazuh_next_action, "ready_signal_count": _int( wazuh_verifier.get("verifier_ready_signal_count") ), + "required_signal_count": 6, + "missing_signal_count": max( + 6 - _int(wazuh_verifier.get("verifier_ready_signal_count")), + 0, + ), + "required_verifier": "wazuh_registry_fim_vulnerability_alert_receipt_post_verifier", "controlled_apply_policy_allowed": True, }, { @@ -558,6 +565,11 @@ def _next_executable_queue( "track_id": "supply_chain_sbom_scan", "state": "sbom_and_scan_artifacts_missing", "next_action": "stage_trivy_syft_grype_check_mode_reports", + "next_executable_action": "stage_trivy_syft_grype_check_mode_reports", + "ready_signal_count": 0, + "required_signal_count": 5, + "missing_signal_count": 5, + "required_verifier": "sbom_vulnerability_artifact_post_verifier", "controlled_apply_policy_allowed": True, }, { @@ -565,10 +577,21 @@ def _next_executable_queue( "track_id": "ai_agent_controlled_apply", "state": "preflight_ready_for_controlled_policy_check", "next_action": "connect_target_selector_diff_check_mode_rollback_post_verifier_km", + "next_executable_action": "connect_target_selector_diff_check_mode_rollback_post_verifier_km", "ready_signal_count": _int( controlled_apply.get("controlled_apply_preflight_ready_count") ) + _int(dry_run.get("dry_run_result_ref_count")), + "required_signal_count": 9, + "missing_signal_count": max( + 9 + - ( + _int(controlled_apply.get("controlled_apply_preflight_ready_count")) + + _int(dry_run.get("dry_run_result_ref_count")) + ), + 0, + ), + "required_verifier": "ai_controlled_apply_same_run_post_verifier_km_writeback", "controlled_apply_policy_allowed": True, }, ] diff --git a/apps/api/tests/test_iwooos_security_control_coverage.py b/apps/api/tests/test_iwooos_security_control_coverage.py index d897f5e56..563fb8f77 100644 --- a/apps/api/tests/test_iwooos_security_control_coverage.py +++ b/apps/api/tests/test_iwooos_security_control_coverage.py @@ -1,5 +1,9 @@ from __future__ import annotations +import os + +os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test") + from fastapi import FastAPI from fastapi.testclient import TestClient @@ -107,7 +111,11 @@ def test_iwooos_security_control_coverage_keeps_runtime_gates_closed() -> None: assert summary["agent_bounty_runtime_gate_open_count"] == 0 assert summary["ai_agent_runtime_write_gate_open_count"] == 0 assert summary["all_scope_runtime_controlled"] is False - assert summary["allowlisted_controlled_apply_bypasses_iwooos_ledger"] is True + assert summary["allowlisted_controlled_apply_bypasses_iwooos_ledger"] is False + assert summary["allowlisted_controlled_apply_requires_iwooos_ledger_receipt"] is True + assert summary["controlled_apply_ledger_contract"] == ( + "same_run_iwooos_aisoc_trace_required" + ) assert ( summary["controlled_apply_policy"] == "low_medium_high_allowed_after_allowlist_check_mode_rollback_verifier_km" @@ -135,3 +143,23 @@ def test_iwooos_security_control_coverage_api_is_public_safe() -> None: assert "runtime_control_blocked" not in response.text assert "工作視窗" not in response.text assert "批准!繼續" not in response.text + + +def test_iwooos_security_control_coverage_p0_actions_are_executable_contracts() -> None: + payload = load_latest_iwooos_security_control_coverage() + actions = payload["p0_next_actions"] + + assert len(actions) == 12 + assert all(action["work_item_id"].startswith("IWOOOS-SEC-P0-") for action in actions) + assert all(action["requires_iwooos_ledger_receipt"] is True for action in actions) + assert all(action["next_executable_action"] for action in actions) + assert all(action["asset_scope_ids"] for action in actions) + assert all( + action["runtime_credit_policy"] + == "no_credit_until_same_run_iwooos_aisoc_receipts_exist" + for action in actions + ) + assert all( + "bounded_execution_receipt" in action["required_controlled_apply_stages"] + for action in actions + ) diff --git a/apps/api/tests/test_iwooos_security_tool_closure_readback.py b/apps/api/tests/test_iwooos_security_tool_closure_readback.py index 47a2bbcf1..46716fa30 100644 --- a/apps/api/tests/test_iwooos_security_tool_closure_readback.py +++ b/apps/api/tests/test_iwooos_security_tool_closure_readback.py @@ -1,5 +1,9 @@ from __future__ import annotations +import os + +os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test") + from fastapi import FastAPI from fastapi.testclient import TestClient @@ -106,6 +110,22 @@ def test_iwooos_security_tool_closure_readback_marks_partial_vs_missing_tracks() assert tracks["ai_agent_controlled_apply"]["missing_signal_count"] >= 0 +def test_iwooos_security_tool_closure_queue_has_executable_receipt_contract() -> None: + payload = load_latest_iwooos_security_tool_closure_readback() + queue = {item["queue_id"]: item for item in payload["next_executable_queue"]} + + assert set(queue) == {"P0-03", "P0-07", "P0-08"} + assert queue["P0-03"]["track_id"] == "wazuh_detection_response" + assert queue["P0-03"]["required_signal_count"] == 6 + assert queue["P0-03"]["missing_signal_count"] >= 0 + assert queue["P0-03"]["required_verifier"] == ( + "wazuh_registry_fim_vulnerability_alert_receipt_post_verifier" + ) + assert all(item["next_executable_action"] for item in queue.values()) + assert all(item["required_signal_count"] > 0 for item in queue.values()) + assert all(item["controlled_apply_policy_allowed"] is True for item in queue.values()) + + def test_iwooos_security_tool_closure_readback_accepts_alert_receipt_signal() -> None: payload = load_latest_iwooos_security_tool_closure_readback( alert_receipt_readback={ diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index 7ca3d3e90..935cb50b6 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -682,6 +682,16 @@ export interface IwoooSSecurityControlCoverageProfessionalReview { priority: string title: string required_evidence: string + work_item_id?: string + status?: string + source_truth_layer?: string + runtime_credit_policy?: string + requires_iwooos_ledger_receipt?: boolean + asset_scope_ids?: string[] + next_executable_action?: string + required_controlled_apply_stages?: string[] + critical_break_glass_required?: boolean + controlled_apply_allowed_risk_levels?: string[] }> security_tool_integration_matrix: IwoooSSecurityControlCoverageToolTrack[] ai_automation_closure_loop: IwoooSSecurityControlCoverageAiLoopStage[] @@ -730,6 +740,9 @@ export interface IwoooSSecurityControlCoverageResponse { full_scope_contract_count: number ui_ux_control_room_requirement_count: number all_scope_runtime_controlled: boolean + allowlisted_controlled_apply_bypasses_iwooos_ledger?: boolean + allowlisted_controlled_apply_requires_iwooos_ledger_receipt?: boolean + controlled_apply_ledger_contract?: string } domains: IwoooSSecurityControlCoverageDomain[] professional_review: IwoooSSecurityControlCoverageProfessionalReview @@ -737,6 +750,16 @@ export interface IwoooSSecurityControlCoverageResponse { priority: string title: string required_evidence: string + work_item_id?: string + status?: string + source_truth_layer?: string + runtime_credit_policy?: string + requires_iwooos_ledger_receipt?: boolean + asset_scope_ids?: string[] + next_executable_action?: string + required_controlled_apply_stages?: string[] + critical_break_glass_required?: boolean + controlled_apply_allowed_risk_levels?: string[] }> no_false_green_rules: string[] source_refs: string[] @@ -784,7 +807,11 @@ export interface IwoooSSecurityToolClosureQueueItem { track_id: IwoooSSecurityToolClosureTrack['track_id'] state: string next_action: string + next_executable_action?: string ready_signal_count?: number + required_signal_count?: number + missing_signal_count?: number + required_verifier?: string controlled_apply_policy_allowed: boolean }