From 3c7a469ae4441d52ade91d8cc5c3df14b77a6cbf Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 31 May 2026 22:38:54 +0800 Subject: [PATCH] feat(web): add IwoooS host tool evidence chain --- apps/web/messages/en.json | 88 ++++++ apps/web/messages/zh-TW.json | 88 ++++++ apps/web/src/app/[locale]/iwooos/page.tsx | 255 ++++++++++++++++++ docs/LOGBOOK.md | 26 ++ .../iwooos-posture-projection.snapshot.json | 5 + .../security-mirror-progress-guard.py | 123 +++++++++ 6 files changed, 585 insertions(+) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 01446622d..ccbe652f9 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -5636,6 +5636,94 @@ } } }, + "hostToolEvidenceChain": { + "eyebrow": "主機與工具證據鏈", + "title": "Kali、開發主機、監控工具一次看清楚", + "subtitle": "把 Kali 192.168.0.112、開發主機 192.168.0.111 / 192.168.0.168、Sentry / SigNoz、MCP、自建 MCP、Ansible、KM、GitHub / Gitea 放在同一條只讀證據鏈。這一層只回答「有沒有納入、證據在哪、下一關是誰」,不執行掃描或主機變更。", + "flowTitle": "證據鏈順序", + "evidenceLabel": "證據", + "nextLabel": "下一步", + "boundaryTitle": "主機與工具邊界", + "boundaryIntro": "以下鍵值固定:這張卡只呈現主機與工具證據鏈,不呼叫 Kali /execute、不 SSH 變更主機、不跑 active scan、不同步 refs、不修改 workflow 或 secret。", + "summary": { + "hosts": { + "label": "主機", + "detail": "112、111、168 三台進入視野。" + }, + "toolLanes": { + "label": "工具線", + "detail": "監控、MCP、Ansible、KM、版本來源與自動化證據。" + }, + "linkedEvidence": { + "label": "證據鏈", + "detail": "已和 Incident / Monitoring 可視化串接。" + }, + "runtime": { + "label": "執行期", + "detail": "仍是 0,維持只讀。" + } + }, + "steps": { + "observe": { + "title": "只讀觀測", + "detail": "先看狀態與範圍。" + }, + "evidence": { + "title": "脫敏收證", + "detail": "只收 metadata 與截圖。" + }, + "review": { + "title": "人工審核", + "detail": "確認 owner 與風險。" + }, + "ownerGate": { + "title": "負責人閘門", + "detail": "沒有回覆就不開下一步。" + }, + "runtimeHold": { + "title": "執行鎖定", + "detail": "掃描、更新、修復皆關閉。" + } + }, + "items": { + "kali112": { + "title": "Kali 192.168.0.112", + "scope": "安全主機已納入 IwoooS;目前只呈現快照、維護窗口與待處理項。", + "evidence": "read-only snapshot、1994 package queue、1 個 systemd 異常、/execute=false。", + "next": "等維護窗口與人工授權,不更新、不重啟、不掃描。" + }, + "dev111": { + "title": "開發主機 192.168.0.111", + "scope": "本機備援、開發服務與 AI fallback 相關資產先納入觀測。", + "evidence": "目前只收主機角色、服務範圍、owner decision 與脫敏證據。", + "next": "補 owner evidence;不做 SSH 變更或服務調校。" + }, + "dev168": { + "title": "開發主機 192.168.0.168", + "scope": "開發主機與專案工作區納入資安範圍。", + "evidence": "目前只收主機中繼資料、專案對應與人工判定。", + "next": "補主機證據與責任邊界;不開自動修復。" + }, + "monitoringTools": { + "title": "Sentry / SigNoz / Monitoring", + "scope": "監控頁已接上 Incident 證據鏈,能看 provider heartbeat 與 timeline。", + "evidence": "Monitoring focus、source refs、timeline、provider correlation 已前台化。", + "next": "持續只讀顯示,不靜音告警、不標記 provider 已匹配。" + }, + "automationTools": { + "title": "MCP / 自建 MCP / Ansible / KM", + "scope": "工具鏈納入同一條 Incident 與 AI 自動化證據視野。", + "evidence": "MCP 調查、Ansible check-mode、PlayBook、KM freshness 以 metadata 呈現。", + "next": "先補可追蹤 evidence;不直接 apply、不寫 KM。" + }, + "sourceControl": { + "title": "GitHub / Gitea 版本來源", + "scope": "版本來源與 workflow / secret 名稱只做治理可視化。", + "evidence": "refs truth、workflow 名稱、secret 名稱與 S4.9 owner response 等待中。", + "next": "等負責人回覆;不建立 repo、不同步 refs、不切 GitHub primary。" + } + } + }, "vibeWorkSecurityOnboarding": { "eyebrow": "VibeWork 新專案收件卡", "title": "先把缺口收齊,不急著加管制", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 01446622d..ccbe652f9 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5636,6 +5636,94 @@ } } }, + "hostToolEvidenceChain": { + "eyebrow": "主機與工具證據鏈", + "title": "Kali、開發主機、監控工具一次看清楚", + "subtitle": "把 Kali 192.168.0.112、開發主機 192.168.0.111 / 192.168.0.168、Sentry / SigNoz、MCP、自建 MCP、Ansible、KM、GitHub / Gitea 放在同一條只讀證據鏈。這一層只回答「有沒有納入、證據在哪、下一關是誰」,不執行掃描或主機變更。", + "flowTitle": "證據鏈順序", + "evidenceLabel": "證據", + "nextLabel": "下一步", + "boundaryTitle": "主機與工具邊界", + "boundaryIntro": "以下鍵值固定:這張卡只呈現主機與工具證據鏈,不呼叫 Kali /execute、不 SSH 變更主機、不跑 active scan、不同步 refs、不修改 workflow 或 secret。", + "summary": { + "hosts": { + "label": "主機", + "detail": "112、111、168 三台進入視野。" + }, + "toolLanes": { + "label": "工具線", + "detail": "監控、MCP、Ansible、KM、版本來源與自動化證據。" + }, + "linkedEvidence": { + "label": "證據鏈", + "detail": "已和 Incident / Monitoring 可視化串接。" + }, + "runtime": { + "label": "執行期", + "detail": "仍是 0,維持只讀。" + } + }, + "steps": { + "observe": { + "title": "只讀觀測", + "detail": "先看狀態與範圍。" + }, + "evidence": { + "title": "脫敏收證", + "detail": "只收 metadata 與截圖。" + }, + "review": { + "title": "人工審核", + "detail": "確認 owner 與風險。" + }, + "ownerGate": { + "title": "負責人閘門", + "detail": "沒有回覆就不開下一步。" + }, + "runtimeHold": { + "title": "執行鎖定", + "detail": "掃描、更新、修復皆關閉。" + } + }, + "items": { + "kali112": { + "title": "Kali 192.168.0.112", + "scope": "安全主機已納入 IwoooS;目前只呈現快照、維護窗口與待處理項。", + "evidence": "read-only snapshot、1994 package queue、1 個 systemd 異常、/execute=false。", + "next": "等維護窗口與人工授權,不更新、不重啟、不掃描。" + }, + "dev111": { + "title": "開發主機 192.168.0.111", + "scope": "本機備援、開發服務與 AI fallback 相關資產先納入觀測。", + "evidence": "目前只收主機角色、服務範圍、owner decision 與脫敏證據。", + "next": "補 owner evidence;不做 SSH 變更或服務調校。" + }, + "dev168": { + "title": "開發主機 192.168.0.168", + "scope": "開發主機與專案工作區納入資安範圍。", + "evidence": "目前只收主機中繼資料、專案對應與人工判定。", + "next": "補主機證據與責任邊界;不開自動修復。" + }, + "monitoringTools": { + "title": "Sentry / SigNoz / Monitoring", + "scope": "監控頁已接上 Incident 證據鏈,能看 provider heartbeat 與 timeline。", + "evidence": "Monitoring focus、source refs、timeline、provider correlation 已前台化。", + "next": "持續只讀顯示,不靜音告警、不標記 provider 已匹配。" + }, + "automationTools": { + "title": "MCP / 自建 MCP / Ansible / KM", + "scope": "工具鏈納入同一條 Incident 與 AI 自動化證據視野。", + "evidence": "MCP 調查、Ansible check-mode、PlayBook、KM freshness 以 metadata 呈現。", + "next": "先補可追蹤 evidence;不直接 apply、不寫 KM。" + }, + "sourceControl": { + "title": "GitHub / Gitea 版本來源", + "scope": "版本來源與 workflow / secret 名稱只做治理可視化。", + "evidence": "refs truth、workflow 名稱、secret 名稱與 S4.9 owner response 等待中。", + "next": "等負責人回覆;不建立 repo、不同步 refs、不切 GitHub primary。" + } + } + }, "vibeWorkSecurityOnboarding": { "eyebrow": "VibeWork 新專案收件卡", "title": "先把缺口收齊,不急著加管制", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index ce90366da..56052365a 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -143,6 +143,20 @@ type GlobalSecurityMeshMatrixItem = { runtimeTone: 'steady' | 'warn' | 'locked' } +type HostToolEvidenceChainItem = { + key: string + lane: string + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + +type HostToolEvidenceChainStep = { + key: string + step: string + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + type VibeWorkSecurityOnboardingItem = { key: string check: string @@ -1399,6 +1413,48 @@ const globalSecurityMeshMatrixBoundaries = [ 'not_authorization=true', ] as const +const hostToolEvidenceChainSummary = [ + { key: 'hosts', value: '3', icon: Activity, tone: 'warn' }, + { key: 'toolLanes', value: '6', icon: Radar, tone: 'steady' }, + { key: 'linkedEvidence', value: '已接線', icon: GitBranch, tone: 'steady' }, + { key: 'runtime', value: '0', icon: Lock, tone: 'locked' }, +] as const + +const hostToolEvidenceChainItems: HostToolEvidenceChainItem[] = [ + { key: 'kali112', lane: 'H1', icon: SearchCheck, tone: 'warn' }, + { key: 'dev111', lane: 'H2', icon: Activity, tone: 'warn' }, + { key: 'dev168', lane: 'H3', icon: Activity, tone: 'warn' }, + { key: 'monitoringTools', lane: 'T1', icon: Radar, tone: 'steady' }, + { key: 'automationTools', lane: 'T2', icon: ListChecks, tone: 'steady' }, + { key: 'sourceControl', lane: 'T3', icon: GitBranch, tone: 'warn' }, +] + +const hostToolEvidenceChainSteps: HostToolEvidenceChainStep[] = [ + { key: 'observe', step: '01', icon: SearchCheck, tone: 'steady' }, + { key: 'evidence', step: '02', icon: FileText, tone: 'warn' }, + { key: 'review', step: '03', icon: ClipboardCheck, tone: 'warn' }, + { key: 'ownerGate', step: '04', icon: Lock, tone: 'locked' }, + { key: 'runtimeHold', step: '05', icon: AlertTriangle, tone: 'locked' }, +] + +const hostToolEvidenceChainBoundaries = [ + 'iwooos_host_tool_evidence_chain_first_layer=true', + 'iwooos_host_tool_evidence_chain_host_count=3', + 'iwooos_host_tool_evidence_chain_tool_lane_count=6', + 'iwooos_host_tool_evidence_chain_step_count=5', + 'kali_112_integrated_as_read_only=true', + 'dev_hosts_111_168_integrated_as_read_only=true', + 'monitoring_tools_evidence_chain_linked=true', + 'kali_112_execute_authorized=false', + 'host_update_authorized=false', + 'active_scan_authorized=false', + 'ssh_mutation_authorized=false', + 'runtime_execution_authorized=false', + 'active_runtime_gate_count=0', + 'action_buttons_allowed=false', + 'not_authorization=true', +] as const + const vibeWorkSecurityOnboardingSummary = [ { key: 'readOnly', value: '已納管', icon: ShieldCheck, tone: 'steady' }, { key: 'missingEvidence', value: '6', icon: ListChecks, tone: 'warn' }, @@ -4782,6 +4838,204 @@ function IwoooSGlobalSecurityMeshMatrixBoard() { ) } +function IwoooSHostToolEvidenceChainBoard() { + const t = useTranslations('iwooos.hostToolEvidenceChain') + const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } + + return ( +
+
+
+
+
+ + {t('eyebrow')} +
+

{t('title')}

+

+ {t('subtitle')} +

+ +
+ {hostToolEvidenceChainSummary.map(item => { + const Icon = item.icon + return ( +
+
+ {t(`summary.${item.key}.label` as never)} + +
+
+ {item.value} +
+

+ {t(`summary.${item.key}.detail` as never)} +

+
+ ) + })} +
+
+ +
+

{t('flowTitle')}

+
+ {hostToolEvidenceChainSteps.map(item => { + const Icon = item.icon + return ( +
+
+ {item.step} + +
+
+ {t(`steps.${item.key}.title` as never)} +
+

+ {t(`steps.${item.key}.detail` as never)} +

+
+ ) + })} +
+
+
+ +
+ {hostToolEvidenceChainItems.map(item => { + const Icon = item.icon + return ( +
+
+
+
{item.lane}
+

+ {t(`items.${item.key}.title` as never)} +

+
+ +
+

+ {t(`items.${item.key}.scope` as never)} +

+
+
+ {t('evidenceLabel')} + + {t(`items.${item.key}.evidence` as never)} + +
+
+ {t('nextLabel')} + + {t(`items.${item.key}.next` as never)} + +
+
+
+ ) + })} +
+ +
+ + {t('boundaryTitle')} + +

+ {t('boundaryIntro')} +

+
+ {hostToolEvidenceChainBoundaries.map(item => ( + + {item} + + ))} +
+
+
+
+ ) +} + function IwoooSVibeWorkSecurityOnboardingBoard() { const t = useTranslations('iwooos.vibeWorkSecurityOnboarding') const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } @@ -13435,6 +13689,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) { + None: iwooos_projection["summary"]["global_security_mesh_matrix_runtime_gate_count"], 0, ) + assert_true( + "iwooos_projection.summary.host_tool_evidence_chain_first_layer", + iwooos_projection["summary"]["host_tool_evidence_chain_first_layer"], + ) + assert_equal( + "iwooos_projection.summary.host_tool_evidence_chain_host_count", + iwooos_projection["summary"]["host_tool_evidence_chain_host_count"], + 3, + ) + assert_equal( + "iwooos_projection.summary.host_tool_evidence_chain_tool_lane_count", + iwooos_projection["summary"]["host_tool_evidence_chain_tool_lane_count"], + 6, + ) + assert_equal( + "iwooos_projection.summary.host_tool_evidence_chain_step_count", + iwooos_projection["summary"]["host_tool_evidence_chain_step_count"], + 5, + ) assert_true( "iwooos_projection.summary.vibework_security_onboarding_first_layer", iwooos_projection["summary"]["vibework_security_onboarding_first_layer"], @@ -5919,6 +5938,7 @@ def validate(root: Path) -> None: "display_professional_security_experience", "display_concrete_work_snapshot", "display_global_security_mesh_matrix", + "display_host_tool_evidence_chain", "display_awooop_read_only_landing_readiness", "display_awooop_cross_session_handoff_packets", "display_progress_hold_movement_gates", @@ -9754,6 +9774,109 @@ def validate(root: Path) -> None: list(web_messages_en["iwooos"]["globalSecurityMeshMatrix"]["items"].keys()), key, ) + assert_text_contains( + "iwooos_page.host_tool_evidence_chain_testid", + iwooos_projection_page, + 'data-testid="iwooos-host-tool-evidence-chain-board"', + ) + assert_text_contains( + "iwooos_page.host_tool_evidence_chain_boundary_testid", + iwooos_projection_page, + 'data-testid="iwooos-host-tool-evidence-chain-boundaries"', + ) + assert_text_contains( + "iwooos_page.host_tool_evidence_chain_component", + iwooos_projection_page, + "IwoooSHostToolEvidenceChainBoard", + ) + for text in [ + "iwooos_host_tool_evidence_chain_first_layer=true", + "iwooos_host_tool_evidence_chain_host_count=3", + "iwooos_host_tool_evidence_chain_tool_lane_count=6", + "iwooos_host_tool_evidence_chain_step_count=5", + "kali_112_integrated_as_read_only=true", + "dev_hosts_111_168_integrated_as_read_only=true", + "monitoring_tools_evidence_chain_linked=true", + "kali_112_execute_authorized=false", + "host_update_authorized=false", + "active_scan_authorized=false", + "ssh_mutation_authorized=false", + "runtime_execution_authorized=false", + "active_runtime_gate_count=0", + "action_buttons_allowed=false", + "not_authorization=true", + ]: + assert_text_contains( + "iwooos_page.host_tool_evidence_chain_boundary", + iwooos_projection_page, + text, + ) + assert_contains( + "web_messages.zh-TW.iwooos.hostToolEvidenceChain", + list(web_messages_zh["iwooos"].keys()), + "hostToolEvidenceChain", + ) + assert_contains( + "web_messages.en.iwooos.hostToolEvidenceChain", + list(web_messages_en["iwooos"].keys()), + "hostToolEvidenceChain", + ) + for key in [ + "eyebrow", + "title", + "subtitle", + "flowTitle", + "evidenceLabel", + "nextLabel", + "boundaryTitle", + "boundaryIntro", + "summary", + "steps", + "items", + ]: + assert_contains( + "web_messages.zh-TW.iwooos.hostToolEvidenceChain.keys", + list(web_messages_zh["iwooos"]["hostToolEvidenceChain"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.hostToolEvidenceChain.keys", + list(web_messages_en["iwooos"]["hostToolEvidenceChain"].keys()), + key, + ) + for key in ["hosts", "toolLanes", "linkedEvidence", "runtime"]: + assert_contains( + "web_messages.zh-TW.iwooos.hostToolEvidenceChain.summary", + list(web_messages_zh["iwooos"]["hostToolEvidenceChain"]["summary"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.hostToolEvidenceChain.summary", + list(web_messages_en["iwooos"]["hostToolEvidenceChain"]["summary"].keys()), + key, + ) + for key in ["observe", "evidence", "review", "ownerGate", "runtimeHold"]: + assert_contains( + "web_messages.zh-TW.iwooos.hostToolEvidenceChain.steps", + list(web_messages_zh["iwooos"]["hostToolEvidenceChain"]["steps"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.hostToolEvidenceChain.steps", + list(web_messages_en["iwooos"]["hostToolEvidenceChain"]["steps"].keys()), + key, + ) + for key in ["kali112", "dev111", "dev168", "monitoringTools", "automationTools", "sourceControl"]: + assert_contains( + "web_messages.zh-TW.iwooos.hostToolEvidenceChain.items", + list(web_messages_zh["iwooos"]["hostToolEvidenceChain"]["items"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.hostToolEvidenceChain.items", + list(web_messages_en["iwooos"]["hostToolEvidenceChain"]["items"].keys()), + key, + ) assert_text_contains( "iwooos_page.vibework_security_onboarding_testid", iwooos_projection_page,