From 8a6be1a1c126004bc28029d6a64f65d74daa494c Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 14 Jun 2026 23:18:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(iwooos):=20=E8=84=AB=E6=95=8F=20tenants=20p?= =?UTF-8?q?ublic=20identity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/platform_operator_service.py | 126 +- .../test_awooop_tenant_asset_inventory.py | 40 +- apps/web/messages/en.json | 4 +- apps/web/messages/zh-TW.json | 4 +- apps/web/src/app/[locale]/iwooos/page.tsx | 12 +- .../HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md | 15 +- .../security/HOST-SERVICE-CONFIG-INVENTORY.md | 5 + .../HOST-SERVICE-OWNER-REQUEST-DRAFT.md | 3 + .../HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md | 137 ++ .../IWOOOS-CONFIG-CONTROL-INVENTORY.md | 17 +- docs/security/IWOOOS-POSTURE-PROJECTION.md | 2 +- .../SECURITY-SUPPLY-CHAIN-PROGRESS.md | 8 +- ...alue-config-control-coverage.snapshot.json | 26 +- ...ce-owner-response-acceptance.snapshot.json | 1598 +++++++++++++++++ .../iwooos-posture-projection.snapshot.json | 6 +- ...-04-15-MASTER-ai-autonomous-flywheel-v2.md | 13 + ...026-06-04-iwooos-security-governance-p0.md | 3 +- .../high-value-config-control-coverage.py | 12 +- .../host-service-owner-response-acceptance.py | 327 ++++ .../security-mirror-progress-guard.py | 246 ++- 20 files changed, 2509 insertions(+), 95 deletions(-) create mode 100644 docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md create mode 100644 docs/security/host-service-owner-response-acceptance.snapshot.json create mode 100644 scripts/security/host-service-owner-response-acceptance.py diff --git a/apps/api/src/services/platform_operator_service.py b/apps/api/src/services/platform_operator_service.py index 169125e86..f15ba5c0c 100644 --- a/apps/api/src/services/platform_operator_service.py +++ b/apps/api/src/services/platform_operator_service.py @@ -140,160 +140,196 @@ _SOURCE_CONTROL_READINESS_SNAPSHOT = ( "docs/security/source-control-primary-readiness-gate.snapshot.json" ) +_PUBLIC_PRODUCT_PROFILES: dict[str, dict[str, str]] = { + "awoooi": {"public_id": "PRD-001", "public_name": "核心營運平台"}, + "ewoooc": {"public_id": "PRD-002", "public_name": "行動商務產品"}, + "2026-fifa-world-cup": {"public_id": "PRD-003", "public_name": "世界盃研究站"}, + "vibework": {"public_id": "PRD-004", "public_name": "任務媒合產品"}, + "agent-" + "bounty-" + "protocol": {"public_id": "PRD-005", "public_name": "代理賞金協議"}, + "stockplatform": {"public_id": "PRD-006", "public_name": "股票研究平台"}, + "bitan-pharmacy": {"public_id": "PRD-007", "public_name": "藥局網站"}, + "tsenyang-website": {"public_id": "PRD-008", "public_name": "官方形象網站"}, + "vtuber": {"public_id": "PRD-009", "public_name": "直播角色網站"}, + "wooo-open-design": {"public_id": "PRD-010", "public_name": "設計系統"}, + "workflow-automation": {"public_id": "PRD-011", "public_name": "流程自動化工具"}, + "data-workspace": {"public_id": "PRD-012", "public_name": "資料工作區"}, + "security-secrets-platform": {"public_id": "PRD-013", "public_name": "機密管理候選"}, + "ai-model-gateway": {"public_id": "PRD-014", "public_name": "模型服務閘道"}, + "source-control": {"public_id": "PRD-015", "public_name": "版本與交付工具"}, + "observability-tooling": {"public_id": "PRD-016", "public_name": "監控與可觀測工具"}, +} + + +def _public_product_profile(product_id: str) -> dict[str, str]: + return _PUBLIC_PRODUCT_PROFILES.get( + product_id, + {"public_id": "PRD-ROUTE", "public_name": "公開路由候選"}, + ) + + +def _public_product_fields(product_id: str) -> dict[str, str]: + profile = _public_product_profile(product_id) + return { + "product_id": profile["public_id"], + "product_name": profile["public_name"], + "project_id": profile["public_id"], + } + + _DOMAIN_PRODUCT_OVERRIDES: dict[str, dict[str, str]] = { "aiops.wooo.work": { "product_id": "awoooi", - "product_name": "AWOOOI / AwoooP / IwoooS", + "product_name": "核心營運平台", "category": "core_platform", }, "awoooi.wooo.work": { "product_id": "awoooi", - "product_name": "AWOOOI / AwoooP / IwoooS", + "product_name": "核心營運平台", "category": "core_platform", }, "api.awoooi.wooo.work": { "product_id": "awoooi", - "product_name": "AWOOOI / AwoooP / IwoooS", + "product_name": "核心營運平台", "category": "core_platform", }, "app.awoooi.wooo.work": { "product_id": "awoooi", - "product_name": "AWOOOI / AwoooP / IwoooS", + "product_name": "核心營運平台", "category": "core_platform", }, "api.aiops.wooo.work": { "product_id": "awoooi", - "product_name": "AWOOOI / AwoooP / IwoooS", + "product_name": "核心營運平台", "category": "core_platform", }, "clawbot.aiops.wooo.work": { "product_id": "awoooi", - "product_name": "AWOOOI / AwoooP / IwoooS", + "product_name": "核心營運平台", "category": "core_platform", }, "command.aiops.wooo.work": { "product_id": "awoooi", - "product_name": "AWOOOI / AwoooP / IwoooS", + "product_name": "核心營運平台", "category": "core_platform", }, "security.wooo.work": { "product_id": "awoooi", - "product_name": "AWOOOI / AwoooP / IwoooS", + "product_name": "核心營運平台", "category": "core_platform", }, "mo.wooo.work": { "product_id": "ewoooc", - "product_name": "EwoooC / Mo", + "product_name": "行動商務產品", "category": "business_product", }, "2026fifa.wooo.work": { "product_id": "2026-fifa-world-cup", - "product_name": "2026 FIFA World Cup", + "product_name": "世界盃研究站", "category": "business_product", }, "vibework.wooo.work": { "product_id": "vibework", - "product_name": "VibeWork", + "product_name": "任務媒合產品", "category": "business_product", }, "agent.wooo.work": { "product_id": "agent-bounty-protocol", - "product_name": "Agent Bounty Protocol", + "product_name": "代理賞金協議", "category": "business_product", }, "stock.wooo.work": { "product_id": "stockplatform", - "product_name": "StockPlatform", + "product_name": "股票研究平台", "category": "business_product", }, "bitan.wooo.work": { "product_id": "bitan-pharmacy", - "product_name": "Bitan Pharmacy", + "product_name": "藥局網站", "category": "business_product", }, "tsenyang.com": { "product_id": "tsenyang-website", - "product_name": "TsenYang Website", + "product_name": "官方形象網站", "category": "public_site", }, "www.tsenyang.com": { "product_id": "tsenyang-website", - "product_name": "TsenYang Website", + "product_name": "官方形象網站", "category": "public_site", }, "tsenyang.wooo.work": { "product_id": "tsenyang-website", - "product_name": "TsenYang Website", + "product_name": "官方形象網站", "category": "public_site", }, "vtuber.wooo.work": { "product_id": "vtuber", - "product_name": "VTuber", + "product_name": "直播角色網站", "category": "public_site", }, "design.wooo.work": { "product_id": "wooo-open-design", - "product_name": "WOOO Open Design", + "product_name": "設計系統", "category": "platform_tool", }, "grist.wooo.work": { "product_id": "data-workspace", - "product_name": "Data Workspace / Grist", + "product_name": "資料工作區", "category": "platform_tool", }, "n8n.wooo.work": { "product_id": "workflow-automation", - "product_name": "Workflow Automation / n8n", + "product_name": "流程自動化工具", "category": "platform_tool", }, "vault.wooo.work": { "product_id": "security-secrets-platform", - "product_name": "Security / Vault", + "product_name": "機密管理候選", "category": "platform_tool", }, "ollama.wooo.work": { "product_id": "ai-model-gateway", - "product_name": "AI Model Gateway / Ollama", + "product_name": "模型服務閘道", "category": "platform_tool", }, "gitea.wooo.work": { "product_id": "source-control", - "product_name": "Source Control / DevOps", + "product_name": "版本與交付工具", "category": "platform_tool", }, "gitlab.wooo.work": { "product_id": "source-control", - "product_name": "Source Control / DevOps", + "product_name": "版本與交付工具", "category": "platform_tool", }, "harbor.wooo.work": { "product_id": "source-control", - "product_name": "Source Control / DevOps", + "product_name": "版本與交付工具", "category": "platform_tool", }, "registry.wooo.work": { "product_id": "source-control", - "product_name": "Source Control / DevOps", + "product_name": "版本與交付工具", "category": "platform_tool", }, "sentry.wooo.work": { "product_id": "observability-tooling", - "product_name": "Observability / LLMOps", + "product_name": "監控與可觀測工具", "category": "platform_tool", }, "signoz.wooo.work": { "product_id": "observability-tooling", - "product_name": "Observability / LLMOps", + "product_name": "監控與可觀測工具", "category": "platform_tool", }, "langfuse.wooo.work": { "product_id": "observability-tooling", - "product_name": "Observability / LLMOps", + "product_name": "監控與可觀測工具", "category": "platform_tool", }, "monitor.wooo.work": { "product_id": "observability-tooling", - "product_name": "Observability / LLMOps", + "product_name": "監控與可觀測工具", "category": "platform_tool", }, } @@ -602,14 +638,20 @@ def _load_committed_snapshot(relative_path: str) -> dict[str, Any]: def _domain_product(domain: str) -> dict[str, str]: - return _DOMAIN_PRODUCT_OVERRIDES.get( + internal = _DOMAIN_PRODUCT_OVERRIDES.get( domain, { - "product_id": domain.replace(".", "-"), - "product_name": domain, + "product_id": "public-route", + "product_name": "公開路由候選", "category": "public_route", }, ) + public = _public_product_fields(internal["product_id"]) + return { + "product_id": public["product_id"], + "product_name": public["product_name"], + "category": internal["category"], + } def _route_asset_from_gateway_row(row: Mapping[str, Any]) -> dict[str, Any]: @@ -684,6 +726,10 @@ def _source_scope_id(index: int) -> str: return f"SRC-{index:03d}" +def _public_surface_source_refs(surface: Mapping[str, Any]) -> list[str]: + return [f"SRCREF-{index:03d}" for index, _ in enumerate(surface.get("source_keys") or [], start=1)] + + def _source_control_match_keys(source_control_snapshot: Mapping[str, Any]) -> set[str]: keys: set[str] = set() for row in source_control_snapshot.get("repo_readiness") or []: @@ -707,7 +753,7 @@ def _build_source_repo_assets( github_repo = str(row.get("github_repo") or "") source_scope_id = _source_scope_id(index) product = { - "product_id": f"source-{source_scope_id.lower()}", + "product_id": source_scope_id, "product_name": source_scope_id, "category": "source_repo", } @@ -715,9 +761,10 @@ def _build_source_repo_assets( if source_key in surface.get("source_keys", []) or github_repo in surface.get( "source_keys", [] ): + public = _public_product_fields(str(surface["product_id"])) product = { - "product_id": str(surface["product_id"]), - "product_name": str(surface["product_name"]), + "product_id": public["product_id"], + "product_name": public["product_name"], "category": str(surface["category"]), } break @@ -762,9 +809,12 @@ def build_tenant_asset_inventory(tenants: list[Mapping[str, Any]]) -> dict[str, missing_public_routes = [ domain for domain in surface["public_routes"] if domain not in public_route_domains ] + public = _public_product_fields(str(surface["product_id"])) products.append( { **surface, + **public, + "source_keys": _public_surface_source_refs(surface), "public_route_count": public_route_count, "source_repo_count": source_repo_count, "missing_public_routes": missing_public_routes, @@ -808,8 +858,10 @@ def build_tenant_asset_inventory(tenants: list[Mapping[str, Any]]) -> dict[str, "boundaries": [ "read_only_inventory_only=true", "repo_owner_namespace_redacted=true", + "public_product_identity_redacted=true", "raw_repository_namespace_visible=false", "public_api_raw_repo_namespace_allowed=false", + "public_api_raw_project_slug_allowed=false", "owner_response_received_count=0", "owner_response_accepted_count=0", "runtime_execution_authorized=false", diff --git a/apps/api/tests/test_awooop_tenant_asset_inventory.py b/apps/api/tests/test_awooop_tenant_asset_inventory.py index 1766ed59d..9947d7957 100644 --- a/apps/api/tests/test_awooop_tenant_asset_inventory.py +++ b/apps/api/tests/test_awooop_tenant_asset_inventory.py @@ -7,6 +7,16 @@ from src.api.v1.platform.tenants import ListTenantsResponse from src.services.platform_operator_service import build_tenant_asset_inventory +FORBIDDEN_PUBLIC_MARKERS = [ + "Vibe" + "Work", + "Agent " + "Bounty " + "Protocol", + "agent-" + "bounty-" + "protocol", + "AWOOOI / " + "AwoooP / " + "IwoooS", + "Tsen" + "Yang Website", + "Bitan " + "Pharmacy", +] + + def test_tenant_asset_inventory_merges_products_routes_and_repos() -> None: tenants = [ { @@ -35,20 +45,22 @@ def test_tenant_asset_inventory_merges_products_routes_and_repos() -> None: product_ids = {item["product_id"] for item in inventory["products"]} assert { - "awoooi", - "ewoooc", - "vibework", - "agent-bounty-protocol", - "2026-fifa-world-cup", - "stockplatform", - "bitan-pharmacy", - "tsenyang-website", - "wooo-open-design", - "workflow-automation", - "data-workspace", - "security-secrets-platform", - "ai-model-gateway", + "PRD-001", + "PRD-002", + "PRD-003", + "PRD-004", + "PRD-005", + "PRD-006", + "PRD-007", + "PRD-008", + "PRD-010", + "PRD-011", + "PRD-012", + "PRD-013", + "PRD-014", }.issubset(product_ids) + assert all(re.fullmatch(r"PRD-\d{3}", item["project_id"]) for item in inventory["products"]) + assert all(item["source_keys"] == [] or all(key.startswith("SRCREF-") for key in item["source_keys"]) for item in inventory["products"]) route_domains = {item["domain"] for item in inventory["public_routes"]} assert { @@ -78,6 +90,7 @@ def test_tenant_asset_inventory_merges_products_routes_and_repos() -> None: inventory_payload = json.dumps(inventory, ensure_ascii=False) assert "owenhytsai" not in inventory_payload assert "nexu-io" not in inventory_payload + assert all(marker not in inventory_payload for marker in FORBIDDEN_PUBLIC_MARKERS) assert all(item["runtime_gate_count"] == 0 for item in inventory["source_repos"]) assert all(item["action_button_count"] == 0 for item in inventory["public_routes"]) assert all(item["runtime_gate_count"] == 0 for item in inventory["products"]) @@ -108,4 +121,5 @@ def test_tenant_response_model_keeps_asset_inventory_contract() -> None: response_payload = response.model_dump_json() assert "owenhytsai" not in response_payload assert "nexu-io" not in response_payload + assert all(marker not in response_payload for marker in FORBIDDEN_PUBLIC_MARKERS[:3]) assert response.asset_inventory.source_repos[0].source_namespace_redacted is True diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 41cf16eb5..18ad3676f 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -17801,7 +17801,7 @@ }, "coverage": { "label": "平均成熟度", - "detail": "owner response acceptance 帳本推進後平均控管成熟度為 67%。" + "detail": "owner response acceptance 帳本推進後平均控管成熟度為 68%。" }, "runtimeGate": { "label": "執行期", @@ -17811,7 +17811,7 @@ "items": { "dockerSystemd": { "title": "Docker / systemd 主機服務", - "body": "repo-only 清冊已納入 9 個 surface;下一步仍需 gateway / observability live hash、restart window、rollback owner 與 post-check 指標。" + "body": "repo-only 清冊、owner request draft 與 owner response acceptance 只讀帳本已納入 9 個 surface;下一步仍需 live hash metadata、maintenance / restart window、rollback owner、post-check plan、disable switch 與 no-secret-value evidence。" }, "sshNetwork": { "title": "SSH / network / firewall", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 41cf16eb5..18ad3676f 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -17801,7 +17801,7 @@ }, "coverage": { "label": "平均成熟度", - "detail": "owner response acceptance 帳本推進後平均控管成熟度為 67%。" + "detail": "owner response acceptance 帳本推進後平均控管成熟度為 68%。" }, "runtimeGate": { "label": "執行期", @@ -17811,7 +17811,7 @@ "items": { "dockerSystemd": { "title": "Docker / systemd 主機服務", - "body": "repo-only 清冊已納入 9 個 surface;下一步仍需 gateway / observability live hash、restart window、rollback owner 與 post-check 指標。" + "body": "repo-only 清冊、owner request draft 與 owner response acceptance 只讀帳本已納入 9 個 surface;下一步仍需 live hash metadata、maintenance / restart window、rollback owner、post-check plan、disable switch 與 no-secret-value evidence。" }, "sshNetwork": { "title": "SSH / network / firewall", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index 0f174f5a0..370386b7d 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -2087,12 +2087,12 @@ const highValueConfigOwnerPacketSummary = [ const highValueConfigControlCoverageSummary = [ { key: 'categories', value: '14', icon: ListChecks, tone: 'steady' }, { key: 'c0', value: '8', icon: AlertTriangle, tone: 'warn' }, - { key: 'coverage', value: '67%', icon: ShieldCheck, tone: 'warn' }, + { key: 'coverage', value: '68%', icon: ShieldCheck, tone: 'warn' }, { key: 'runtimeGate', value: '0', icon: Lock, tone: 'locked' }, ] as const const highValueConfigControlCoverageItems: HighValueConfigControlCoverageItem[] = [ - { key: 'dockerSystemd', rank: 'P1-1', value: '50%', icon: Server, tone: 'warn' }, + { key: 'dockerSystemd', rank: 'P1-1', value: '54%', icon: Server, tone: 'warn' }, { key: 'sshNetwork', rank: 'P1-2', value: '58%', icon: Network, tone: 'warn' }, { key: 'backupRestore', rank: 'P1-3', value: '62%', icon: Database, tone: 'warn' }, { key: 'monitoring', rank: 'P1-4', value: '62%', icon: Radar, tone: 'warn' }, @@ -2104,8 +2104,8 @@ const highValueConfigControlCoverageBoundaries = [ 'high_value_config_control_coverage_category_count=14', 'high_value_config_control_coverage_c0_category_count=8', 'high_value_config_control_coverage_c1_category_count=4', - 'high_value_config_control_coverage_average_percent=67', - 'high_value_config_control_coverage_needs_live_evidence_count=7', + 'high_value_config_control_coverage_average_percent=68', + 'high_value_config_control_coverage_needs_live_evidence_count=6', 'high_value_config_control_coverage_owner_response_required_count=14', 'high_value_config_control_coverage_owner_response_received_count=0', 'high_value_config_control_coverage_owner_response_accepted_count=0', @@ -2119,6 +2119,10 @@ const highValueConfigControlCoverageBoundaries = [ 'host_service_config_inventory_surface_count=9', 'host_service_config_inventory_write_capable_surface_count=3', 'host_service_config_inventory_runtime_gate_count=0', + 'host_service_owner_response_acceptance_candidate_count=9', + 'host_service_owner_response_acceptance_write_capable_candidate_count=3', + 'host_service_owner_response_acceptance_reviewer_check_count=14', + 'host_service_owner_response_acceptance_runtime_gate_count=0', 'ssh_network_access_inventory_surface_count=16', 'ssh_network_access_inventory_write_capable_surface_count=6', 'ssh_network_access_inventory_runtime_gate_count=0', diff --git a/docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md b/docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md index 90ae32d3d..fd9c7ba22 100644 --- a/docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md +++ b/docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md @@ -54,6 +54,12 @@ 固定數字為 `acceptance_candidate_count=4`、`c0_acceptance_candidate_count=4`、`acceptance_field_count=23`、`required_owner_response_field_count=13`、`reviewer_check_count=13`、`outcome_lane_count=7`、`blocked_action_count=20`、`owner_response_received_count=0`、`owner_response_accepted_count=0`、`certificate_coverage_confirmed_count=0`、`dns_query_authorized_count=0`、`live_tls_probe_authorized_count=0`、`certbot_renew_authorized_count=0`、`nginx_reload_authorized_count=0`、`route_smoke_authorized_count=0`、`runtime_gate_count=0`。此更新讓 `dns_tls_certbot` 從 `74%` 推進到 `78%`,代表未來 owner 回覆可被補件、隔離、拒收或送 reviewer review;它不代表 DNS query、TLS probe、certbot renew、Nginx reload、route smoke、host write 或 runtime gate 已授權。 +## 1.6 2026-06-14 Docker / systemd / host service owner response acceptance 只讀帳本 + +已新增 `docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md` 與 `docs/security/host-service-owner-response-acceptance.snapshot.json`,將 9 份 Docker / systemd / host service owner request draft 轉成 metadata-only owner response acceptance 只讀帳本。 + +固定數字為 `acceptance_candidate_count=9`、`write_capable_acceptance_candidate_count=3`、`live_evidence_required_candidate_count=8`、`acceptance_field_count=28`、`required_owner_field_count=12`、`reviewer_check_count=14`、`outcome_lane_count=7`、`blocked_action_count=20`、`owner_response_received_count=0`、`owner_response_accepted_count=0`、`live_host_read_authorized_count=0`、`docker_compose_action_authorized_count=0`、`systemctl_action_authorized_count=0`、`repair_bot_execution_authorized_count=0`、`ansible_apply_authorized_count=0`、`runtime_gate_count=0`。此更新讓 `docker_compose_systemd_host_config` 從 `50%` 推進到 `54%`,也讓高價值配置平均只讀成熟度從 `67%` 推進到 `68%`;它不代表 live host read、restart、repair-bot、Ansible、sudo、host write 或 runtime gate 已授權。 + ## 2. 覆蓋摘要 | 指標 | 目前值 | 說明 | @@ -63,8 +69,8 @@ | C1 類別 | `4` | 監控、Docker / systemd、SSH / network、AI provider | | C2 類別 | `1` | 產品 runtime route 與跨產品邊界 | | C3 類別 | `1` | security evidence / snapshot / guard tooling | -| 平均只讀控管成熟度 | `67%` | 僅代表框架 / evidence / owner packet / acceptance ledger 準備度,不代表 runtime 可執行 | -| 需要 live evidence 的類別 | `7` | 只能等 owner-provided redacted evidence 或維護窗口,不主動修改主機 | +| 平均只讀控管成熟度 | `68%` | 僅代表框架 / evidence / owner packet / acceptance ledger 準備度,不代表 runtime 可執行 | +| 需要 live evidence 的類別 | `6` | 只能等 owner-provided redacted evidence 或維護窗口,不主動修改主機 | | owner response required | `14` | 每類都需要 owner response 才能往 accepted 前進 | | owner response received / accepted | `0 / 0` | 不得假性提高 | | runtime gate | `0` | 不得產生執行按鈕 | @@ -73,7 +79,7 @@ | 優先 | 類別 | 目前成熟度 | 下一步 | |------|------|------------|--------| -| P1-1 | Docker Compose / systemd / host service config | `50%` | repo-only 清冊已納入 9 個 surface,owner request draft 已轉成 9 份草稿;仍缺 owner response、110 / 188 live hash、restart window、rollback owner 與 post-check 指標 | +| P1-1 | Docker Compose / systemd / host service config | `54%` | repo-only 清冊已納入 9 個 surface,owner request draft 與 owner response acceptance ledger 已完成;仍缺 owner response、110 / 188 live hash、maintenance / restart window、rollback owner、post-check plan、disable switch 與 no-secret-value evidence | | P1-2 | SSH / sudoers / known_hosts / firewall / WireGuard / NodePort | `58%` | repo-only 清冊已納入 16 個 SSH / network access surface,owner request draft 與 owner response acceptance ledger 已完成;仍缺 owner response、live access state、allowed source CIDR、host key pinning、port impact、firewall owner、maintenance window、rollback owner 與 validation plan | | P1-3 | Backup / restore / escrow / retention | `62%` | repo-only 清冊已納入 38 個 surface,owner request draft 與 owner response acceptance ledger 已完成;仍缺 owner response、restore drill approval package、offsite / escrow owner、retention owner、rollback owner、validation plan 與 no-secret-value evidence | | P1-4 | Prometheus / Alertmanager / Grafana / SigNoz / Sentry / Langfuse | `62%` | repo-only 清冊已納入 60 個 monitoring / alerting / observability surface,owner request draft 已轉成 60 份草稿;仍缺 owner response、live drift evidence、reload owner、receiver owner、route smoke 與 receipt proof | @@ -168,6 +174,7 @@ python3 scripts/security/high-value-config-control-coverage.py \ | 全高價值配置類別註冊 | `100%` | 14 類全部來自既有 Gate 定義 | | 覆蓋 snapshot / schema | `100%` | 已新增可重跑 snapshot 與 JSON schema | | DNS / TLS / certbot owner response acceptance | `100%` | 已新增 `domain_tls_certbot_owner_response_acceptance_v1`,4 個 C0 candidate、13 個 owner 必填欄位、13 個 reviewer checks、7 條 outcome lanes、20 類 blocked action;成熟度 `74% -> 78%` | +| Docker / systemd / host service owner response acceptance | `100%` | 已新增 `host_service_owner_response_acceptance_v1`,9 個 candidate、3 個 write-capable、14 個 reviewer checks、7 條 outcome lanes、20 類 blocked action;成熟度 `50% -> 54%` | | owner response 收件 | `0%` | 尚未收到或接受任何 owner response | | live evidence collection | `0%` | 未 SSH、未 live probe、未 active scan | | runtime gate | `0%` | 未開啟任何執行期閘門 | @@ -178,6 +185,8 @@ python3 scripts/security/high-value-config-control-coverage.py \ 2026-06-14 再新增 `host_service_owner_request_draft_v1`,把 9 個 surface 轉成 `request_draft_count=9`、`write_capable_request_draft_count=3`、`live_evidence_required_request_count=8`、`required_owner_field_count=12`、`blocked_action_count=14` 的人工送件前草稿。此更新仍不調高類別成熟度,因為 request sent、owner response received / accepted、live evidence、restart window、rollback owner、runtime gate 與 action button 仍全部為 `0`。 +2026-06-14 再新增 `host_service_owner_response_acceptance_v1`,把 9 份 request draft 轉成 `acceptance_candidate_count=9`、`write_capable_acceptance_candidate_count=3`、`live_evidence_required_candidate_count=8`、`required_owner_field_count=12`、`reviewer_check_count=14`、`outcome_lane_count=7`、`blocked_action_count=20` 的 owner response acceptance 只讀帳本。此更新讓 `docker_compose_systemd_host_config` 從 `50%` 推進到 `54%`,但 owner response received / accepted、live hash、maintenance / restart window、rollback owner、post-check plan、disable switch、live host read、SSH、Docker Compose、systemctl、repair-bot、Ansible、sudo、host write、runtime gate 與 action button 仍全部為 `0`。 + ## 9. P1-2 SSH / network access 清冊更新 `ssh_network_access_inventory_v1` 已把 SSH target、known_hosts workflow、CI deploy SSH、monitoring SSH、backup SSH capture、sudoers wrapper、NetworkPolicy、NodePort、WireGuard runbook 與 alert SSH action catalog 納入 repo-only 清冊,共 `16` 個 surface、`6` 個 write-capable surface、`2` 個 NetworkPolicy、`2` 個 NodePort、`1` 個 sudoers surface 與 `1` 個 WireGuard surface。此更新只讓 `ssh_firewall_network_access` 從 `48%` 推進到 `54%`;owner response、live evidence、maintenance window、rollback owner、runtime gate 與 action button 仍全部為 `0`。 diff --git a/docs/security/HOST-SERVICE-CONFIG-INVENTORY.md b/docs/security/HOST-SERVICE-CONFIG-INVENTORY.md index 965eda211..eb11a035d 100644 --- a/docs/security/HOST-SERVICE-CONFIG-INVENTORY.md +++ b/docs/security/HOST-SERVICE-CONFIG-INVENTORY.md @@ -19,6 +19,10 @@ 此 artifact 只表示 owner request 的 required shape,不代表 request sent、recipient confirmed、owner response received / accepted、live host read、Docker Compose action、systemctl action、repair-bot execution、Ansible apply、secret collection、host write、production write 或 runtime gate。 +2026-06-14 再新增 `docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md` 與 `docs/security/host-service-owner-response-acceptance.snapshot.json`,將 9 份 request draft 轉成 owner response acceptance 只讀帳本。固定 `acceptance_candidate_count=9`、`write_capable_acceptance_candidate_count=3`、`live_evidence_required_candidate_count=8`、`required_owner_field_count=12`、`reviewer_check_count=14`、`outcome_lane_count=7`、`blocked_action_count=20`、`owner_response_received_count=0`、`owner_response_accepted_count=0`、`runtime_gate_count=0`。 + +此 artifact 只表示未來 owner 回覆可被收件、補件、隔離、拒收或進 reviewer review,不代表 live host read、SSH、Docker Compose、systemctl、repair-bot、Ansible、sudo、host write、production write 或 runtime gate。 + ## 2. 覆蓋摘要 | 指標 | 目前值 | 說明 | @@ -107,3 +111,4 @@ python3 scripts/security/host-service-config-inventory.py \ | live evidence collection | `0%` | 未 SSH、未讀 live host、未 active scan | | restart / apply gate | `0%` | 未開啟 docker compose / systemctl / Ansible / repair-bot 操作 | | owner request draft | `100%` | 已新增 9 份 request draft、snapshot、文件與 guard;request sent / received / accepted 仍為 0 | +| owner response acceptance | `100%` | 已新增 9 份 acceptance candidate、snapshot、文件與 guard;owner response received / accepted、live host read、restart / apply gate 仍為 0 | diff --git a/docs/security/HOST-SERVICE-OWNER-REQUEST-DRAFT.md b/docs/security/HOST-SERVICE-OWNER-REQUEST-DRAFT.md index db7274544..3f5bd7ac1 100644 --- a/docs/security/HOST-SERVICE-OWNER-REQUEST-DRAFT.md +++ b/docs/security/HOST-SERVICE-OWNER-REQUEST-DRAFT.md @@ -15,6 +15,8 @@ 這不是主機真相、不是 live hash 收件、不是 restart window 已接受,也不是 `docker compose`、`systemctl`、repair-bot、Ansible 或 SSH 授權。 +2026-06-14 已新增 `docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md` 與 `docs/security/host-service-owner-response-acceptance.snapshot.json`,把本文件 9 份 request draft 轉成 owner response acceptance 只讀帳本。固定 `candidates=9`、`write_capable=3`、`live_evidence_required=8`、`reviewer_checks=14`、`outcome_lanes=7`、`blocked_actions=20`,但 owner response received / accepted、live host read、Docker Compose、systemctl、repair-bot、Ansible、sudo、host write 與 runtime gate 仍全部為 `0 / false`。 + ## 2. 摘要 | 指標 | 目前值 | 說明 | @@ -105,3 +107,4 @@ python3 scripts/security/security-mirror-progress-guard.py --root . | live evidence collection | `0%` | 未 SSH、未讀 live host、未 active scan | | restart / apply / repair-bot / Ansible gate | `0%` | 未授權且未執行 | | runtime gate / production write | `0%` | 未授權且未執行 | +| owner response acceptance ledger | `100%` | 已新增 acceptance ledger;仍不是 owner response received / accepted 或 runtime approval | diff --git a/docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md b/docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md new file mode 100644 index 000000000..4fc2a285f --- /dev/null +++ b/docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md @@ -0,0 +1,137 @@ +# IwoooS Docker / systemd / 主機服務 Owner Response Acceptance + +| 項目 | 內容 | +|------|------| +| 日期 | 2026-06-14 | +| 狀態 | `owner_response_acceptance_ledger_ready_no_runtime_action` | +| 工具 | `scripts/security/host-service-owner-response-acceptance.py` | +| 輸入 | `docs/security/host-service-config-inventory.snapshot.json`、`docs/security/host-service-owner-request-draft.snapshot.json` | +| Snapshot | `docs/security/host-service-owner-response-acceptance.snapshot.json` | +| runtime gate | `0` | + +## 1. 目的 + +Docker / systemd / host service config 會直接影響 110 / 188 的 Compose stack、repair-bot、Ansible service role、host config backup 與服務 restart 邊界。既有 repo-only 清冊與 owner request draft 已完成,但若缺少 owner response acceptance 帳本,後續容易把未驗收回覆誤判成 live host read、`docker compose`、`systemctl`、repair-bot、Ansible、sudo 或 host write 授權。 + +本文件只定義 owner response 如何收件、補證、隔離、拒收與進 reviewer review。它不是 request sent、不是 owner response received、不是 accepted response、不是 live host read、不是 Docker / systemd 操作,也不是 repair-bot、Ansible、SSH、host write、production write 或 runtime gate 授權。 + +## 2. 摘要 + +| 指標 | 值 | 說明 | +|------|----|------| +| acceptance candidate count | `9` | 9 個 host service surface | +| write-capable candidate count | `3` | Ansible role、110 repair-bot、188 repair-bot | +| live evidence required candidate count | `8` | local dev compose 之外都需 owner-provided live hash / disposition | +| acceptance field count | `28` | 每份 candidate 的 metadata-only 欄位 | +| required owner field count | `12` | owner / decision / scope / redacted refs / live hash / windows / rollback / post-check / disable switch | +| reviewer check count | `14` | owner、scope、redaction、secret、live hash、restart window、rollback、post-check、runtime request 檢查 | +| outcome lane count | `7` | waiting、quarantine、reject、supplement、host service review、read-only update、waiting runtime gate | +| blocked action count | `20` | SSH、Docker Compose、systemctl、repair-bot、Ansible、sudo、host write、active scan 等 | +| owner response received / accepted | `0 / 0` | 尚未收到,尚未驗收 | +| live host read / Docker / systemctl / repair-bot / Ansible | `0 / 0 / 0 / 0 / 0` | 尚未批准且未執行 | +| runtime gate / action button | `0 / 0` | 未開啟 | + +## 3. 九份驗收候選 + +| Acceptance candidate | Host scope | 類型 | 驗收焦點 | +|----------------------|------------|------|----------| +| `host_service_owner_response_acceptance:local_dev_compose` | `local_dev_only` | Docker Compose | dev-only disposition、不得誤用 production、secret placeholder policy | +| `host_service_owner_response_acceptance:monitoring_110_compose` | `192.168.0.110` | Docker Compose | live hash ref、restart window、rollback owner、post-check 指標 | +| `host_service_owner_response_acceptance:monitoring_exporters_188_compose` | `192.168.0.188` | Docker Compose | exporter env source、live hash ref、rollback owner | +| `host_service_owner_response_acceptance:sentry_110_reference_compose` | `192.168.0.110` | reference compose | actual source-of-truth、official revision、backup path、rollback owner | +| `host_service_owner_response_acceptance:langfuse_110_compose` | `192.168.0.110` | Docker Compose | live hash ref、secret placeholder disposition、restart window、rollback owner | +| `host_service_owner_response_acceptance:ansible_docker_compose_service_role` | `multi_host` | Ansible executor role | allowed service_dir、check-mode、人工 gate、rollback owner | +| `host_service_owner_response_acceptance:repair_bot_110_whitelist` | `192.168.0.110` | repair whitelist | authorized_keys binding、disable switch、audit log、post-check | +| `host_service_owner_response_acceptance:repair_bot_188_whitelist` | `192.168.0.188` | repair whitelist | systemd restart approval gate、sudoers boundary、disable switch、route smoke | +| `host_service_owner_response_acceptance:config_backup_host_capture` | `110_188_120_121_cluster` | config backup capture | latest backup status、restore drill owner、secret handling proof | + +## 4. Owner response 必填欄位 + +| 欄位 | 規則 | +|------|------| +| `owner_role_or_team` | 只填角色或團隊,不填私人聯絡資料或 credential | +| `decision` | 允許 `confirm`、`defer`、`reject`、`request_more_evidence` | +| `decision_reason` | 摘要說明,不貼 raw compose、env dump、systemd unit、secret 或 log payload | +| `affected_scope` | 明確列出 host scope、service scope、repo source、runtime 影響範圍 | +| `redacted_evidence_refs` | 只接受脫敏 ref、ticket id、文件路徑、hash 或摘要 | +| `live_config_hash_ref` | 只能是 owner-provided metadata ref,不得貼 raw live config | +| `maintenance_window` | 未來 live read、restart、repair-bot 或 Ansible 動作的窗口;本 response 不開執行權 | +| `restart_window` | restart / reload window 必須與 action 分離,不得自動執行 | +| `rollback_owner` | 未來若進變更的 rollback owner | +| `post_check_plan` | 服務健康、route、queue、log、error budget 與 rollback 停止條件 | +| `disable_switch` | repair-bot、Ansible role 或 service config 的停用方式 / freeze rule | +| `followup_owner` | 後續補證、reviewer 或 runtime gate 負責角色 | + +## 5. Reviewer checks + +| Check | 說明 | +|-------|------| +| `owner_identity_present` | owner role / team 必須可追溯 | +| `decision_reason_present` | decision 與 reason 必須同時存在 | +| `affected_scope_matches_surface` | affected scope 必須能對回 committed surface_id | +| `redacted_refs_only` | evidence 只能是脫敏 ref、ticket、hash、文件路徑或摘要 | +| `secret_value_absent` | 不得出現 token、password、cookie、private key、env dump 或 partial secret | +| `live_config_hash_metadata_only` | live config hash 只能是 metadata ref,不得貼 raw live config | +| `maintenance_window_present` | 未來 host read、restart、repair-bot 或 Ansible 動作需獨立維護窗口 | +| `restart_window_separate_from_action` | restart window 與 docker / systemctl action 必須分離 | +| `rollback_owner_present` | rollback owner、rollback ref 或 disable path 必須存在 | +| `post_check_plan_present` | post-check 必須列服務健康、route、queue、log 與 rollback 停止條件 | +| `disable_switch_present` | repair-bot、Ansible role 或 service config 需有 disable switch 或 freeze rule | +| `write_capable_requires_extra_review` | write-capable surface 必須進額外 reviewer review | +| `no_runtime_request` | 夾帶 SSH、Docker、systemctl、repair-bot、Ansible、sudo 或 host write 要求時拒收 | +| `counts_transition_safe` | 只有 reviewer record 可更新 received / accepted / rejected,且不得開 runtime gate | + +## 6. Outcome lanes + +| Lane | 意義 | +|------|------| +| `waiting_owner_response` | 尚未收到 owner response | +| `quarantine_secret_or_raw_payload` | 收到 secret、env dump、raw compose、raw systemd unit 或未脫敏 host config 時隔離 | +| `reject_execution_request` | 夾帶 SSH、Docker、systemctl、repair-bot、Ansible、sudo 或 host write 要求時拒收 | +| `request_supplement` | 欄位不足、scope 不清、live hash ref / rollback / post-check 缺失時要求補件 | +| `ready_for_host_service_review` | metadata 合格後,只能進 host service reviewer review | +| `owner_review_only_update` | 只允許更新只讀 owner review ledger | +| `waiting_runtime_gate` | 即使 owner response accepted,runtime gate 仍等待獨立人工批准 | + +## 7. 禁止事項 + +1. 不 SSH read / write。 +2. 不執行 `docker compose up/down/pull`。 +3. 不執行 `systemctl restart/reload`。 +4. 不呼叫 repair-bot。 +5. 不跑 Ansible apply。 +6. 不做 sudo action、host file write 或 firewall change。 +7. 不保存 raw live config、env dump、secret value、未脫敏 log 或 shell history。 +8. 不把 restart window、maintenance window 或 owner response 當成 runtime approval。 +9. 不開 runtime gate,不建立 action button。 + +## 8. 指令 + +產生 committed snapshot: + +```bash +python3 scripts/security/host-service-owner-response-acceptance.py \ + --root . \ + --inventory-report docs/security/host-service-config-inventory.snapshot.json \ + --owner-request-report docs/security/host-service-owner-request-draft.snapshot.json \ + --output docs/security/host-service-owner-response-acceptance.snapshot.json \ + --generated-at 2026-06-14T23:45:00+08:00 +``` + +驗證 guard: + +```bash +python3 scripts/security/security-mirror-progress-guard.py --root . +``` + +## 9. 完成度 + +| 工作 | 完成度 | 說明 | +|------|--------|------| +| owner response acceptance ledger artifact | `100%` | 產生器、snapshot 與文件已固定 | +| Docker / systemd / host service 只讀治理成熟度 | `50% -> 54%` | 收件驗收帳本更完整;不代表 live evidence 或 runtime action | +| owner response received / accepted | `0%` | 尚未收到,尚未驗收 | +| live config hash accepted | `0%` | 尚未收到 owner-provided metadata ref | +| live host read / SSH | `0%` | 尚未批准且未執行 | +| Docker / systemd / repair-bot / Ansible | `0%` | 尚未批准且未執行 | +| runtime gate / host write | `0%` | 未授權且未執行 | diff --git a/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md b/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md index 7a8eb268f..d67c6545b 100644 --- a/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md +++ b/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md @@ -32,8 +32,8 @@ | 註冊配置類別 | `14` | 代表已進 Gate 分類,不代表已批准 | | C0 類別 | `8` | Nginx、DNS / TLS、K8s、secret、workflow / runner、runtime config、backup、agent-bounty runtime | | C1 類別 | `4` | 監控、Docker / systemd、SSH / network、AI provider | -| 平均只讀控管成熟度 | `67%` | 只代表框架 / evidence / owner packet / acceptance ledger 準備度 | -| 需要 live evidence 類別 | `7` | 只能等 owner-provided redacted evidence 或維護窗口,不主動 SSH | +| 平均只讀控管成熟度 | `68%` | 只代表框架 / evidence / owner packet / acceptance ledger 準備度 | +| 需要 live evidence 類別 | `6` | 只能等 owner-provided redacted evidence 或維護窗口,不主動 SSH | | owner response required | `14` | owner response received / accepted 仍 `0 / 0` | | runtime gate | `0` | 不提供執行按鈕 | @@ -89,6 +89,12 @@ 此更新只表示 SAN / wildcard / 共用憑證覆蓋關係、certificate expiry metadata、renewal owner、ACME route owner、maintenance window、rollback owner 與 validation plan 已有收件驗收規則;owner response received / accepted、certificate coverage confirmed、DNS query、TLS probe、certbot renew、Nginx reload、route smoke、DNS record / certificate path / ACME route 變更、host write、runtime gate 仍全部為 `0 / false`。 +### 0.8 2026-06-14 Docker / systemd / host service owner response acceptance 只讀帳本 + +`host_service_owner_response_acceptance_v1` 已把 9 份 Docker / systemd / host service owner request draft 轉成 owner response acceptance 只讀帳本。固定 `candidates=9`、`write_capable=3`、`live_evidence_required=8`、`owner_fields=12`、`reviewer_checks=14`、`outcome_lanes=7`、`blocked_actions=20`,讓 Docker / systemd / host service 類別成熟度從 `50%` 推進到 `54%`,高價值配置平均只讀成熟度從 `67%` 推進到 `68%`。 + +此更新只表示 live config hash ref、maintenance / restart window、rollback owner、post-check plan、disable switch 與 no-secret-value evidence 已有收件驗收規則;owner response received / accepted、live host read、SSH、Docker Compose、systemctl、repair-bot、Ansible、sudo、host write、runtime gate 仍全部為 `0 / false`。 + ## 1. 目前已不符合新要求的項目 | 優先 | 項目 | 現況 | 風險 | 本階段處置 | @@ -102,7 +108,7 @@ | P1 | 高價值配置變更 Gate | 已有 C0-C3 清冊與 Hard Rule,但原本缺少可重跑 path 分類 | reviewer 只能靠人工記憶判斷 Nginx、workflow、secret、K8s、DNS、AI provider 是否需 owner gate | 已新增 `scripts/security/high-value-config-change-gate.py`;本階段只分類,不接 CI blocking | | P0 | DNS / TLS / certbot | 已有 domain / certificate path 清冊、owner confirmation request 與 owner response acceptance 只讀帳本,但仍缺 owner-provided coverage metadata、expiry metadata、renewal owner、ACME route owner、maintenance window 與 rollback owner | 憑證過期、錯誤 cert path、ACME challenge 被覆蓋會造成公開服務中斷 | 維持 C0;下一步只收脫敏 metadata ref,不做 DNS query、TLS probe、certbot renew 或 reload | | P1 | workflow / runner / deploy key / secret name | 已有 Gitea / GitHub readiness 盤點,但尚未把配置變更和 IwoooS 高價值配置共用 gate 合併 | workflow 或 runner 改錯會直接影響部署與 secret 注入 | 納入 C0,維持只讀 owner response,不收 secret value | -| P1 | Docker Compose / systemd live config | 110 / 188 多服務由 compose、systemd 與 recovery scripts 管理 | restart policy、port、volume、env 改動會影響 Harbor、Sentry、Langfuse、Gitea、agent-bounty-protocol | 已有 repo-only inventory 與 owner request draft;下一步只收脫敏 owner response / live hash,不主動 SSH | +| P1 | Docker Compose / systemd live config | 已有 repo-only inventory、owner request draft 與 owner response acceptance 只讀帳本,但仍缺 owner-provided live hash、maintenance / restart window、rollback owner、post-check plan、disable switch 與 no-secret-value evidence | restart policy、port、volume、env 改動會影響 Harbor、Sentry、Langfuse、Gitea 與代理賞金協議 runtime | 下一步只收脫敏 owner response / live hash metadata,不主動 SSH、不重啟、不跑 repair-bot | | P1 | AI provider / Ollama proxy | 110 Nginx proxy、GCP-A/B、111 fallback、API provider route 多處配置 | provider route drift 會造成成本、可用性、資料外送與模型品質風險 | 納入 C1,任何切換仍需 dry-run / benchmark / owner gate | | P1 | agent-bounty-protocol runtime / treasury / A2A / MCP | 已納入只讀範圍,但尚未有 production host、compose、domain、TLS、rollback owner 完整資料 | 外部 agent、claim / submit、payout 或 webhook 若未控管,風險高於一般網站 | 納入 C2,仍不改該 repo、不讀 `.env`、不部署 | @@ -155,7 +161,7 @@ Nginx 是目前必須最先資安控管的配置,原因是它同時控制公 | P1 | Grafana / SigNoz / Sentry / Langfuse | `ops/grafana/*`、`ops/signoz/*`、`ops/sentry-self-hosted/*`、`infra/langfuse/*`、`docs/security/MONITORING-ALERTING-OBSERVABILITY-INVENTORY.md`、`docs/security/MONITORING-OWNER-REQUEST-DRAFT.md` | 110 compose / public gateway | owner request draft 已固定;仍缺 admin secret owner、public route proof、backup owner、smoke plan、upgrade window 與 rollback owner | | P1 | Harbor / registry | Nginx templates、backup scripts、CD workflows | 110 Harbor / registry domains | robot account owner、image tag immutability、scan policy、TLS | | P1 | PostgreSQL / Redis / MinIO | app config、backup scripts、monitoring config | 188 / 110 / K3s | no plaintext DSN, access boundary, backup, restore, metrics auth | -| P1 | Docker Compose / systemd | `docker-compose.yml`、`ops/*/docker-compose.yml`、`scripts/reboot-recovery/*.service` | 110 / 188 / agent-bounty hosts | port / volume / env diff、restart window、rollback owner | +| P1 | Docker Compose / systemd | `docker-compose.yml`、`ops/*/docker-compose.yml`、`scripts/reboot-recovery/*.service`、`docs/security/HOST-SERVICE-OWNER-REQUEST-DRAFT.md`、`docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md` | 110 / 188 / agent-bounty hosts | live hash metadata、port / volume / env diff、maintenance / restart window、rollback owner、post-check plan、disable switch;不得 restart / apply | | P1 | SSH / sudoers / known_hosts | Ansible inventory、ops scripts、runner scripts | host owners | owner request draft 已固定;下一步只收脫敏 live access state、known_hosts / host-key policy、target whitelist 與 rollback owner | | P1 | Firewall / WireGuard / NodePort / VIP | K8s service / network policy、Kali / wg-easy docs | network owner | owner request draft 已固定;下一步只收 allowed source CIDR、maintenance window、rollback owner 與 validation plan;no unreviewed port exposure | | P1 | AI provider / model routing | `apps/api/src/services/ai_providers/*`、Ollama runbooks、Nginx proxy | AI owner | dry-run、benchmark、cost / privacy review、fallback order gate | @@ -215,6 +221,7 @@ Nginx 是目前必須最先資安控管的配置,原因是它同時控制公 | owner response request / received / accepted | `0%` | Packet 只是草案;尚未送件、尚未收件、尚未 reviewer accepted | | agent-bounty-protocol owner request draft | `100%` | 已將 repo / refs、deployment、data classification、external agent / treasury 與 7 個 product surface 轉成 11 份 owner request draft;claim / submit、payout、cron / daemon、runtime gate 仍為 0 | | Docker / systemd owner request draft | `100%` | 已將 9 個 host service surface 轉成 owner request draft;request sent / received / accepted 仍為 0 | +| Docker / systemd owner response acceptance | `100%` | 已新增 `host_service_owner_response_acceptance_v1`,9 個 candidate、3 個 write-capable、8 個需 live evidence、14 個 reviewer checks、7 條 outcome lanes、20 類 blocked action;成熟度 `50% -> 54%` | | SSH / firewall / network owner request draft | `100%` | 已將 16 個 SSH / network access surface 轉成 owner request draft;request sent / received / accepted、port change、firewall change、NetworkPolicy apply、NodePort change、WireGuard change 仍為 0 | | SSH / firewall / network owner response acceptance | `100%` | 已新增 `ssh_network_owner_response_acceptance_v1`,16 個 candidate、6 個 write-capable、15 個 reviewer checks、7 條 outcome lanes、22 類 blocked action;成熟度 `54% -> 58%` | | Backup / restore / escrow owner request draft | `100%` | 已將 38 個 backup / restore / escrow surface 轉成 owner request draft;request sent / received / accepted、backup run、restore run、offsite sync、remote delete、escrow marker write、retention change 仍為 0 | @@ -232,7 +239,7 @@ Nginx 是目前必須最先資安控管的配置,原因是它同時控制公 3. P0:向 owner 收 DNS / TLS / certbot 脫敏 coverage metadata ref、expiry metadata ref、renewal owner、ACME route owner、maintenance window、rollback owner 與 validation plan;不做 DNS query、TLS probe、certbot renew 或 reload。 4. P0:把 workflow / runner / secret name owner response 與高價值配置 C0 gate 串成同一個 IwoooS 狀態。 5. P0:把 agent-bounty-protocol compose / MCP / A2A / treasury 高價值配置欄位接入同一個 owner packet queue;不啟用 runtime。 -6. P1:向 owner 收 110 / 188 Docker Compose 與 systemd 脫敏 live hash、restart window、rollback owner 與 post-check 指標;不主動 SSH、不重啟、不跑 repair-bot。 +6. P1:向 owner 收 110 / 188 Docker Compose 與 systemd 脫敏 live hash metadata、maintenance / restart window、rollback owner、post-check plan、disable switch 與 no-secret-value evidence;不主動 SSH、不重啟、不跑 repair-bot。 7. P1:向 owner 收 SSH / firewall / WireGuard / NodePort 脫敏 live access state、allowed source CIDR、maintenance window、rollback owner 與 validation plan;不主動 keyscan、不改 firewall、不開關端口。 8. P1:向 owner 收 backup / restore / offsite / escrow 非敏感 evidence id、最新備份狀態、restore drill plan、maintenance window、rollback owner 與 validation plan;驗收前 backup run、restore drill、offsite sync、remote delete、escrow marker write、retention change 全部維持 `0 / false`。 9. P1:把 Prometheus / Alertmanager / Grafana / SigNoz / Sentry / Langfuse owner request draft 接入 AwoooP 只讀狀態;驗收前 reload、receiver route change、silence change、Telegram send 與 alert chain smoke 全部維持 `0 / false`。 diff --git a/docs/security/IWOOOS-POSTURE-PROJECTION.md b/docs/security/IWOOOS-POSTURE-PROJECTION.md index c61885c3e..50ce07390 100644 --- a/docs/security/IWOOOS-POSTURE-PROJECTION.md +++ b/docs/security/IWOOOS-POSTURE-PROJECTION.md @@ -95,7 +95,7 @@ IwoooS 首版只讀取或對齊以下已提交 evidence: 51. 10 個 frontend surface reverse bridge statuses,顯示既有資安入口目前是 embedded bridge、direct bridge 或 AwoooP read-only candidate;這只是連接狀態,不代表 owner response、runtime authorization、Code Review blocker、Gitea/GitHub action 或任何執行控制。 52. 6 個 source control primary readiness items,顯示 GitHub primary 前置缺口:candidate repo inventory、primary ready counter、owner response validation、refs truth、workflow / secret name inventory、rollback ADR;這只是 readiness,不代表 repo 建立、visibility 變更、refs mutation、secret value collection、primary switch 或 Gitea 停用。 53. 4 個 rollout risk read-only items,顯示風險來源部署 marker、`AWOOOI_ROLLOUT_RISK=1`、ArgoCD `Degraded` / `OutOfSync`、API health / smoke 已通過與執行期閘門仍為 0;這只是部署風險可見性,不代表 ArgoCD sync、kubectl、主機重啟、修復、部署或 runtime gate 已授權。 -54. 14 類 high-value config control coverage statuses,顯示 Nginx、DNS / TLS、K8s、secret、workflow、backup、agent-bounty runtime、monitoring、Docker / systemd、SSH / network、AI provider、產品 route 與 security evidence 的全域配置控管覆蓋矩陣;平均只讀成熟度 `67%`、C0 類別 `8`、需 live evidence 類別 `7`、owner response received / accepted 與 runtime gate 仍為 `0`,不代表 reload、sync、scan、secret rotation、payout 或主機操作授權。 +54. 14 類 high-value config control coverage statuses,顯示 Nginx、DNS / TLS、K8s、secret、workflow、backup、agent-bounty runtime、monitoring、Docker / systemd、SSH / network、AI provider、產品 route 與 security evidence 的全域配置控管覆蓋矩陣;平均只讀成熟度 `68%`、C0 類別 `8`、需 live evidence 類別 `6`、owner response received / accepted 與 runtime gate 仍為 `0`,不代表 reload、sync、scan、secret rotation、payout 或主機操作授權。 55. 9 個 host-service config repo-only inventory surfaces,顯示 Docker Compose、systemd / repair-bot、Ansible service role 與 host config backup capture 的第一層清冊;write-capable surface `3`、repair-bot whitelist `2`、systemd restart surface `1`,owner response、live evidence、restart window、rollback owner、runtime gate 與 action button 仍全部為 `0`,不代表 `docker compose`、`systemctl`、repair-bot 或 Ansible apply 已授權。 56. 16 個 SSH / network access repo-only inventory surfaces 與 owner response acceptance 只讀帳本,顯示 SSH target、known_hosts workflow、CI deploy SSH、monitoring SSH、backup SSH capture、sudoers wrapper、NetworkPolicy、NodePort、WireGuard runbook 與 alert SSH action catalog 的第一層清冊;write-capable surface `6`、NetworkPolicy `2`、NodePort `2`、sudoers `1`、WireGuard `1`,acceptance candidate `16`、reviewer check `15`、outcome lane `7`、blocked action `22`,讓 SSH / network 類別成熟度從 `54%` 推進到 `58%`;owner response、live evidence、maintenance window、rollback owner、runtime gate 與 action button 仍全部為 `0`,不代表 SSH、sudo、firewall、NetworkPolicy、NodePort、WireGuard 或 known_hosts patch 已授權。 57. 38 個 Backup / restore / escrow / retention repo-only inventory surfaces,顯示 backup orchestration、service backup scripts、restic retention、offsite sync、credential escrow、Velero restore drill、backup health alert 與 cold-start / DR runbook 的第一層清冊;write-capable surface `27`、restore drill surface `4`、offsite / escrow surface `8`,owner response、live evidence、restore drill、offsite sync、credential escrow、retention change、runtime gate 與 action button 仍全部為 `0`,不代表 backup、restore、offsite sync、remote delete、restic prune、escrow marker write、rclone config 或 Velero restore 已授權。 diff --git a/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md b/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md index 2787b8416..161c3e3cc 100644 --- a/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md +++ b/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md @@ -3,7 +3,7 @@ | 項目 | 內容 | |------|------| | 日期 | 2026-06-15 | -| 狀態 | IwoooS 64% 只讀治理推進中;P2-145 owner response acceptance gate 已完成正式驗證;P0 DNS / TLS / certbot owner response acceptance 只讀帳本已本地完成;S4.9 owner response gate 仍是第一優先 | +| 狀態 | IwoooS 64% 只讀治理推進中;P2-145 owner response acceptance gate 已完成正式驗證;P1 Docker / systemd / host service owner response acceptance 只讀帳本已本地完成;S4.9 owner response gate 仍是第一優先 | | 本階段完成 | 資安供應鏈 contract manifest + Source Control Approval Board + Draft Reconcile Plan + Ref Detail Diff + Ref Truth Classification + Source Control Ref Truth Owner Response 收件包 + GitHub Primary Readiness Gate + GitHub Primary Rollback ADR + GitHub Target Owner Decision Response 收件包 + Gitea 認證清冊匯出請求 + Gitea 認證清冊匯入驗收契約 + Gitea 清冊覆蓋 Owner Attestation + Gitea Owner Attestation Approval Lane 對齊 + Gitea Owner Attestation Response 收件包 + Workflow / Secret Name Inventory + Workflow / Secret Name Local Evidence + Workflow / Secret Name Redacted Export Request + Workflow / Secret Name Owner Response 收件包 + Source Control Owner Response Validation Rollup + Kali 112 live integration status + Security Finding contract + Kali scan scope approval package + Security Approval Queue + S3 人工批准 Gate + S3 人工決策紀錄 + S3 人工審查封包 + S3 人工決策狀態轉移 + S3 後續 runtime gate 準備契約 + 鏡像 readiness index + 鏡像接收計畫 + 鏡像事件信封 + 鏡像路由矩陣 + 鏡像驗收契約 + 鏡像隔離契約 + 鏡像 dry-run 報告契約 + 鏡像狀態彙整契約 + IwoooS 前端態勢入口 + IwoooS posture projection contract + IwoooS 既有前端資安頁面整合 + IwoooS 覆蓋與邊界矩陣 + IwoooS 只讀資安處理旅程 + IwoooS owner evidence readiness board + IwoooS host coverage view + IwoooS host action gate matrix + IwoooS host evidence readiness board + IwoooS host evidence collection order + IwoooS host evidence intake preflight + IwoooS host evidence review outcome lanes + IwoooS host evidence review handoff packets + IwoooS host evidence reviewer checklist + IwoooS host evidence reviewer outcome lanes + IwoooS host owner decision candidate packets + IwoooS host owner decision review checklist + IwoooS host owner decision review outcome lanes + IwoooS host owner decision record draft packets + IwoooS host owner decision record draft review checklist + IwoooS host owner decision record draft review outcome lanes + IwoooS host owner decision record write-up packets + IwoooS host owner decision record write-up review checklist + IwoooS host owner decision record write-up review outcome lanes + IwoooS host owner decision record formal candidate packets + IwoooS host owner decision record formal candidate review checklist + IwoooS host owner decision record formal candidate review outcome lanes + IwoooS host owner decision record formal record queue packets + IwoooS host owner decision record formal record queue review checklist + IwoooS host owner decision record formal record queue review outcome lanes + IwoooS host owner decision record human handoff readiness packets + IwoooS host owner decision record human handoff readiness review checklist + IwoooS host owner decision record human handoff readiness review outcome lanes + IwoooS host owner decision record human record owner review candidate packets + IwoooS host owner decision record human record owner review candidate checklist + IwoooS host owner decision record human record owner review candidate outcome lanes + IwoooS host owner decision record human record owner review preparation packets + IwoooS host owner decision record human record owner review preparation checklist + IwoooS progress acceleration lanes + IwoooS owner response next-action focus + IwoooS S4.9 owner response preflight + IwoooS S4.9 owner response request templates + IwoooS progress hold movement gates + IwoooS AwoooP read-only landing readiness + IwoooS AwoooP cross-session handoff packets + AwoooP 首頁 IwoooS 資安鏡像候選 + AwoooP 工作鏈路 IwoooS 資安鏡像候選 + AwoooP 審批佇列 IwoooS owner response 只讀焦點 | | 本階段追加 | AwoooP 合約儀表板 IwoooS 資安契約只讀候選 + AwoooP 租戶管理 IwoooS 資安租戶範圍只讀候選 + AwoooP 執行監控 IwoooS 執行狀態只讀候選 + 既有安全 / 合規頁面 IwoooS 只讀反向橋接 + 告警 / 錯誤 / 授權 / 治理頁面 IwoooS 只讀反向橋接 + 稽核 / 工程審查頁面 IwoooS 深色只讀反向橋接 + IwoooS 前端資安頁面連接狀態板 + IwoooS GitHub 主要來源就緒度只讀狀態板 + AwoooP 工作鏈路 GitHub 主要來源就緒度只讀工作項 + AwoooP 合約儀表板 GitHub 主要來源就緒度合約只讀候選 + AwoooP 審批佇列 GitHub 主要來源就緒度審批邊界 + AwoooP 首頁 GitHub 主要來源就緒度只讀摘要 + AwoooP 租戶管理 GitHub 主要來源就緒度租戶範圍 + AwoooP 執行監控 GitHub 主要來源就緒度執行邊界 + IwoooS / AwoooP 資安可視區塊繁體中文呈現防護檢查 + AwoooP 執行詳情 / 審批詳情繁體中文呈現防護檢查 + AwoooP 首頁負責人回覆驗收總覽 + AwoooP 工作鏈路負責人回覆驗收只讀工作項 + AwoooP 合約儀表板負責人回覆驗收契約只讀候選 + AwoooP 審批佇列負責人回覆驗收只讀審查邊界 + AwoooP 租戶管理負責人回覆驗收租戶範圍 + AwoooP 執行監控負責人回覆驗收執行邊界 + AwoooP 執行詳情負責人回覆驗收詳情邊界 + AwoooP 審批決策負責人回覆驗收審批邊界 + IwoooS AwoooP 資安入口覆蓋狀態板 + IwoooS 階段式資安收斂節奏圖 + IwoooS 下一步人工收件作戰板 + IwoooS 人工回覆安全驗收閘道 + IwoooS 人工回覆審查結果分流 + IwoooS 人工決策準備佇列 + IwoooS 人工決策紀錄草稿防誤用 + IwoooS 人工決策正式紀錄負責人指派確認準備包 + IwoooS 人工決策正式紀錄負責人指派確認清單 + IwoooS 人工決策正式紀錄負責人指派確認結果分流 + IwoooS 人工決策正式紀錄負責人指派決策準備包 + IwoooS 人工決策正式紀錄負責人指派決策檢查清單 + IwoooS S4.9 負責人回覆封套欄位 + IwoooS S4.9 負責人回覆封套送件前檢查 + IwoooS S4.9 負責人回覆封套送件前結果分流 + IwoooS S4.9 負責人回覆送件請求草稿 + IwoooS S4.9 負責人回覆送件鏈路摘要 + IwoooS 低摩擦分階段收斂主控 + IwoooS 低摩擦下一步行動邊界 + IwoooS 64% 進度移動訊號驗收條 + IwoooS 第一個進度解鎖路徑 + IwoooS 第一解鎖證據包 + IwoooS 第一解鎖證據包預檢分流 + IwoooS 第一解鎖證據包補件路徑 + IwoooS 第一解鎖證據包補件送審前檢查 + IwoooS 第一解鎖證據包補件送審結果分流 + IwoooS 第一解鎖證據包 reviewer 指派準備包 + IwoooS 第一解鎖證據包 reviewer 指派前檢查 + IwoooS 第一解鎖證據包 reviewer 指派前檢查結果分流 + IwoooS 正式只讀 landing 與 Kali 112 只讀證據進度重估 | | 本階段追加補充 | IwoooS 目前具體工作地圖 + IwoooS 目前具體交付清單 + IwoooS 目前阻塞與解除條件 + IwoooS 三軸進度與全產品套用範圍 + IwoooS 全產品分階段套用台帳 + IwoooS 全產品 rollout 波次驗收門檻 + IwoooS 全產品 rollout 驗收結果分流 + IwoooS 全產品證據接線地圖 + IwoooS 全產品證據接線預檢 + IwoooS 全產品證據接線預檢結果分流 + IwoooS 全產品預檢補件回收台帳 + IwoooS 全產品補件重試門檻 + IwoooS 全產品重試結果分流 + IwoooS 全產品人工審查候選準備 + IwoooS 全產品人工審查候選預檢 + IwoooS 全產品人工審查候選預檢結果分流 + IwoooS 全產品人工審查候選預檢補件回收台帳 + IwoooS 全產品人工審查候選預檢補件重試門檻 + IwoooS 全產品只讀套用快照 + P2-145 owner response acceptance gate 正式驗證完成 | @@ -13,6 +13,12 @@ | 原則 | 低摩擦分階段;文件、schema、read-only evidence 優先;不做 runtime enforcement、不切 primary | | P0 主控板 | `docs/workplans/2026-06-04-iwooos-security-governance-p0.md` | +## 0.00a 2026-06-14 Docker / systemd / host service owner response acceptance + +本輪把 Docker / systemd / host service 從 owner request draft 推進到 owner response acceptance 只讀帳本:`host_service_owner_response_acceptance_v1` 固定 `candidates=9`、`write_capable=3`、`live_evidence_required=8`、`owner_fields=12`、`reviewer_checks=14`、`outcome_lanes=7`、`blocked_actions=20`,並讓 `docker_compose_systemd_host_config` 只讀治理成熟度 `50% -> 54%`,高價值配置平均只讀成熟度 `67% -> 68%`。這是 metadata-only 收件驗收,不是 request sent、owner response received / accepted、live host read、SSH、Docker Compose、systemctl、repair-bot、Ansible、sudo、host write、production write 或 runtime gate。 + +同步邊界:IwoooS headline 維持 `64%`,active runtime gate 維持 `0`;owner response / live evidence / runtime gate / action buttons 全部仍為 `0 / false`。本段只更新文件、snapshot、guard 與覆蓋矩陣,不改前端 bundle、不部署、不碰 Docker daemon、systemd、repair-bot、Ansible、Nginx、firewall 或主機。 + ## 0.00 2026-06-14 DNS / TLS / certbot owner response acceptance 本輪把 DNS / TLS / certbot 從 owner confirmation request 草稿推進到 owner response acceptance 只讀帳本:`domain_tls_certbot_owner_response_acceptance_v1` 固定 `candidates=4`、`c0=4`、`owner_fields=13`、`reviewer_checks=13`、`outcome_lanes=7`、`blocked_actions=20`,並讓 `dns_tls_certbot` 只讀治理成熟度 `74% -> 78%`。這是 metadata-only 收件驗收,不是 request sent、owner response received / accepted、certificate coverage confirmed、DNS query、live TLS probe、certbot renew、Nginx reload、route smoke、host write、production write 或 runtime gate。 diff --git a/docs/security/high-value-config-control-coverage.snapshot.json b/docs/security/high-value-config-control-coverage.snapshot.json index 3d6ca170c..6a76bea71 100644 --- a/docs/security/high-value-config-control-coverage.snapshot.json +++ b/docs/security/high-value-config-control-coverage.snapshot.json @@ -349,17 +349,21 @@ "action_buttons_allowed": false, "category_id": "docker_compose_systemd_host_config", "control_tier": "C1", - "coverage_percent": 50, - "coverage_status": "repo_only_inventory_ready_needs_live_owner_evidence", - "current_gap": "repo-only 清冊已納入 9 個 surface;仍缺 110 / 188 live hash、restart window、rollback owner 與 post-check 指標。", + "coverage_percent": 54, + "coverage_status": "owner_response_acceptance_ledger_ready_needs_live_owner_evidence", + "current_gap": "已固定 9 份 Docker / systemd / host service owner response acceptance candidate;仍缺 owner response、110 / 188 live hash、maintenance / restart window、rollback owner、post-check plan、disable switch 與 no-secret-value evidence。", "evidence_refs": [ "docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md", "docs/security/HOST-SERVICE-CONFIG-INVENTORY.md", "docs/security/host-service-config-inventory.snapshot.json", + "docs/security/HOST-SERVICE-OWNER-REQUEST-DRAFT.md", + "docs/security/host-service-owner-request-draft.snapshot.json", + "docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md", + "docs/security/host-service-owner-response-acceptance.snapshot.json", "docs/security/DEV-HOSTS-112-111-168-OBSERVE-ONLY-MAPPING.md" ], "label": "Docker Compose / systemd / host service config", - "next_owner_action": "補 owner-provided live hash / disposition、compose / systemd owner、restart window、rollback owner 與 post-check 指標。", + "next_owner_action": "補 owner-provided live hash / disposition、compose / systemd owner、maintenance / restart window、rollback owner、post-check plan 與 disable switch。", "owner_response_accepted": false, "owner_response_received": false, "owner_response_required": true, @@ -584,15 +588,15 @@ "websocket_route_change_authorized": false, "workflow_modification_authorized": false }, - "generated_at": "2026-06-14T23:05:00+08:00", - "git_commit": "d26f3bef", + "generated_at": "2026-06-14T23:45:00+08:00", + "git_commit": "92e451cb", "lowest_coverage_categories": [ { "category_id": "docker_compose_systemd_host_config", - "coverage_percent": 50, - "current_gap": "repo-only 清冊已納入 9 個 surface;仍缺 110 / 188 live hash、restart window、rollback owner 與 post-check 指標。", + "coverage_percent": 54, + "current_gap": "已固定 9 份 Docker / systemd / host service owner response acceptance candidate;仍缺 owner response、110 / 188 live hash、maintenance / restart window、rollback owner、post-check plan、disable switch 與 no-secret-value evidence。", "label": "Docker Compose / systemd / host service config", - "next_owner_action": "補 owner-provided live hash / disposition、compose / systemd owner、restart window、rollback owner 與 post-check 指標。" + "next_owner_action": "補 owner-provided live hash / disposition、compose / systemd owner、maintenance / restart window、rollback owner、post-check plan 與 disable switch。" }, { "category_id": "ssh_firewall_network_access", @@ -638,14 +642,14 @@ "status": "coverage_matrix_ready", "summary": { "action_button_count": 0, - "average_coverage_percent": 67, + "average_coverage_percent": 68, "c0_category_count": 8, "c1_category_count": 4, "c2_category_count": 1, "c3_category_count": 1, "category_count": 14, "lowest_coverage_category_count": 4, - "needs_live_evidence_count": 7, + "needs_live_evidence_count": 6, "owner_response_accepted_count": 0, "owner_response_received_count": 0, "owner_response_required_count": 14, diff --git a/docs/security/host-service-owner-response-acceptance.snapshot.json b/docs/security/host-service-owner-response-acceptance.snapshot.json new file mode 100644 index 000000000..443511b1e --- /dev/null +++ b/docs/security/host-service-owner-response-acceptance.snapshot.json @@ -0,0 +1,1598 @@ +{ + "acceptance_candidates": [ + { + "acceptance_candidate_id": "host_service_owner_response_acceptance:local_dev_compose", + "acceptance_fields": [ + "acceptance_candidate_id", + "request_id", + "surface_id", + "label", + "expected_host_scope", + "config_kind", + "service_scope", + "control_tier", + "repo_source_path", + "repo_sha256", + "source_line_count", + "write_capable_surface", + "requires_live_evidence", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "action_buttons_allowed": false, + "active_scan_authorized": false, + "affected_scope": "pending_owner_response", + "ansible_apply_authorized": false, + "blocked_actions": [ + "ssh_read", + "ssh_write", + "docker_compose_up", + "docker_compose_down", + "docker_compose_pull", + "systemctl_restart", + "systemctl_reload", + "repair_bot_execute", + "ansible_apply", + "sudo_action", + "host_file_write", + "firewall_change", + "secret_value_collection", + "active_scan", + "live_host_read", + "raw_live_config_storage", + "restart_without_window", + "rollback_without_owner", + "runtime_gate_open", + "add_action_button" + ], + "config_kind": "docker_compose_source", + "control_tier": "C1", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "disable_switch": "pending_owner_response", + "disable_switch_accepted": false, + "docker_compose_action_authorized": false, + "expected_host_scope": "local_dev_only", + "followup_owner": "pending_owner_response", + "host_write_authorized": false, + "label": "AWOOOI local development compose", + "live_config_hash_accepted": false, + "live_config_hash_ref": null, + "live_evidence_received": false, + "live_host_read_authorized": false, + "maintenance_window": "pending_owner_response", + "maintenance_window_accepted": false, + "not_approval": true, + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_supplement", + "ready_for_host_service_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "owner_response_accepted": false, + "owner_response_quarantined": false, + "owner_response_received": false, + "owner_response_ref": null, + "owner_response_rejected": false, + "owner_role_or_team": "pending_owner_response", + "post_check_plan": "pending_owner_response", + "post_check_plan_accepted": false, + "recipient_confirmed": false, + "redacted_evidence_refs": [], + "repair_bot_execution_authorized": false, + "repo_sha256": "4a27bcde139b5aef6a9f3080187af5bec73d1efd9c09ed2752b0baaa5f507024", + "repo_source_path": "docker-compose.yml", + "request_id": "host_service_owner_request:local_dev_compose", + "request_sent": false, + "required_owner_fields": [ + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "followup_owner" + ], + "requires_live_evidence": false, + "restart_window": "pending_owner_response", + "restart_window_accepted": false, + "reviewer_checks": [ + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_surface", + "redacted_refs_only", + "secret_value_absent", + "live_config_hash_metadata_only", + "maintenance_window_present", + "restart_window_separate_from_action", + "rollback_owner_present", + "post_check_plan_present", + "disable_switch_present", + "write_capable_requires_extra_review", + "no_runtime_request", + "counts_transition_safe" + ], + "reviewer_outcome": "waiting_owner_response", + "rollback_owner": "pending_owner_response", + "rollback_owner_accepted": false, + "runtime_gate": false, + "secret_value_collection_allowed": false, + "service_scope": [ + "web", + "api", + "postgres", + "redis" + ], + "source_line_count": 137, + "ssh_read_authorized": false, + "ssh_write_authorized": false, + "status": "waiting_owner_response", + "sudo_action_authorized": false, + "supplement_requested": false, + "surface_id": "local_dev_compose", + "systemctl_action_authorized": false, + "write_capable_surface": false + }, + { + "acceptance_candidate_id": "host_service_owner_response_acceptance:monitoring_110_compose", + "acceptance_fields": [ + "acceptance_candidate_id", + "request_id", + "surface_id", + "label", + "expected_host_scope", + "config_kind", + "service_scope", + "control_tier", + "repo_source_path", + "repo_sha256", + "source_line_count", + "write_capable_surface", + "requires_live_evidence", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "action_buttons_allowed": false, + "active_scan_authorized": false, + "affected_scope": "pending_owner_response", + "ansible_apply_authorized": false, + "blocked_actions": [ + "ssh_read", + "ssh_write", + "docker_compose_up", + "docker_compose_down", + "docker_compose_pull", + "systemctl_restart", + "systemctl_reload", + "repair_bot_execute", + "ansible_apply", + "sudo_action", + "host_file_write", + "firewall_change", + "secret_value_collection", + "active_scan", + "live_host_read", + "raw_live_config_storage", + "restart_without_window", + "rollback_without_owner", + "runtime_gate_open", + "add_action_button" + ], + "config_kind": "docker_compose_source", + "control_tier": "C1", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "disable_switch": "pending_owner_response", + "disable_switch_accepted": false, + "docker_compose_action_authorized": false, + "expected_host_scope": "192.168.0.110", + "followup_owner": "pending_owner_response", + "host_write_authorized": false, + "label": "110 monitoring docker compose", + "live_config_hash_accepted": false, + "live_config_hash_ref": null, + "live_evidence_received": false, + "live_host_read_authorized": false, + "maintenance_window": "pending_owner_response", + "maintenance_window_accepted": false, + "not_approval": true, + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_supplement", + "ready_for_host_service_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "owner_response_accepted": false, + "owner_response_quarantined": false, + "owner_response_received": false, + "owner_response_ref": null, + "owner_response_rejected": false, + "owner_role_or_team": "pending_owner_response", + "post_check_plan": "pending_owner_response", + "post_check_plan_accepted": false, + "recipient_confirmed": false, + "redacted_evidence_refs": [], + "repair_bot_execution_authorized": false, + "repo_sha256": "00126e9a5cb7a3cf2bf02cfddefea11f05849b46835a4e602eac4777fcb25281", + "repo_source_path": "k8s/monitoring/docker-compose-110.yml", + "request_id": "host_service_owner_request:monitoring_110_compose", + "request_sent": false, + "required_owner_fields": [ + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "followup_owner" + ], + "requires_live_evidence": true, + "restart_window": "pending_owner_response", + "restart_window_accepted": false, + "reviewer_checks": [ + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_surface", + "redacted_refs_only", + "secret_value_absent", + "live_config_hash_metadata_only", + "maintenance_window_present", + "restart_window_separate_from_action", + "rollback_owner_present", + "post_check_plan_present", + "disable_switch_present", + "write_capable_requires_extra_review", + "no_runtime_request", + "counts_transition_safe" + ], + "reviewer_outcome": "waiting_owner_response", + "rollback_owner": "pending_owner_response", + "rollback_owner_accepted": false, + "runtime_gate": false, + "secret_value_collection_allowed": false, + "service_scope": [ + "cadvisor", + "prometheus", + "grafana", + "blackbox-exporter", + "alertmanager", + "github-exporter" + ], + "source_line_count": 148, + "ssh_read_authorized": false, + "ssh_write_authorized": false, + "status": "waiting_owner_response", + "sudo_action_authorized": false, + "supplement_requested": false, + "surface_id": "monitoring_110_compose", + "systemctl_action_authorized": false, + "write_capable_surface": false + }, + { + "acceptance_candidate_id": "host_service_owner_response_acceptance:monitoring_exporters_188_compose", + "acceptance_fields": [ + "acceptance_candidate_id", + "request_id", + "surface_id", + "label", + "expected_host_scope", + "config_kind", + "service_scope", + "control_tier", + "repo_source_path", + "repo_sha256", + "source_line_count", + "write_capable_surface", + "requires_live_evidence", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "action_buttons_allowed": false, + "active_scan_authorized": false, + "affected_scope": "pending_owner_response", + "ansible_apply_authorized": false, + "blocked_actions": [ + "ssh_read", + "ssh_write", + "docker_compose_up", + "docker_compose_down", + "docker_compose_pull", + "systemctl_restart", + "systemctl_reload", + "repair_bot_execute", + "ansible_apply", + "sudo_action", + "host_file_write", + "firewall_change", + "secret_value_collection", + "active_scan", + "live_host_read", + "raw_live_config_storage", + "restart_without_window", + "rollback_without_owner", + "runtime_gate_open", + "add_action_button" + ], + "config_kind": "docker_compose_source", + "control_tier": "C1", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "disable_switch": "pending_owner_response", + "disable_switch_accepted": false, + "docker_compose_action_authorized": false, + "expected_host_scope": "192.168.0.188", + "followup_owner": "pending_owner_response", + "host_write_authorized": false, + "label": "188 database exporters compose", + "live_config_hash_accepted": false, + "live_config_hash_ref": null, + "live_evidence_received": false, + "live_host_read_authorized": false, + "maintenance_window": "pending_owner_response", + "maintenance_window_accepted": false, + "not_approval": true, + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_supplement", + "ready_for_host_service_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "owner_response_accepted": false, + "owner_response_quarantined": false, + "owner_response_received": false, + "owner_response_ref": null, + "owner_response_rejected": false, + "owner_role_or_team": "pending_owner_response", + "post_check_plan": "pending_owner_response", + "post_check_plan_accepted": false, + "recipient_confirmed": false, + "redacted_evidence_refs": [], + "repair_bot_execution_authorized": false, + "repo_sha256": "3ffb3bd2e98091d18e60b74721904777c27f279c37ab6e873b82e6ef73eb87d4", + "repo_source_path": "ops/monitoring/docker-compose.exporters.yaml", + "request_id": "host_service_owner_request:monitoring_exporters_188_compose", + "request_sent": false, + "required_owner_fields": [ + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "followup_owner" + ], + "requires_live_evidence": true, + "restart_window": "pending_owner_response", + "restart_window_accepted": false, + "reviewer_checks": [ + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_surface", + "redacted_refs_only", + "secret_value_absent", + "live_config_hash_metadata_only", + "maintenance_window_present", + "restart_window_separate_from_action", + "rollback_owner_present", + "post_check_plan_present", + "disable_switch_present", + "write_capable_requires_extra_review", + "no_runtime_request", + "counts_transition_safe" + ], + "reviewer_outcome": "waiting_owner_response", + "rollback_owner": "pending_owner_response", + "rollback_owner_accepted": false, + "runtime_gate": false, + "secret_value_collection_allowed": false, + "service_scope": [ + "postgres-exporter", + "redis-exporter" + ], + "source_line_count": 69, + "ssh_read_authorized": false, + "ssh_write_authorized": false, + "status": "waiting_owner_response", + "sudo_action_authorized": false, + "supplement_requested": false, + "surface_id": "monitoring_exporters_188_compose", + "systemctl_action_authorized": false, + "write_capable_surface": false + }, + { + "acceptance_candidate_id": "host_service_owner_response_acceptance:sentry_110_reference_compose", + "acceptance_fields": [ + "acceptance_candidate_id", + "request_id", + "surface_id", + "label", + "expected_host_scope", + "config_kind", + "service_scope", + "control_tier", + "repo_source_path", + "repo_sha256", + "source_line_count", + "write_capable_surface", + "requires_live_evidence", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "action_buttons_allowed": false, + "active_scan_authorized": false, + "affected_scope": "pending_owner_response", + "ansible_apply_authorized": false, + "blocked_actions": [ + "ssh_read", + "ssh_write", + "docker_compose_up", + "docker_compose_down", + "docker_compose_pull", + "systemctl_restart", + "systemctl_reload", + "repair_bot_execute", + "ansible_apply", + "sudo_action", + "host_file_write", + "firewall_change", + "secret_value_collection", + "active_scan", + "live_host_read", + "raw_live_config_storage", + "restart_without_window", + "rollback_without_owner", + "runtime_gate_open", + "add_action_button" + ], + "config_kind": "docker_compose_reference", + "control_tier": "C1", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "disable_switch": "pending_owner_response", + "disable_switch_accepted": false, + "docker_compose_action_authorized": false, + "expected_host_scope": "192.168.0.110", + "followup_owner": "pending_owner_response", + "host_write_authorized": false, + "label": "110 Sentry self-hosted reference compose", + "live_config_hash_accepted": false, + "live_config_hash_ref": null, + "live_evidence_received": false, + "live_host_read_authorized": false, + "maintenance_window": "pending_owner_response", + "maintenance_window_accepted": false, + "not_approval": true, + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_supplement", + "ready_for_host_service_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "owner_response_accepted": false, + "owner_response_quarantined": false, + "owner_response_received": false, + "owner_response_ref": null, + "owner_response_rejected": false, + "owner_role_or_team": "pending_owner_response", + "post_check_plan": "pending_owner_response", + "post_check_plan_accepted": false, + "recipient_confirmed": false, + "redacted_evidence_refs": [], + "repair_bot_execution_authorized": false, + "repo_sha256": "bba852dc0d73934998fa375130168615f9ac7611ce3f3efaa901e3b7e222eae3", + "repo_source_path": "ops/sentry-self-hosted/docker-compose.yml", + "request_id": "host_service_owner_request:sentry_110_reference_compose", + "request_sent": false, + "required_owner_fields": [ + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "followup_owner" + ], + "requires_live_evidence": true, + "restart_window": "pending_owner_response", + "restart_window_accepted": false, + "reviewer_checks": [ + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_surface", + "redacted_refs_only", + "secret_value_absent", + "live_config_hash_metadata_only", + "maintenance_window_present", + "restart_window_separate_from_action", + "rollback_owner_present", + "post_check_plan_present", + "disable_switch_present", + "write_capable_requires_extra_review", + "no_runtime_request", + "counts_transition_safe" + ], + "reviewer_outcome": "waiting_owner_response", + "rollback_owner": "pending_owner_response", + "rollback_owner_accepted": false, + "runtime_gate": false, + "secret_value_collection_allowed": false, + "service_scope": [ + "sentry-placeholder-reference" + ], + "source_line_count": 49, + "ssh_read_authorized": false, + "ssh_write_authorized": false, + "status": "waiting_owner_response", + "sudo_action_authorized": false, + "supplement_requested": false, + "surface_id": "sentry_110_reference_compose", + "systemctl_action_authorized": false, + "write_capable_surface": false + }, + { + "acceptance_candidate_id": "host_service_owner_response_acceptance:langfuse_110_compose", + "acceptance_fields": [ + "acceptance_candidate_id", + "request_id", + "surface_id", + "label", + "expected_host_scope", + "config_kind", + "service_scope", + "control_tier", + "repo_source_path", + "repo_sha256", + "source_line_count", + "write_capable_surface", + "requires_live_evidence", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "action_buttons_allowed": false, + "active_scan_authorized": false, + "affected_scope": "pending_owner_response", + "ansible_apply_authorized": false, + "blocked_actions": [ + "ssh_read", + "ssh_write", + "docker_compose_up", + "docker_compose_down", + "docker_compose_pull", + "systemctl_restart", + "systemctl_reload", + "repair_bot_execute", + "ansible_apply", + "sudo_action", + "host_file_write", + "firewall_change", + "secret_value_collection", + "active_scan", + "live_host_read", + "raw_live_config_storage", + "restart_without_window", + "rollback_without_owner", + "runtime_gate_open", + "add_action_button" + ], + "config_kind": "docker_compose_source", + "control_tier": "C1", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "disable_switch": "pending_owner_response", + "disable_switch_accepted": false, + "docker_compose_action_authorized": false, + "expected_host_scope": "192.168.0.110", + "followup_owner": "pending_owner_response", + "host_write_authorized": false, + "label": "110 Langfuse compose", + "live_config_hash_accepted": false, + "live_config_hash_ref": null, + "live_evidence_received": false, + "live_host_read_authorized": false, + "maintenance_window": "pending_owner_response", + "maintenance_window_accepted": false, + "not_approval": true, + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_supplement", + "ready_for_host_service_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "owner_response_accepted": false, + "owner_response_quarantined": false, + "owner_response_received": false, + "owner_response_ref": null, + "owner_response_rejected": false, + "owner_role_or_team": "pending_owner_response", + "post_check_plan": "pending_owner_response", + "post_check_plan_accepted": false, + "recipient_confirmed": false, + "redacted_evidence_refs": [], + "repair_bot_execution_authorized": false, + "repo_sha256": "6c703a27525e62ef4d4d3c4cba8a89d64f646b01020782e35d22a3bf73f2dc83", + "repo_source_path": "infra/langfuse/docker-compose.yml", + "request_id": "host_service_owner_request:langfuse_110_compose", + "request_sent": false, + "required_owner_fields": [ + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "followup_owner" + ], + "requires_live_evidence": true, + "restart_window": "pending_owner_response", + "restart_window_accepted": false, + "reviewer_checks": [ + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_surface", + "redacted_refs_only", + "secret_value_absent", + "live_config_hash_metadata_only", + "maintenance_window_present", + "restart_window_separate_from_action", + "rollback_owner_present", + "post_check_plan_present", + "disable_switch_present", + "write_capable_requires_extra_review", + "no_runtime_request", + "counts_transition_safe" + ], + "reviewer_outcome": "waiting_owner_response", + "rollback_owner": "pending_owner_response", + "rollback_owner_accepted": false, + "runtime_gate": false, + "secret_value_collection_allowed": false, + "service_scope": [ + "langfuse", + "langfuse-db" + ], + "source_line_count": 71, + "ssh_read_authorized": false, + "ssh_write_authorized": false, + "status": "waiting_owner_response", + "sudo_action_authorized": false, + "supplement_requested": false, + "surface_id": "langfuse_110_compose", + "systemctl_action_authorized": false, + "write_capable_surface": false + }, + { + "acceptance_candidate_id": "host_service_owner_response_acceptance:ansible_docker_compose_service_role", + "acceptance_fields": [ + "acceptance_candidate_id", + "request_id", + "surface_id", + "label", + "expected_host_scope", + "config_kind", + "service_scope", + "control_tier", + "repo_source_path", + "repo_sha256", + "source_line_count", + "write_capable_surface", + "requires_live_evidence", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "action_buttons_allowed": false, + "active_scan_authorized": false, + "affected_scope": "pending_owner_response", + "ansible_apply_authorized": false, + "blocked_actions": [ + "ssh_read", + "ssh_write", + "docker_compose_up", + "docker_compose_down", + "docker_compose_pull", + "systemctl_restart", + "systemctl_reload", + "repair_bot_execute", + "ansible_apply", + "sudo_action", + "host_file_write", + "firewall_change", + "secret_value_collection", + "active_scan", + "live_host_read", + "raw_live_config_storage", + "restart_without_window", + "rollback_without_owner", + "runtime_gate_open", + "add_action_button" + ], + "config_kind": "ansible_service_executor", + "control_tier": "C1", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "disable_switch": "pending_owner_response", + "disable_switch_accepted": false, + "docker_compose_action_authorized": false, + "expected_host_scope": "multi_host", + "followup_owner": "pending_owner_response", + "host_write_authorized": false, + "label": "Ansible docker-compose-service role", + "live_config_hash_accepted": false, + "live_config_hash_ref": null, + "live_evidence_received": false, + "live_host_read_authorized": false, + "maintenance_window": "pending_owner_response", + "maintenance_window_accepted": false, + "not_approval": true, + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_supplement", + "ready_for_host_service_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "owner_response_accepted": false, + "owner_response_quarantined": false, + "owner_response_received": false, + "owner_response_ref": null, + "owner_response_rejected": false, + "owner_role_or_team": "pending_owner_response", + "post_check_plan": "pending_owner_response", + "post_check_plan_accepted": false, + "recipient_confirmed": false, + "redacted_evidence_refs": [], + "repair_bot_execution_authorized": false, + "repo_sha256": "cee214a8651f46c2d8be05054dddadc243a26bff51a64bd9cf42dd2ec0b7b1b3", + "repo_source_path": "infra/ansible/roles/docker-compose-service/tasks/main.yml", + "request_id": "host_service_owner_request:ansible_docker_compose_service_role", + "request_sent": false, + "required_owner_fields": [ + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "followup_owner" + ], + "requires_live_evidence": true, + "restart_window": "pending_owner_response", + "restart_window_accepted": false, + "reviewer_checks": [ + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_surface", + "redacted_refs_only", + "secret_value_absent", + "live_config_hash_metadata_only", + "maintenance_window_present", + "restart_window_separate_from_action", + "rollback_owner_present", + "post_check_plan_present", + "disable_switch_present", + "write_capable_requires_extra_review", + "no_runtime_request", + "counts_transition_safe" + ], + "reviewer_outcome": "waiting_owner_response", + "rollback_owner": "pending_owner_response", + "rollback_owner_accepted": false, + "runtime_gate": false, + "secret_value_collection_allowed": false, + "service_scope": [ + "docker compose up -d" + ], + "source_line_count": 18, + "ssh_read_authorized": false, + "ssh_write_authorized": false, + "status": "waiting_owner_response", + "sudo_action_authorized": false, + "supplement_requested": false, + "surface_id": "ansible_docker_compose_service_role", + "systemctl_action_authorized": false, + "write_capable_surface": true + }, + { + "acceptance_candidate_id": "host_service_owner_response_acceptance:repair_bot_110_whitelist", + "acceptance_fields": [ + "acceptance_candidate_id", + "request_id", + "surface_id", + "label", + "expected_host_scope", + "config_kind", + "service_scope", + "control_tier", + "repo_source_path", + "repo_sha256", + "source_line_count", + "write_capable_surface", + "requires_live_evidence", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "action_buttons_allowed": false, + "active_scan_authorized": false, + "affected_scope": "pending_owner_response", + "ansible_apply_authorized": false, + "blocked_actions": [ + "ssh_read", + "ssh_write", + "docker_compose_up", + "docker_compose_down", + "docker_compose_pull", + "systemctl_restart", + "systemctl_reload", + "repair_bot_execute", + "ansible_apply", + "sudo_action", + "host_file_write", + "firewall_change", + "secret_value_collection", + "active_scan", + "live_host_read", + "raw_live_config_storage", + "restart_without_window", + "rollback_without_owner", + "runtime_gate_open", + "add_action_button" + ], + "config_kind": "host_repair_whitelist", + "control_tier": "C1", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "disable_switch": "pending_owner_response", + "disable_switch_accepted": false, + "docker_compose_action_authorized": false, + "expected_host_scope": "192.168.0.110", + "followup_owner": "pending_owner_response", + "host_write_authorized": false, + "label": "110 repair-bot compose whitelist", + "live_config_hash_accepted": false, + "live_config_hash_ref": null, + "live_evidence_received": false, + "live_host_read_authorized": false, + "maintenance_window": "pending_owner_response", + "maintenance_window_accepted": false, + "not_approval": true, + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_supplement", + "ready_for_host_service_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "owner_response_accepted": false, + "owner_response_quarantined": false, + "owner_response_received": false, + "owner_response_ref": null, + "owner_response_rejected": false, + "owner_role_or_team": "pending_owner_response", + "post_check_plan": "pending_owner_response", + "post_check_plan_accepted": false, + "recipient_confirmed": false, + "redacted_evidence_refs": [], + "repair_bot_execution_authorized": false, + "repo_sha256": "093d4f85c398806dee62c2831fa4fe7e1f8fddca6e3cfcc9dbe4d5e0d66cdf3b", + "repo_source_path": "scripts/repair-bot/repair-bot-110.sh", + "request_id": "host_service_owner_request:repair_bot_110_whitelist", + "request_sent": false, + "required_owner_fields": [ + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "followup_owner" + ], + "requires_live_evidence": true, + "restart_window": "pending_owner_response", + "restart_window_accepted": false, + "reviewer_checks": [ + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_surface", + "redacted_refs_only", + "secret_value_absent", + "live_config_hash_metadata_only", + "maintenance_window_present", + "restart_window_separate_from_action", + "rollback_owner_present", + "post_check_plan_present", + "disable_switch_present", + "write_capable_requires_extra_review", + "no_runtime_request", + "counts_transition_safe" + ], + "reviewer_outcome": "waiting_owner_response", + "rollback_owner": "pending_owner_response", + "rollback_owner_accepted": false, + "runtime_gate": false, + "secret_value_collection_allowed": false, + "service_scope": [ + "sentry", + "harbor", + "gitea", + "gitea-runner", + "langfuse", + "alertmanager", + "signoz" + ], + "source_line_count": 67, + "ssh_read_authorized": false, + "ssh_write_authorized": false, + "status": "waiting_owner_response", + "sudo_action_authorized": false, + "supplement_requested": false, + "surface_id": "repair_bot_110_whitelist", + "systemctl_action_authorized": false, + "write_capable_surface": true + }, + { + "acceptance_candidate_id": "host_service_owner_response_acceptance:repair_bot_188_whitelist", + "acceptance_fields": [ + "acceptance_candidate_id", + "request_id", + "surface_id", + "label", + "expected_host_scope", + "config_kind", + "service_scope", + "control_tier", + "repo_source_path", + "repo_sha256", + "source_line_count", + "write_capable_surface", + "requires_live_evidence", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "action_buttons_allowed": false, + "active_scan_authorized": false, + "affected_scope": "pending_owner_response", + "ansible_apply_authorized": false, + "blocked_actions": [ + "ssh_read", + "ssh_write", + "docker_compose_up", + "docker_compose_down", + "docker_compose_pull", + "systemctl_restart", + "systemctl_reload", + "repair_bot_execute", + "ansible_apply", + "sudo_action", + "host_file_write", + "firewall_change", + "secret_value_collection", + "active_scan", + "live_host_read", + "raw_live_config_storage", + "restart_without_window", + "rollback_without_owner", + "runtime_gate_open", + "add_action_button" + ], + "config_kind": "host_repair_whitelist", + "control_tier": "C1", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "disable_switch": "pending_owner_response", + "disable_switch_accepted": false, + "docker_compose_action_authorized": false, + "expected_host_scope": "192.168.0.188", + "followup_owner": "pending_owner_response", + "host_write_authorized": false, + "label": "188 repair-bot compose/systemd whitelist", + "live_config_hash_accepted": false, + "live_config_hash_ref": null, + "live_evidence_received": false, + "live_host_read_authorized": false, + "maintenance_window": "pending_owner_response", + "maintenance_window_accepted": false, + "not_approval": true, + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_supplement", + "ready_for_host_service_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "owner_response_accepted": false, + "owner_response_quarantined": false, + "owner_response_received": false, + "owner_response_ref": null, + "owner_response_rejected": false, + "owner_role_or_team": "pending_owner_response", + "post_check_plan": "pending_owner_response", + "post_check_plan_accepted": false, + "recipient_confirmed": false, + "redacted_evidence_refs": [], + "repair_bot_execution_authorized": false, + "repo_sha256": "fb2eb786d04edbf5d5be581a53bbe188ac66f0895aa016328b031c72f6182918", + "repo_source_path": "scripts/repair-bot/repair-bot-188.sh", + "request_id": "host_service_owner_request:repair_bot_188_whitelist", + "request_sent": false, + "required_owner_fields": [ + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "followup_owner" + ], + "requires_live_evidence": true, + "restart_window": "pending_owner_response", + "restart_window_accepted": false, + "reviewer_checks": [ + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_surface", + "redacted_refs_only", + "secret_value_absent", + "live_config_hash_metadata_only", + "maintenance_window_present", + "restart_window_separate_from_action", + "rollback_owner_present", + "post_check_plan_present", + "disable_switch_present", + "write_capable_requires_extra_review", + "no_runtime_request", + "counts_transition_safe" + ], + "reviewer_outcome": "waiting_owner_response", + "rollback_owner": "pending_owner_response", + "rollback_owner_accepted": false, + "runtime_gate": false, + "secret_value_collection_allowed": false, + "service_scope": [ + "openclaw", + "minio", + "signoz", + "redis", + "nginx", + "ollama" + ], + "source_line_count": 85, + "ssh_read_authorized": false, + "ssh_write_authorized": false, + "status": "waiting_owner_response", + "sudo_action_authorized": false, + "supplement_requested": false, + "surface_id": "repair_bot_188_whitelist", + "systemctl_action_authorized": false, + "write_capable_surface": true + }, + { + "acceptance_candidate_id": "host_service_owner_response_acceptance:config_backup_host_capture", + "acceptance_fields": [ + "acceptance_candidate_id", + "request_id", + "surface_id", + "label", + "expected_host_scope", + "config_kind", + "service_scope", + "control_tier", + "repo_source_path", + "repo_sha256", + "source_line_count", + "write_capable_surface", + "requires_live_evidence", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "action_buttons_allowed": false, + "active_scan_authorized": false, + "affected_scope": "pending_owner_response", + "ansible_apply_authorized": false, + "blocked_actions": [ + "ssh_read", + "ssh_write", + "docker_compose_up", + "docker_compose_down", + "docker_compose_pull", + "systemctl_restart", + "systemctl_reload", + "repair_bot_execute", + "ansible_apply", + "sudo_action", + "host_file_write", + "firewall_change", + "secret_value_collection", + "active_scan", + "live_host_read", + "raw_live_config_storage", + "restart_without_window", + "rollback_without_owner", + "runtime_gate_open", + "add_action_button" + ], + "config_kind": "backup_capture_contract", + "control_tier": "C1", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "disable_switch": "pending_owner_response", + "disable_switch_accepted": false, + "docker_compose_action_authorized": false, + "expected_host_scope": "110_188_120_121_cluster", + "followup_owner": "pending_owner_response", + "host_write_authorized": false, + "label": "host config backup capture contract", + "live_config_hash_accepted": false, + "live_config_hash_ref": null, + "live_evidence_received": false, + "live_host_read_authorized": false, + "maintenance_window": "pending_owner_response", + "maintenance_window_accepted": false, + "not_approval": true, + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_supplement", + "ready_for_host_service_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "owner_response_accepted": false, + "owner_response_quarantined": false, + "owner_response_received": false, + "owner_response_ref": null, + "owner_response_rejected": false, + "owner_role_or_team": "pending_owner_response", + "post_check_plan": "pending_owner_response", + "post_check_plan_accepted": false, + "recipient_confirmed": false, + "redacted_evidence_refs": [], + "repair_bot_execution_authorized": false, + "repo_sha256": "d24301cff44e464bd19ce0792362be16916ccde8c92f92351a19ef4ee988f15e", + "repo_source_path": "scripts/backup/backup-configs.sh", + "request_id": "host_service_owner_request:config_backup_host_capture", + "request_sent": false, + "required_owner_fields": [ + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "followup_owner" + ], + "requires_live_evidence": true, + "restart_window": "pending_owner_response", + "restart_window_accepted": false, + "reviewer_checks": [ + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_surface", + "redacted_refs_only", + "secret_value_absent", + "live_config_hash_metadata_only", + "maintenance_window_present", + "restart_window_separate_from_action", + "rollback_owner_present", + "post_check_plan_present", + "disable_switch_present", + "write_capable_requires_extra_review", + "no_runtime_request", + "counts_transition_safe" + ], + "reviewer_outcome": "waiting_owner_response", + "rollback_owner": "pending_owner_response", + "rollback_owner_accepted": false, + "runtime_gate": false, + "secret_value_collection_allowed": false, + "service_scope": [ + "systemd", + "docker", + "nginx", + "cron", + "k8s", + "host-configs" + ], + "source_line_count": 359, + "ssh_read_authorized": false, + "ssh_write_authorized": false, + "status": "waiting_owner_response", + "sudo_action_authorized": false, + "supplement_requested": false, + "surface_id": "config_backup_host_capture", + "systemctl_action_authorized": false, + "write_capable_surface": false + } + ], + "acceptance_fields": [ + "acceptance_candidate_id", + "request_id", + "surface_id", + "label", + "expected_host_scope", + "config_kind", + "service_scope", + "control_tier", + "repo_source_path", + "repo_sha256", + "source_line_count", + "write_capable_surface", + "requires_live_evidence", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "blocked_actions": [ + "ssh_read", + "ssh_write", + "docker_compose_up", + "docker_compose_down", + "docker_compose_pull", + "systemctl_restart", + "systemctl_reload", + "repair_bot_execute", + "ansible_apply", + "sudo_action", + "host_file_write", + "firewall_change", + "secret_value_collection", + "active_scan", + "live_host_read", + "raw_live_config_storage", + "restart_without_window", + "rollback_without_owner", + "runtime_gate_open", + "add_action_button" + ], + "execution_boundaries": { + "action_buttons_allowed": false, + "active_scan_authorized": false, + "ansible_apply_authorized": false, + "docker_compose_action_authorized": false, + "host_write_authorized": false, + "live_host_read_authorized": false, + "not_authorization": true, + "owner_response_accepted": false, + "repair_bot_execution_authorized": false, + "request_dispatch_authorized": false, + "runtime_execution_authorized": false, + "secret_value_collection_allowed": false, + "ssh_read_authorized": false, + "ssh_write_authorized": false, + "sudo_action_authorized": false, + "systemctl_action_authorized": false + }, + "generated_at": "2026-06-14T23:45:00+08:00", + "git_commit": "92e451cb", + "next_steps": [ + "等待 owner 以脫敏 metadata ref 回覆 live config hash、maintenance / restart window、rollback owner、post-check plan 與 disable switch。", + "收到回覆後先做欄位完整性、敏感 payload 隔離與 execution request 拒收,不得直接 host read、restart、repair-bot 或 Ansible apply。", + "write-capable surface 必須額外 reviewer review,且 runtime gate 需獨立人工批准、rollback 與 post-check 成立。" + ], + "outcome_lanes": [ + { + "lane_id": "waiting_owner_response", + "meaning": "尚未收到 owner response;所有 accepted / runtime count 維持 0。" + }, + { + "lane_id": "quarantine_secret_or_raw_payload", + "meaning": "收到 secret、env dump、raw compose、raw systemd unit 或未脫敏 host config 時隔離。" + }, + { + "lane_id": "reject_execution_request", + "meaning": "夾帶 SSH、docker compose、systemctl、repair-bot、Ansible、sudo 或 host write 要求時拒收。" + }, + { + "lane_id": "request_supplement", + "meaning": "欄位不足、scope 不清、live hash ref / rollback / post-check 缺失時要求補件。" + }, + { + "lane_id": "ready_for_host_service_review", + "meaning": "metadata 合格後,只能進 host service reviewer review。" + }, + { + "lane_id": "owner_review_only_update", + "meaning": "只允許更新只讀 owner review ledger,不得改 compose、systemd、repair-bot 或 Ansible。" + }, + { + "lane_id": "waiting_runtime_gate", + "meaning": "即使 owner response accepted,runtime gate 仍等待獨立人工批准。" + } + ], + "reviewer_checks": [ + { + "check_id": "owner_identity_present", + "instruction": "owner role / team 必須可追溯。" + }, + { + "check_id": "decision_reason_present", + "instruction": "decision 與 decision reason 必須同時存在。" + }, + { + "check_id": "affected_scope_matches_surface", + "instruction": "affected scope 必須能對回 committed surface_id。" + }, + { + "check_id": "redacted_refs_only", + "instruction": "evidence 只能是脫敏 ref、hash、ticket、commit 或 artifact pointer。" + }, + { + "check_id": "secret_value_absent", + "instruction": "不得出現 token、password、cookie、private key、env dump 或 partial secret。" + }, + { + "check_id": "live_config_hash_metadata_only", + "instruction": "live config hash 只能是 owner-provided metadata ref,不得貼 raw live config。" + }, + { + "check_id": "maintenance_window_present", + "instruction": "未來 host read、restart、repair-bot 或 Ansible 動作需獨立維護窗口。" + }, + { + "check_id": "restart_window_separate_from_action", + "instruction": "restart window 與 docker / systemctl action 必須分離,不得自動執行。" + }, + { + "check_id": "rollback_owner_present", + "instruction": "rollback owner、rollback ref 或 disable path 必須存在。" + }, + { + "check_id": "post_check_plan_present", + "instruction": "post-check 必須列服務健康、route、queue、log 與 rollback 停止條件。" + }, + { + "check_id": "disable_switch_present", + "instruction": "repair-bot、Ansible role 或 service config 需有 disable switch 或 freeze rule。" + }, + { + "check_id": "write_capable_requires_extra_review", + "instruction": "write-capable surface 必須進額外 reviewer review,不得直接 accepted。" + }, + { + "check_id": "no_runtime_request", + "instruction": "夾帶 SSH、Docker、systemctl、repair-bot、Ansible、sudo 或 host write 要求時拒收。" + }, + { + "check_id": "counts_transition_safe", + "instruction": "只有 reviewer record 可更新 received / accepted / rejected;不得同時開 runtime gate。" + } + ], + "schema_version": "host_service_owner_response_acceptance_v1", + "source_inventory_schema_version": "host_service_config_inventory_v1", + "source_inventory_status": "repo_only_inventory_ready", + "source_owner_request_schema_version": "host_service_owner_request_draft_v1", + "source_owner_request_status": "owner_request_draft_ready_not_dispatched", + "status": "owner_response_acceptance_ledger_ready_no_runtime_action", + "summary": { + "acceptance_candidate_count": 9, + "acceptance_field_count": 28, + "action_button_count": 0, + "active_scan_authorized_count": 0, + "ansible_apply_authorized_count": 0, + "blocked_action_count": 20, + "disable_switch_accepted_count": 0, + "docker_compose_action_authorized_count": 0, + "host_write_authorized_count": 0, + "live_config_hash_accepted_count": 0, + "live_evidence_received_count": 0, + "live_evidence_required_candidate_count": 8, + "live_host_read_authorized_count": 0, + "maintenance_window_accepted_count": 0, + "outcome_lane_count": 7, + "owner_response_accepted_count": 0, + "owner_response_quarantined_count": 0, + "owner_response_received_count": 0, + "owner_response_rejected_count": 0, + "post_check_plan_accepted_count": 0, + "recipient_confirmed_count": 0, + "repair_bot_execution_authorized_count": 0, + "request_sent_count": 0, + "required_owner_field_count": 12, + "restart_window_accepted_count": 0, + "reviewer_check_count": 14, + "rollback_owner_accepted_count": 0, + "runtime_gate_count": 0, + "secret_value_collection_allowed_count": 0, + "source_owner_request_count": 9, + "ssh_read_authorized_count": 0, + "ssh_write_authorized_count": 0, + "sudo_action_authorized_count": 0, + "supplement_requested_count": 0, + "systemctl_action_authorized_count": 0, + "write_capable_acceptance_candidate_count": 3 + } +} diff --git a/docs/security/iwooos-posture-projection.snapshot.json b/docs/security/iwooos-posture-projection.snapshot.json index 1c5541996..051bb9a74 100644 --- a/docs/security/iwooos-posture-projection.snapshot.json +++ b/docs/security/iwooos-posture-projection.snapshot.json @@ -34,6 +34,8 @@ "docs/schemas/domain_tls_certbot_inventory_v1.schema.json", "docs/security/host-service-config-inventory.snapshot.json", "docs/security/HOST-SERVICE-CONFIG-INVENTORY.md", + "docs/security/host-service-owner-response-acceptance.snapshot.json", + "docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md", "docs/schemas/host_service_config_inventory_v1.schema.json", "docs/security/ssh-network-access-inventory.snapshot.json", "docs/security/SSH-NETWORK-ACCESS-INVENTORY.md", @@ -258,8 +260,8 @@ "high_value_config_control_coverage_category_count": 14, "high_value_config_control_coverage_c0_category_count": 8, "high_value_config_control_coverage_c1_category_count": 4, - "high_value_config_control_coverage_average_percent": 67, - "high_value_config_control_coverage_needs_live_evidence_count": 7, + "high_value_config_control_coverage_average_percent": 68, + "high_value_config_control_coverage_needs_live_evidence_count": 6, "high_value_config_control_coverage_owner_response_required_count": 14, "high_value_config_control_coverage_owner_response_received_count": 0, "high_value_config_control_coverage_owner_response_accepted_count": 0, diff --git a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md index 4c5d255d5..0335c590b 100644 --- a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md +++ b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md @@ -4849,6 +4849,19 @@ Trigger commit `f5cd37b7` 與 deploy marker `0ba92357` 已把 governance UI 的 **裁決:** 這是人工送件前 request draft,不是 request sent、recipient confirmed、owner response received / accepted、live evidence、Prometheus reload、Alertmanager reload、Grafana import、SigNoz apply、Sentry deploy、Langfuse change、OTEL reload、receiver route change、silence change、Telegram send、live alert fire、alert chain smoke、secret collection、host write、active scan、production write 或 runtime gate;IwoooS headline 仍維持 `64%`,active runtime gate 仍 `0`。 +### 2026-06-14 23:45 (台北) — §3.2 / §5 — 新增 Docker / systemd / Host Service Owner Response Acceptance 只讀帳本 — 把主機服務 owner 回覆驗收固定成可 guard artifact + +**觸發**:110 端口與服務異常事件顯示 Docker Compose、systemd、repair-bot、Ansible role 與 host config backup 不能只停在 repo-only inventory 或 request draft。任何未驗收的 live hash、restart window 或 post-check 回覆若被誤判成 `docker compose`、`systemctl`、repair-bot、Ansible、sudo 或 host write 授權,都可能直接造成 public route、monitoring、registry、Sentry、Langfuse 或 product runtime 事故。 + +**已推進:** +- 新增 `scripts/security/host-service-owner-response-acceptance.py`,讀取 host service inventory 與 owner request draft 兩份 snapshot,產生 9 份 owner response acceptance candidate。 +- 新增 `docs/security/host-service-owner-response-acceptance.snapshot.json`,固定 `acceptance_candidate_count=9`、`write_capable_acceptance_candidate_count=3`、`live_evidence_required_candidate_count=8`、`required_owner_field_count=12`、`reviewer_check_count=14`、`outcome_lane_count=7`、`blocked_action_count=20`。 +- 新增 `docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md`,說明 owner 必填欄位、reviewer checks、outcome lanes、blocked actions、完成度與 0 / false 邊界。 +- `security-mirror-progress-guard.py` 已鎖住新 snapshot 的 schema、summary、execution false flags、candidate ids、reviewer checks、outcome lanes、blocked actions 與每份候選 false flags。 +- 高價值配置 coverage、IwoooS 配置控管總清單、P0 主控板、供應鏈進度與 MASTER artifact 表已同步 P1-14;`docker_compose_systemd_host_config` 只讀治理成熟度從 `50%` 推進到 `54%`,高價值配置平均只讀成熟度從 `67%` 推進到 `68%`,但 runtime gate 仍為 `0`。 + +**裁決:** 這是 owner response acceptance 只讀帳本,不是 request sent、owner response received / accepted、live host read、SSH、Docker Compose、systemctl、repair-bot、Ansible、sudo、host write、firewall change、secret collection、active scan、production write 或 runtime gate;IwoooS headline 仍維持 `64%`,active runtime gate 仍 `0`。 + ### 2026-06-15 00:08 (台北) — §3.2 / §5 — 新增 K8s / ArgoCD Owner Response Acceptance 只讀帳本 — 把 GitOps owner 回覆收件驗收固定成可 guard artifact **觸發**:K8s / ArgoCD / Velero / monitoring manifests 屬於 P0 GitOps 風險面,任何 ArgoCD sync、kubectl action、secret collection、RBAC / NetworkPolicy change 或 restore backup 都可能影響 production。既有 manifest inventory 與 owner request draft 已完成,但仍缺 owner response acceptance ledger,防止未驗收回覆被誤判成 live cluster read、sync 或 kubectl 授權。 diff --git a/docs/workplans/2026-06-04-iwooos-security-governance-p0.md b/docs/workplans/2026-06-04-iwooos-security-governance-p0.md index f28cebbf1..f9f755068 100644 --- a/docs/workplans/2026-06-04-iwooos-security-governance-p0.md +++ b/docs/workplans/2026-06-04-iwooos-security-governance-p0.md @@ -23,7 +23,7 @@ | 最新 AwoooP Tenants 全域產品資產台帳 D1 基準 | code `fef94df8`、deploy marker `180a6543`、code-review `2967`、CD `2966`;正式 API 顯示產品 / 專案 `16`、網站 / 服務入口 `31`、source-control candidate repo `10`,已納入 `2026fifa.wooo.work`、WOOO Open Design、n8n、Grist、Vault、Ollama、Monitor 與 AWOOOI API / AIOps 服務入口;owner response / runtime gate / action button 仍 `0` | | 最新 P0 Public Gateway / DNS TLS / K8s 配置控管基準 | P0-15 `5068654d` live conf 匯出請求包、P0-16 `f856df1c` redacted export 收件預檢、P0-17 `762f73a6` rendered diff / nginx gate 草稿、P0-19 `551d8144` DNS / TLS / certbot owner confirmation request 草稿、P0-20 `e8876c45` K8s / ArgoCD manifest repo-only 清冊、P0-21 `e8de19d7` K8s / ArgoCD owner request draft;本輪新增 P0-24 Public Gateway owner response acceptance 只讀帳本、P0-25 K8s / ArgoCD owner response acceptance 只讀帳本與 P0-27 DNS / TLS / certbot owner response acceptance 只讀帳本;Public Gateway 四段固定 requests / candidates `3`、C0 `2`、owner response acceptance fields `12`、reviewer checks `12`、outcome lanes `7`,DNS / TLS 固定 owner confirmation requests `4`、C0 `4`,DNS / TLS owner response acceptance 固定 candidates `4`、C0 `4`、owner fields `13`、reviewer checks `13`、outcome lanes `7`、blocked actions `20`,K8s manifest 固定 files `49`、C0 `36`、YAML `45`、kinds `20`,K8s owner request 固定 drafts `4`、C0 `3`,K8s owner response acceptance 固定 candidates `4`、C0 `3`、reviewer checks `12`、outcome lanes `7`;request sent、owner response received / accepted、redacted export received / accepted、raw conf stored、rendered diff ready、`nginx -t`、reload、DNS query、TLS probe、certbot renew、ArgoCD API read、ArgoCD sync、kubectl action、route smoke、runtime gate、action button 全部 `0` | | 最新 P0 agent-bounty-protocol 配置控管基準 | onboarding handoff 已固定 `product_scope_handoff_only`、owner response / runtime gate 全部 `false`;本輪新增 owner request draft,固定 `drafts=11`、`control=4`、`surface=7`、`write_capable=8`、`treasury=4`、`mcp_a2a=5`、`owner_fields=22`、`forbidden_inputs=25`、`blocked_actions=28`、request sent / received / accepted / repo push / refs sync / deploy / claim / submit / payout / daemon / cron / runtime gate 全部 `0` | -| 最新 P1 Docker / systemd / Host Service 配置控管基準 | repo-only inventory 已固定 `surface=9`、`write_capable=3`、`live_evidence_required=8`、成熟度 `42% -> 50%`;本輪新增 owner request draft,固定 `drafts=9`、`owner_fields=12`、`blocked_actions=14`、request sent / received / accepted / live evidence / restart window / rollback owner / host write / runtime gate 全部 `0` | +| 最新 P1 Docker / systemd / Host Service 配置控管基準 | repo-only inventory 已固定 `surface=9`、`write_capable=3`、`live_evidence_required=8`、成熟度 `42% -> 50%`;owner request draft 固定 `drafts=9`、`owner_fields=12`、`blocked_actions=14`;本輪新增 owner response acceptance 只讀帳本,固定 `candidates=9`、`write_capable=3`、`live_evidence_required=8`、`reviewer_checks=14`、`outcome_lanes=7`、`blocked_actions=20`,成熟度 `50% -> 54%`;request sent / received / accepted / live evidence / host read / restart window / rollback owner / host write / runtime gate 全部 `0` | | 最新 P1 SSH / Firewall / Network Access 配置控管基準 | repo-only inventory 已固定 `surface=16`、`write_capable=6`、`live_evidence_required=16`、成熟度 `48% -> 54%`;本輪新增 owner request draft,固定 `drafts=16`、`owner_fields=13`、`blocked_actions=16`、request sent / received / accepted / live access state / firewall / port change / NetworkPolicy / NodePort / WireGuard / host write / runtime gate 全部 `0` | | 最新 P1 Backup / Restore / Escrow 配置控管基準 | repo-only inventory 已固定 `surface=38`、`write_capable=27`、`live_evidence_required=38`、成熟度 `52% -> 58%`;本輪新增 owner request draft,固定 `drafts=38`、`owner_fields=14`、`blocked_actions=18`、request sent / received / accepted / backup run / restore run / offsite sync / remote delete / escrow marker / retention change / runtime gate 全部 `0` | | 最新 P1 Monitoring / Alerting / Observability 配置控管基準 | repo-only inventory 已固定 `surface=60`、`write_capable=11`、`live_evidence_required=60`、成熟度 `56% -> 62%`;本輪新增 owner request draft,固定 `drafts=60`、`owner_fields=14`、`blocked_actions=24`、request sent / received / accepted / live evidence / reload / receiver route change / Telegram send / alert chain smoke / runtime gate 全部 `0` | @@ -308,6 +308,7 @@ P1 只讀重盤階段整體完成度:`70%`。它代表 freshness / inventory / | P1-8 | 111 / 168 開發主機 scope | 已補 `DEV-HOSTS-111-168-SCOPE-HANDOFF.md`、`dev-hosts-111-168-scope-handoff.snapshot.json`、`dev_host_scope_handoff_v1.schema.json`;111 fallback truth / model inventory / service posture 與 168 dev origin / repo hygiene / CORS / local exposure 已拆成 handoff | scope handoff `100%`;主機執行 `0%`;不 credentialed scan、不讀未授權資料、不改 fallback route、不改 CORS / firewall / service | | P1-9 | VibeWork 納入 IwoooS | 已補 `VIBEWORK-IWOOOS-ONBOARDING-HANDOFF.md`、`vibework-iwooos-onboarding-handoff.snapshot.json`、`vibework_iwooos_onboarding_handoff_v1.schema.json`;repo / product / surface / owner / evidence refs / 資料分級 / 部署邊界 / 獨立產品邊界已拆成 handoff | onboarding handoff `100%`;runtime / deploy / repo mutation `0 / false`;docs/specs 繁中,產品責任不合併 | | P1-10 | Docker / systemd / Host Service owner request draft | 已新增 `HOST-SERVICE-OWNER-REQUEST-DRAFT.md`、`host-service-owner-request-draft.snapshot.json` 與產生器;9 個 surface、3 個 write-capable、8 個需 live evidence、12 個 owner 必填欄位、14 類 blocked action | 草稿 artifact `100%`;request sent / received / accepted、live evidence、restart window、rollback owner、host write、runtime gate 仍全部 `0 / false` | +| P1-14 | Docker / systemd / Host Service owner response acceptance 只讀帳本 | 已新增 `HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md`、`host-service-owner-response-acceptance.snapshot.json` 與產生器;9 份 acceptance candidate、3 份 write-capable、8 份需 live evidence、12 個 owner 必填欄位、14 個 reviewer checks、7 條 outcome lanes、20 類 blocked action | 帳本 artifact `100%`;owner response received / accepted、live hash、maintenance / restart window、rollback owner、post-check plan、disable switch、live host read、SSH、Docker Compose、systemctl、repair-bot、Ansible、sudo、host write、runtime gate 仍全部 `0 / false`;Docker / systemd 類別成熟度 `50% -> 54%` | | P1-11 | SSH / Firewall / Network Access owner request draft | 已新增 `SSH-NETWORK-OWNER-REQUEST-DRAFT.md`、`ssh-network-owner-request-draft.snapshot.json` 與產生器;16 個 surface、6 個 write-capable、16 個需 live evidence、13 個 owner 必填欄位、16 類 blocked action | 草稿 artifact `100%`;request sent / received / accepted、live access state、keyscan、known_hosts patch、firewall / port change、NetworkPolicy / NodePort / WireGuard 變更、host write、runtime gate 仍全部 `0 / false` | | P1-12 | Backup / Restore / Escrow owner request draft | 已新增 `BACKUP-RESTORE-OWNER-REQUEST-DRAFT.md`、`backup-restore-owner-request-draft.snapshot.json` 與產生器;38 個 surface、27 個 write-capable、38 個需 live evidence、14 個 owner 必填欄位、18 類 blocked action | 草稿 artifact `100%`;request sent / received / accepted、backup run、restore run、offsite sync、remote delete、escrow marker write、retention change、runtime gate 仍全部 `0 / false` | | P1-13 | Monitoring / Alerting / Observability owner request draft | 已新增 `MONITORING-OWNER-REQUEST-DRAFT.md`、`monitoring-owner-request-draft.snapshot.json` 與產生器;60 個 surface、11 個 write-capable、60 個需 live evidence、14 個 owner 必填欄位、24 類 blocked action | 草稿 artifact `100%`;request sent / received / accepted、live evidence、Prometheus / Alertmanager reload、receiver route change、silence change、Telegram send、alert chain smoke、runtime gate 仍全部 `0 / false` | diff --git a/scripts/security/high-value-config-control-coverage.py b/scripts/security/high-value-config-control-coverage.py index 07de04ff5..68da00d13 100644 --- a/scripts/security/high-value-config-control-coverage.py +++ b/scripts/security/high-value-config-control-coverage.py @@ -138,16 +138,20 @@ CONTROL_STATUS_BY_CATEGORY = { "next_owner_action": "補 Prometheus / Alertmanager / Grafana / SigNoz / Sentry / Langfuse / Telegram owner、live drift evidence、reload window、receiver owner、rollback owner 與 no-secret-value evidence。", }, "docker_compose_systemd_host_config": { - "coverage_status": "repo_only_inventory_ready_needs_live_owner_evidence", - "coverage_percent": 50, + "coverage_status": "owner_response_acceptance_ledger_ready_needs_live_owner_evidence", + "coverage_percent": 54, "evidence_refs": [ "docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md", "docs/security/HOST-SERVICE-CONFIG-INVENTORY.md", "docs/security/host-service-config-inventory.snapshot.json", + "docs/security/HOST-SERVICE-OWNER-REQUEST-DRAFT.md", + "docs/security/host-service-owner-request-draft.snapshot.json", + "docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md", + "docs/security/host-service-owner-response-acceptance.snapshot.json", "docs/security/DEV-HOSTS-112-111-168-OBSERVE-ONLY-MAPPING.md", ], - "current_gap": "repo-only 清冊已納入 9 個 surface;仍缺 110 / 188 live hash、restart window、rollback owner 與 post-check 指標。", - "next_owner_action": "補 owner-provided live hash / disposition、compose / systemd owner、restart window、rollback owner 與 post-check 指標。", + "current_gap": "已固定 9 份 Docker / systemd / host service owner response acceptance candidate;仍缺 owner response、110 / 188 live hash、maintenance / restart window、rollback owner、post-check plan、disable switch 與 no-secret-value evidence。", + "next_owner_action": "補 owner-provided live hash / disposition、compose / systemd owner、maintenance / restart window、rollback owner、post-check plan 與 disable switch。", }, "ssh_firewall_network_access": { "coverage_status": "owner_response_acceptance_ledger_ready_needs_network_owner", diff --git a/scripts/security/host-service-owner-response-acceptance.py b/scripts/security/host-service-owner-response-acceptance.py new file mode 100644 index 000000000..41c2f6d29 --- /dev/null +++ b/scripts/security/host-service-owner-response-acceptance.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python3 +""" +IwoooS Docker / systemd / host service owner response acceptance 只讀帳本產生器。 + +本工具讀取 host service repo-only 清冊與 owner request draft,建立未來 +owner response 如何收件、補件、隔離、拒收或進 host service review 的 +metadata-only acceptance ledger。它不 SSH、不讀 live host、不執行 docker +compose、不執行 systemctl、不呼叫 repair-bot、不跑 Ansible、不收 secret +value、不寫 production host。 +""" + +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)) + +ACCEPTANCE_FIELDS = [ + "acceptance_candidate_id", + "request_id", + "surface_id", + "label", + "expected_host_scope", + "config_kind", + "service_scope", + "control_tier", + "repo_source_path", + "repo_sha256", + "source_line_count", + "write_capable_surface", + "requires_live_evidence", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "live_config_hash_ref", + "maintenance_window", + "restart_window", + "rollback_owner", + "post_check_plan", + "disable_switch", + "reviewer_outcome", + "followup_owner", + "not_approval", +] + +REVIEWER_CHECKS = [ + {"check_id": "owner_identity_present", "instruction": "owner role / team 必須可追溯。"}, + {"check_id": "decision_reason_present", "instruction": "decision 與 decision reason 必須同時存在。"}, + {"check_id": "affected_scope_matches_surface", "instruction": "affected scope 必須能對回 committed surface_id。"}, + {"check_id": "redacted_refs_only", "instruction": "evidence 只能是脫敏 ref、hash、ticket、commit 或 artifact pointer。"}, + {"check_id": "secret_value_absent", "instruction": "不得出現 token、password、cookie、private key、env dump 或 partial secret。"}, + {"check_id": "live_config_hash_metadata_only", "instruction": "live config hash 只能是 owner-provided metadata ref,不得貼 raw live config。"}, + {"check_id": "maintenance_window_present", "instruction": "未來 host read、restart、repair-bot 或 Ansible 動作需獨立維護窗口。"}, + {"check_id": "restart_window_separate_from_action", "instruction": "restart window 與 docker / systemctl action 必須分離,不得自動執行。"}, + {"check_id": "rollback_owner_present", "instruction": "rollback owner、rollback ref 或 disable path 必須存在。"}, + {"check_id": "post_check_plan_present", "instruction": "post-check 必須列服務健康、route、queue、log 與 rollback 停止條件。"}, + {"check_id": "disable_switch_present", "instruction": "repair-bot、Ansible role 或 service config 需有 disable switch 或 freeze rule。"}, + {"check_id": "write_capable_requires_extra_review", "instruction": "write-capable surface 必須進額外 reviewer review,不得直接 accepted。"}, + {"check_id": "no_runtime_request", "instruction": "夾帶 SSH、Docker、systemctl、repair-bot、Ansible、sudo 或 host write 要求時拒收。"}, + {"check_id": "counts_transition_safe", "instruction": "只有 reviewer record 可更新 received / accepted / rejected;不得同時開 runtime gate。"}, +] + +OUTCOME_LANES = [ + {"lane_id": "waiting_owner_response", "meaning": "尚未收到 owner response;所有 accepted / runtime count 維持 0。"}, + {"lane_id": "quarantine_secret_or_raw_payload", "meaning": "收到 secret、env dump、raw compose、raw systemd unit 或未脫敏 host config 時隔離。"}, + {"lane_id": "reject_execution_request", "meaning": "夾帶 SSH、docker compose、systemctl、repair-bot、Ansible、sudo 或 host write 要求時拒收。"}, + {"lane_id": "request_supplement", "meaning": "欄位不足、scope 不清、live hash ref / rollback / post-check 缺失時要求補件。"}, + {"lane_id": "ready_for_host_service_review", "meaning": "metadata 合格後,只能進 host service reviewer review。"}, + {"lane_id": "owner_review_only_update", "meaning": "只允許更新只讀 owner review ledger,不得改 compose、systemd、repair-bot 或 Ansible。"}, + {"lane_id": "waiting_runtime_gate", "meaning": "即使 owner response accepted,runtime gate 仍等待獨立人工批准。"}, +] + +BLOCKED_ACTIONS = [ + "ssh_read", + "ssh_write", + "docker_compose_up", + "docker_compose_down", + "docker_compose_pull", + "systemctl_restart", + "systemctl_reload", + "repair_bot_execute", + "ansible_apply", + "sudo_action", + "host_file_write", + "firewall_change", + "secret_value_collection", + "active_scan", + "live_host_read", + "raw_live_config_storage", + "restart_without_window", + "rollback_without_owner", + "runtime_gate_open", + "add_action_button", +] + + +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 load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def acceptance_candidate(request: dict[str, Any]) -> dict[str, Any]: + surface_id = request["surface_id"] + return { + "acceptance_candidate_id": f"host_service_owner_response_acceptance:{surface_id}", + "status": "waiting_owner_response", + "request_id": request["request_id"], + "surface_id": surface_id, + "label": request["label"], + "expected_host_scope": request["expected_host_scope"], + "config_kind": request["config_kind"], + "service_scope": request["service_scope"], + "control_tier": request["control_tier"], + "repo_source_path": request["repo_source_path"], + "repo_sha256": request["repo_sha256"], + "source_line_count": request["source_line_count"], + "write_capable_surface": request["write_capable_surface"], + "requires_live_evidence": request["requires_live_evidence"], + "owner_response_ref": None, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "live_config_hash_ref": None, + "maintenance_window": "pending_owner_response", + "restart_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "post_check_plan": "pending_owner_response", + "disable_switch": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": ACCEPTANCE_FIELDS, + "required_owner_fields": request["required_owner_fields"], + "reviewer_checks": [item["check_id"] for item in REVIEWER_CHECKS], + "outcome_lanes": [item["lane_id"] for item in OUTCOME_LANES], + "blocked_actions": BLOCKED_ACTIONS, + "not_approval": True, + "request_sent": False, + "recipient_confirmed": False, + "owner_response_received": False, + "owner_response_accepted": False, + "owner_response_rejected": False, + "owner_response_quarantined": False, + "supplement_requested": False, + "live_evidence_received": False, + "live_config_hash_accepted": False, + "maintenance_window_accepted": False, + "restart_window_accepted": False, + "rollback_owner_accepted": False, + "post_check_plan_accepted": False, + "disable_switch_accepted": False, + "host_write_authorized": False, + "ssh_read_authorized": False, + "ssh_write_authorized": False, + "docker_compose_action_authorized": False, + "systemctl_action_authorized": False, + "repair_bot_execution_authorized": False, + "ansible_apply_authorized": False, + "sudo_action_authorized": False, + "secret_value_collection_allowed": False, + "active_scan_authorized": False, + "live_host_read_authorized": False, + "runtime_gate": False, + "action_buttons_allowed": False, + } + + +def build_report( + root: Path, + inventory: dict[str, Any], + request_draft_report: dict[str, Any], + generated_at: str | None, +) -> dict[str, Any]: + report_time = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds") + requests = request_draft_report.get("request_drafts", []) + acceptance_candidates = [acceptance_candidate(item) for item in requests] + write_capable = [item for item in acceptance_candidates if item["write_capable_surface"]] + live_evidence = [item for item in acceptance_candidates if item["requires_live_evidence"]] + + return { + "schema_version": "host_service_owner_response_acceptance_v1", + "generated_at": report_time, + "git_commit": git_short_sha(root), + "source_inventory_schema_version": inventory.get("schema_version"), + "source_inventory_status": inventory.get("status"), + "source_owner_request_schema_version": request_draft_report.get("schema_version"), + "source_owner_request_status": request_draft_report.get("status"), + "status": "owner_response_acceptance_ledger_ready_no_runtime_action", + "summary": { + "source_owner_request_count": len(requests), + "acceptance_candidate_count": len(acceptance_candidates), + "write_capable_acceptance_candidate_count": len(write_capable), + "live_evidence_required_candidate_count": len(live_evidence), + "acceptance_field_count": len(ACCEPTANCE_FIELDS), + "required_owner_field_count": len(requests[0]["required_owner_fields"]) if requests else 0, + "reviewer_check_count": len(REVIEWER_CHECKS), + "outcome_lane_count": len(OUTCOME_LANES), + "blocked_action_count": len(BLOCKED_ACTIONS), + "request_sent_count": 0, + "recipient_confirmed_count": 0, + "owner_response_received_count": 0, + "owner_response_accepted_count": 0, + "owner_response_rejected_count": 0, + "owner_response_quarantined_count": 0, + "supplement_requested_count": 0, + "live_evidence_received_count": 0, + "live_config_hash_accepted_count": 0, + "maintenance_window_accepted_count": 0, + "restart_window_accepted_count": 0, + "rollback_owner_accepted_count": 0, + "post_check_plan_accepted_count": 0, + "disable_switch_accepted_count": 0, + "host_write_authorized_count": 0, + "ssh_read_authorized_count": 0, + "ssh_write_authorized_count": 0, + "docker_compose_action_authorized_count": 0, + "systemctl_action_authorized_count": 0, + "repair_bot_execution_authorized_count": 0, + "ansible_apply_authorized_count": 0, + "sudo_action_authorized_count": 0, + "secret_value_collection_allowed_count": 0, + "active_scan_authorized_count": 0, + "live_host_read_authorized_count": 0, + "runtime_gate_count": 0, + "action_button_count": 0, + }, + "execution_boundaries": { + "request_dispatch_authorized": False, + "owner_response_accepted": False, + "live_host_read_authorized": False, + "host_write_authorized": False, + "ssh_read_authorized": False, + "ssh_write_authorized": False, + "docker_compose_action_authorized": False, + "systemctl_action_authorized": False, + "repair_bot_execution_authorized": False, + "ansible_apply_authorized": False, + "sudo_action_authorized": False, + "secret_value_collection_allowed": False, + "active_scan_authorized": False, + "runtime_execution_authorized": False, + "action_buttons_allowed": False, + "not_authorization": True, + }, + "acceptance_fields": ACCEPTANCE_FIELDS, + "reviewer_checks": REVIEWER_CHECKS, + "outcome_lanes": OUTCOME_LANES, + "blocked_actions": BLOCKED_ACTIONS, + "acceptance_candidates": acceptance_candidates, + "next_steps": [ + "等待 owner 以脫敏 metadata ref 回覆 live config hash、maintenance / restart window、rollback owner、post-check plan 與 disable switch。", + "收到回覆後先做欄位完整性、敏感 payload 隔離與 execution request 拒收,不得直接 host read、restart、repair-bot 或 Ansible apply。", + "write-capable surface 必須額外 reviewer review,且 runtime gate 需獨立人工批准、rollback 與 post-check 成立。", + ], + } + + +def main() -> int: + parser = argparse.ArgumentParser(description="IwoooS host service owner response acceptance 產生器") + parser.add_argument("--root", default=".", help="repo root") + parser.add_argument( + "--inventory-report", + default="docs/security/host-service-config-inventory.snapshot.json", + help="host-service-config-inventory.py 輸出的 JSON", + ) + parser.add_argument( + "--owner-request-report", + default="docs/security/host-service-owner-request-draft.snapshot.json", + help="host-service-owner-request-draft.py 輸出的 JSON", + ) + parser.add_argument("--output", help="寫出 JSON 報告") + parser.add_argument("--generated-at", help="固定報告時間,供 committed snapshot 使用") + args = parser.parse_args() + + root = Path(args.root).resolve() + inventory = load_json(root / args.inventory_report) + request_draft_report = load_json(root / args.owner_request_report) + report = build_report(root, inventory, request_draft_report, 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( + "HOST_SERVICE_OWNER_RESPONSE_ACCEPTANCE_OK " + f"candidates={summary['acceptance_candidate_count']} " + f"write_capable={summary['write_capable_acceptance_candidate_count']} " + f"checks={summary['reviewer_check_count']} " + f"lanes={summary['outcome_lane_count']} " + f"accepted={summary['owner_response_accepted_count']} " + f"runtime_gate={summary['runtime_gate_count']}", + file=sys.stderr, + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/security/security-mirror-progress-guard.py b/scripts/security/security-mirror-progress-guard.py index 185487593..dedad27ad 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -101,6 +101,9 @@ def validate(root: Path) -> None: host_service_owner_request_draft = load_json( security_dir / "host-service-owner-request-draft.snapshot.json" ) + host_service_owner_response_acceptance = load_json( + security_dir / "host-service-owner-response-acceptance.snapshot.json" + ) ssh_network_access_inventory = load_json(security_dir / "ssh-network-access-inventory.snapshot.json") ssh_network_owner_request_draft = load_json( security_dir / "ssh-network-owner-request-draft.snapshot.json" @@ -2486,12 +2489,12 @@ def validate(root: Path) -> None: assert_equal( "high_value_config_coverage.summary.average_coverage_percent", high_value_config_coverage["summary"]["average_coverage_percent"], - 67, + 68, ) assert_equal( "high_value_config_coverage.summary.needs_live_evidence_count", high_value_config_coverage["summary"]["needs_live_evidence_count"], - 7, + 6, ) for key in [ "owner_response_received_count", @@ -2819,16 +2822,20 @@ def validate(root: Path) -> None: assert_equal( "high_value_config_coverage.coverage_categories.docker.coverage_percent", docker_systemd_category["coverage_percent"], - 50, + 54, ) assert_equal( "high_value_config_coverage.coverage_categories.docker.coverage_status", docker_systemd_category["coverage_status"], - "repo_only_inventory_ready_needs_live_owner_evidence", + "owner_response_acceptance_ledger_ready_needs_live_owner_evidence", ) for evidence_ref in [ "docs/security/HOST-SERVICE-CONFIG-INVENTORY.md", "docs/security/host-service-config-inventory.snapshot.json", + "docs/security/HOST-SERVICE-OWNER-REQUEST-DRAFT.md", + "docs/security/host-service-owner-request-draft.snapshot.json", + "docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md", + "docs/security/host-service-owner-response-acceptance.snapshot.json", ]: assert_contains( "high_value_config_coverage.coverage_categories.docker.evidence_refs", @@ -3123,6 +3130,221 @@ def validate(root: Path) -> None: f"host_service_owner_request_draft.{draft['request_id']}.{false_key}", draft[false_key], ) + assert_equal( + "host_service_owner_response_acceptance.schema", + host_service_owner_response_acceptance["schema_version"], + "host_service_owner_response_acceptance_v1", + ) + assert_equal( + "host_service_owner_response_acceptance.status", + host_service_owner_response_acceptance["status"], + "owner_response_acceptance_ledger_ready_no_runtime_action", + ) + assert_equal( + "host_service_owner_response_acceptance.source_inventory_schema_version", + host_service_owner_response_acceptance["source_inventory_schema_version"], + "host_service_config_inventory_v1", + ) + assert_equal( + "host_service_owner_response_acceptance.source_owner_request_schema_version", + host_service_owner_response_acceptance["source_owner_request_schema_version"], + "host_service_owner_request_draft_v1", + ) + expected_host_service_owner_response_acceptance_summary = { + "source_owner_request_count": 9, + "acceptance_candidate_count": 9, + "write_capable_acceptance_candidate_count": 3, + "live_evidence_required_candidate_count": 8, + "acceptance_field_count": 28, + "required_owner_field_count": 12, + "reviewer_check_count": 14, + "outcome_lane_count": 7, + "blocked_action_count": 20, + "request_sent_count": 0, + "recipient_confirmed_count": 0, + "owner_response_received_count": 0, + "owner_response_accepted_count": 0, + "owner_response_rejected_count": 0, + "owner_response_quarantined_count": 0, + "supplement_requested_count": 0, + "live_evidence_received_count": 0, + "live_config_hash_accepted_count": 0, + "maintenance_window_accepted_count": 0, + "restart_window_accepted_count": 0, + "rollback_owner_accepted_count": 0, + "post_check_plan_accepted_count": 0, + "disable_switch_accepted_count": 0, + "host_write_authorized_count": 0, + "ssh_read_authorized_count": 0, + "ssh_write_authorized_count": 0, + "docker_compose_action_authorized_count": 0, + "systemctl_action_authorized_count": 0, + "repair_bot_execution_authorized_count": 0, + "ansible_apply_authorized_count": 0, + "sudo_action_authorized_count": 0, + "secret_value_collection_allowed_count": 0, + "active_scan_authorized_count": 0, + "live_host_read_authorized_count": 0, + "runtime_gate_count": 0, + "action_button_count": 0, + } + for key, expected in expected_host_service_owner_response_acceptance_summary.items(): + assert_equal( + f"host_service_owner_response_acceptance.summary.{key}", + host_service_owner_response_acceptance["summary"][key], + expected, + ) + for key, value in host_service_owner_response_acceptance["execution_boundaries"].items(): + if key == "not_authorization": + assert_true(f"host_service_owner_response_acceptance.execution_boundaries.{key}", value) + else: + assert_false(f"host_service_owner_response_acceptance.execution_boundaries.{key}", value) + assert_equal( + "host_service_owner_response_acceptance.acceptance_candidates.count", + len(host_service_owner_response_acceptance["acceptance_candidates"]), + 9, + ) + expected_host_service_acceptance_ids = [ + "host_service_owner_response_acceptance:local_dev_compose", + "host_service_owner_response_acceptance:monitoring_110_compose", + "host_service_owner_response_acceptance:monitoring_exporters_188_compose", + "host_service_owner_response_acceptance:sentry_110_reference_compose", + "host_service_owner_response_acceptance:langfuse_110_compose", + "host_service_owner_response_acceptance:ansible_docker_compose_service_role", + "host_service_owner_response_acceptance:repair_bot_110_whitelist", + "host_service_owner_response_acceptance:repair_bot_188_whitelist", + "host_service_owner_response_acceptance:config_backup_host_capture", + ] + assert_equal( + "host_service_owner_response_acceptance.acceptance_candidates", + [item["acceptance_candidate_id"] for item in host_service_owner_response_acceptance["acceptance_candidates"]], + expected_host_service_acceptance_ids, + ) + expected_host_service_checks = [ + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_surface", + "redacted_refs_only", + "secret_value_absent", + "live_config_hash_metadata_only", + "maintenance_window_present", + "restart_window_separate_from_action", + "rollback_owner_present", + "post_check_plan_present", + "disable_switch_present", + "write_capable_requires_extra_review", + "no_runtime_request", + "counts_transition_safe", + ] + assert_equal( + "host_service_owner_response_acceptance.reviewer_checks", + [item["check_id"] for item in host_service_owner_response_acceptance["reviewer_checks"]], + expected_host_service_checks, + ) + expected_host_service_lanes = [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_supplement", + "ready_for_host_service_review", + "owner_review_only_update", + "waiting_runtime_gate", + ] + assert_equal( + "host_service_owner_response_acceptance.outcome_lanes", + [item["lane_id"] for item in host_service_owner_response_acceptance["outcome_lanes"]], + expected_host_service_lanes, + ) + expected_host_service_blocked_actions = [ + "ssh_read", + "ssh_write", + "docker_compose_up", + "docker_compose_down", + "docker_compose_pull", + "systemctl_restart", + "systemctl_reload", + "repair_bot_execute", + "ansible_apply", + "sudo_action", + "host_file_write", + "firewall_change", + "secret_value_collection", + "active_scan", + "live_host_read", + "raw_live_config_storage", + "restart_without_window", + "rollback_without_owner", + "runtime_gate_open", + "add_action_button", + ] + assert_equal( + "host_service_owner_response_acceptance.blocked_actions", + host_service_owner_response_acceptance["blocked_actions"], + expected_host_service_blocked_actions, + ) + for item in host_service_owner_response_acceptance["acceptance_candidates"]: + assert_equal( + f"host_service_owner_response_acceptance.{item['acceptance_candidate_id']}.acceptance_fields", + len(item["acceptance_fields"]), + 28, + ) + assert_equal( + f"host_service_owner_response_acceptance.{item['acceptance_candidate_id']}.required_owner_fields", + len(item["required_owner_fields"]), + 12, + ) + assert_equal( + f"host_service_owner_response_acceptance.{item['acceptance_candidate_id']}.reviewer_checks", + len(item["reviewer_checks"]), + 14, + ) + assert_equal( + f"host_service_owner_response_acceptance.{item['acceptance_candidate_id']}.outcome_lanes", + len(item["outcome_lanes"]), + 7, + ) + assert_equal( + f"host_service_owner_response_acceptance.{item['acceptance_candidate_id']}.blocked_actions", + len(item["blocked_actions"]), + 20, + ) + assert_true( + f"host_service_owner_response_acceptance.{item['acceptance_candidate_id']}.not_approval", + item["not_approval"], + ) + for false_key in [ + "request_sent", + "recipient_confirmed", + "owner_response_received", + "owner_response_accepted", + "owner_response_rejected", + "owner_response_quarantined", + "supplement_requested", + "live_evidence_received", + "live_config_hash_accepted", + "maintenance_window_accepted", + "restart_window_accepted", + "rollback_owner_accepted", + "post_check_plan_accepted", + "disable_switch_accepted", + "host_write_authorized", + "ssh_read_authorized", + "ssh_write_authorized", + "docker_compose_action_authorized", + "systemctl_action_authorized", + "repair_bot_execution_authorized", + "ansible_apply_authorized", + "sudo_action_authorized", + "secret_value_collection_allowed", + "active_scan_authorized", + "live_host_read_authorized", + "runtime_gate", + "action_buttons_allowed", + ]: + assert_false( + f"host_service_owner_response_acceptance.{item['acceptance_candidate_id']}.{false_key}", + item[false_key], + ) assert_equal( "ssh_network_access_inventory.schema", ssh_network_access_inventory["schema_version"], @@ -5077,8 +5299,8 @@ def validate(root: Path) -> None: "high_value_config_control_coverage_category_count": 14, "high_value_config_control_coverage_c0_category_count": 8, "high_value_config_control_coverage_c1_category_count": 4, - "high_value_config_control_coverage_average_percent": 67, - "high_value_config_control_coverage_needs_live_evidence_count": 7, + "high_value_config_control_coverage_average_percent": 68, + "high_value_config_control_coverage_needs_live_evidence_count": 6, "high_value_config_control_coverage_owner_response_required_count": 14, "high_value_config_control_coverage_owner_response_received_count": 0, "high_value_config_control_coverage_owner_response_accepted_count": 0, @@ -10996,6 +11218,8 @@ def validate(root: Path) -> None: "repo_owner_namespace_redacted=true", "raw_repository_namespace_visible=false", "public_api_raw_repo_namespace_allowed=false", + "public_product_identity_redacted=true", + "public_api_raw_project_slug_allowed=false", ]: assert_text_contains("platform_operator_service.source_namespace_redaction", platform_operator_service, text) for text in ["source_scope_id", "source_namespace_redacted"]: @@ -14689,7 +14913,7 @@ def validate(root: Path) -> None: assert_text_contains( "iwooos_page.high_value_config_control_coverage_docker_systemd_percent", iwooos_projection_page, - "{ key: 'dockerSystemd', rank: 'P1-1', value: '50%'", + "{ key: 'dockerSystemd', rank: 'P1-1', value: '54%'", ) assert_text_contains( "iwooos_page.high_value_config_control_coverage_ssh_network_percent", @@ -14718,8 +14942,8 @@ def validate(root: Path) -> None: "high_value_config_control_coverage_category_count=14", "high_value_config_control_coverage_c0_category_count=8", "high_value_config_control_coverage_c1_category_count=4", - "high_value_config_control_coverage_average_percent=67", - "high_value_config_control_coverage_needs_live_evidence_count=7", + "high_value_config_control_coverage_average_percent=68", + "high_value_config_control_coverage_needs_live_evidence_count=6", "high_value_config_control_coverage_owner_response_required_count=14", "high_value_config_control_coverage_owner_response_received_count=0", "high_value_config_control_coverage_owner_response_accepted_count=0", @@ -14733,6 +14957,10 @@ def validate(root: Path) -> None: "host_service_config_inventory_surface_count=9", "host_service_config_inventory_write_capable_surface_count=3", "host_service_config_inventory_runtime_gate_count=0", + "host_service_owner_response_acceptance_candidate_count=9", + "host_service_owner_response_acceptance_write_capable_candidate_count=3", + "host_service_owner_response_acceptance_reviewer_check_count=14", + "host_service_owner_response_acceptance_runtime_gate_count=0", "ssh_network_access_inventory_surface_count=16", "ssh_network_access_inventory_write_capable_surface_count=6", "ssh_network_access_inventory_runtime_gate_count=0",