diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 447d98ab9..012d3fe9d 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -5043,6 +5043,123 @@ "actions": "Actions:所有高風險動作仍需人工決策,頁面不提供執行按鈕。" } }, + "professionalSecurityExperience": { + "eyebrow": "專業資安視覺層", + "title": "用圖表看風險,不用先讀長文", + "subtitle": "對齊 XDR / CNAPP / Exposure Management 的主流做法:攻擊路徑、資產熱區與處置流程先視覺化,完整 evidence 再往下展開。", + "tabsLabel": "IwoooS 視覺模式", + "tabs": { + "attackPath": "攻擊路徑", + "assetHeat": "資產熱區", + "responseFlow": "處置流程" + }, + "attackPath": { + "chartLabel": "IwoooS 攻擊路徑與 Gate 邊界圖", + "nodes": { + "publicWeb": { + "title": "網站入口", + "state": "6/6" + }, + "apiRuntime": { + "title": "API / Runtime", + "state": "觀測" + }, + "projectSource": { + "title": "專案程式碼", + "state": "只讀" + }, + "devHosts": { + "title": "開發主機", + "state": "111 / 168" + }, + "kali112": { + "title": "Kali 112", + "state": "待維護" + }, + "runtimeGate": { + "title": "Gate 0", + "state": "鎖定" + } + }, + "signals": { + "scope": { + "label": "目前視野", + "value": "網站、主機、程式碼、版本來源已進入同一張圖", + "detail": "先把資產關係講清楚,再決定哪些地方需要加強。" + }, + "blastRadius": { + "label": "影響半徑", + "value": "Kali / 開發主機 / GitHub primary 都先以待驗收處理", + "detail": "避免一開始就把管控拉太高,讓產品流程卡死。" + }, + "gate": { + "label": "主動動作", + "value": "掃描、修復、更新、切主來源仍維持 Gate 0", + "detail": "頁面只呈現決策與 evidence,不提供直接執行按鈕。" + } + } + }, + "assetHeat": { + "cells": { + "awoooi": { + "title": "AwoooI 核心", + "caption": "正式路由與資安入口已接線" + }, + "awooop": { + "title": "AwoooP", + "caption": "工作項與 evidence 已連動" + }, + "iwooos": { + "title": "IwoooS", + "caption": "資安入口先視覺化" + }, + "websites": { + "title": "網站群", + "caption": "先只讀套用資安態勢" + }, + "kali112": { + "title": "Kali 112", + "caption": "維護窗口待人工確認" + }, + "devHosts": { + "title": "開發主機", + "caption": "111 / 168 納入範圍" + }, + "githubPrimary": { + "title": "GitHub Primary", + "caption": "待版本與負責人證據" + }, + "runtimeActions": { + "title": "Runtime 動作", + "caption": "仍需批准才開閘" + } + } + }, + "responseFlow": { + "steps": { + "observe": { + "title": "觀測", + "caption": "先建立資產與證據視野" + }, + "triage": { + "title": "分流", + "caption": "辨識高風險與可等待項目" + }, + "evidence": { + "title": "證據", + "caption": "建立可追溯快照與 owner 回覆" + }, + "approval": { + "title": "批准", + "caption": "需要人工確認才進入動作" + }, + "runtime": { + "title": "執行", + "caption": "掃描、更新、修復仍鎖住" + } + } + } + }, "informationArchitecture": { "overview": { "title": "一眼看懂", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 447d98ab9..012d3fe9d 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5043,6 +5043,123 @@ "actions": "Actions:所有高風險動作仍需人工決策,頁面不提供執行按鈕。" } }, + "professionalSecurityExperience": { + "eyebrow": "專業資安視覺層", + "title": "用圖表看風險,不用先讀長文", + "subtitle": "對齊 XDR / CNAPP / Exposure Management 的主流做法:攻擊路徑、資產熱區與處置流程先視覺化,完整 evidence 再往下展開。", + "tabsLabel": "IwoooS 視覺模式", + "tabs": { + "attackPath": "攻擊路徑", + "assetHeat": "資產熱區", + "responseFlow": "處置流程" + }, + "attackPath": { + "chartLabel": "IwoooS 攻擊路徑與 Gate 邊界圖", + "nodes": { + "publicWeb": { + "title": "網站入口", + "state": "6/6" + }, + "apiRuntime": { + "title": "API / Runtime", + "state": "觀測" + }, + "projectSource": { + "title": "專案程式碼", + "state": "只讀" + }, + "devHosts": { + "title": "開發主機", + "state": "111 / 168" + }, + "kali112": { + "title": "Kali 112", + "state": "待維護" + }, + "runtimeGate": { + "title": "Gate 0", + "state": "鎖定" + } + }, + "signals": { + "scope": { + "label": "目前視野", + "value": "網站、主機、程式碼、版本來源已進入同一張圖", + "detail": "先把資產關係講清楚,再決定哪些地方需要加強。" + }, + "blastRadius": { + "label": "影響半徑", + "value": "Kali / 開發主機 / GitHub primary 都先以待驗收處理", + "detail": "避免一開始就把管控拉太高,讓產品流程卡死。" + }, + "gate": { + "label": "主動動作", + "value": "掃描、修復、更新、切主來源仍維持 Gate 0", + "detail": "頁面只呈現決策與 evidence,不提供直接執行按鈕。" + } + } + }, + "assetHeat": { + "cells": { + "awoooi": { + "title": "AwoooI 核心", + "caption": "正式路由與資安入口已接線" + }, + "awooop": { + "title": "AwoooP", + "caption": "工作項與 evidence 已連動" + }, + "iwooos": { + "title": "IwoooS", + "caption": "資安入口先視覺化" + }, + "websites": { + "title": "網站群", + "caption": "先只讀套用資安態勢" + }, + "kali112": { + "title": "Kali 112", + "caption": "維護窗口待人工確認" + }, + "devHosts": { + "title": "開發主機", + "caption": "111 / 168 納入範圍" + }, + "githubPrimary": { + "title": "GitHub Primary", + "caption": "待版本與負責人證據" + }, + "runtimeActions": { + "title": "Runtime 動作", + "caption": "仍需批准才開閘" + } + } + }, + "responseFlow": { + "steps": { + "observe": { + "title": "觀測", + "caption": "先建立資產與證據視野" + }, + "triage": { + "title": "分流", + "caption": "辨識高風險與可等待項目" + }, + "evidence": { + "title": "證據", + "caption": "建立可追溯快照與 owner 回覆" + }, + "approval": { + "title": "批准", + "caption": "需要人工確認才進入動作" + }, + "runtime": { + "title": "執行", + "caption": "掃描、更新、修復仍鎖住" + } + } + } + }, "informationArchitecture": { "overview": { "title": "一眼看懂", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index fdc8293b9..bf5dc50e4 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -62,6 +62,34 @@ type VisualDashboardGate = { tone: 'steady' | 'warn' | 'locked' } +type VisualExperienceMode = 'attackPath' | 'assetHeat' | 'responseFlow' + +type VisualExperienceTab = { + key: VisualExperienceMode + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + +type AttackPathNode = { + key: string + x: number + y: number + tone: 'steady' | 'warn' | 'locked' +} + +type AssetHeatCell = { + key: string + level: string + tone: 'steady' | 'warn' | 'locked' +} + +type ResponseFlowStep = { + key: string + step: string + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + type ProgressAccelerationLane = { key: string lane: string @@ -693,6 +721,40 @@ const visualDashboardGates: VisualDashboardGate[] = [ { key: 'runtimeExecution', gate: 'Gate 0', value: '0', icon: Lock, tone: 'locked' }, ] +const visualExperienceTabs: VisualExperienceTab[] = [ + { key: 'attackPath', icon: Radar, tone: 'warn' }, + { key: 'assetHeat', icon: Activity, tone: 'steady' }, + { key: 'responseFlow', icon: ClipboardCheck, tone: 'locked' }, +] + +const attackPathNodes: AttackPathNode[] = [ + { key: 'publicWeb', x: 58, y: 54, tone: 'steady' }, + { key: 'apiRuntime', x: 206, y: 108, tone: 'warn' }, + { key: 'projectSource', x: 344, y: 64, tone: 'warn' }, + { key: 'devHosts', x: 474, y: 130, tone: 'warn' }, + { key: 'kali112', x: 360, y: 204, tone: 'warn' }, + { key: 'runtimeGate', x: 556, y: 214, tone: 'locked' }, +] + +const assetHeatCells: AssetHeatCell[] = [ + { key: 'awoooi', level: '低', tone: 'steady' }, + { key: 'awooop', level: '低', tone: 'steady' }, + { key: 'iwooos', level: '低', tone: 'steady' }, + { key: 'websites', level: '中', tone: 'warn' }, + { key: 'kali112', level: '中', tone: 'warn' }, + { key: 'devHosts', level: '中', tone: 'warn' }, + { key: 'githubPrimary', level: '待', tone: 'locked' }, + { key: 'runtimeActions', level: '鎖', tone: 'locked' }, +] + +const responseFlowSteps: ResponseFlowStep[] = [ + { key: 'observe', step: '01', icon: SearchCheck, tone: 'steady' }, + { key: 'triage', step: '02', icon: ListChecks, tone: 'warn' }, + { key: 'evidence', step: '03', icon: FileText, tone: 'warn' }, + { key: 'approval', step: '04', icon: ClipboardCheck, tone: 'warn' }, + { key: 'runtime', step: '05', icon: Lock, tone: 'locked' }, +] + const allProductCoverageSnapshotSummary = [ { key: 'scopeCount', value: '6', icon: ListChecks, tone: 'steady' }, { key: 'readOnlyScopes', value: '6', icon: ShieldCheck, tone: 'steady' }, @@ -3724,6 +3786,302 @@ function IwoooSVisualCommandDashboard() { ) } +function IwoooSProfessionalSecurityExperience() { + const t = useTranslations('iwooos.professionalSecurityExperience') + const [mode, setMode] = useState('attackPath') + const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } + + return ( +
+
+
+
+ + {t('eyebrow')} +
+

{t('title')}

+

+ {t('subtitle')} +

+
+ +
+ {visualExperienceTabs.map(tab => { + const Icon = tab.icon + const active = tab.key === mode + return ( + + ) + })} +
+
+ + {mode === 'attackPath' ? : null} + {mode === 'assetHeat' ? : null} + {mode === 'responseFlow' ? : null} +
+ ) +} + +function IwoooSAttackPathVisual() { + const t = useTranslations('iwooos.professionalSecurityExperience') + const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } + const edges = [ + ['publicWeb', 'apiRuntime'], + ['apiRuntime', 'projectSource'], + ['projectSource', 'devHosts'], + ['apiRuntime', 'kali112'], + ['devHosts', 'runtimeGate'], + ['kali112', 'runtimeGate'], + ] as const + + return ( +
+
+ + + + + + + {edges.map(([fromKey, toKey]) => { + const from = attackPathNodes.find(node => node.key === fromKey) + const to = attackPathNodes.find(node => node.key === toKey) + if (!from || !to) return null + return ( + + ) + })} + {attackPathNodes.map(node => ( + + + + + {t(`attackPath.nodes.${node.key}.title` as never)} + + + {t(`attackPath.nodes.${node.key}.state` as never)} + + + ))} + +
+ +
+ {['scope', 'blastRadius', 'gate'].map(key => ( +
+
{t(`attackPath.signals.${key}.label` as never)}
+
+ {t(`attackPath.signals.${key}.value` as never)} +
+
+ {t(`attackPath.signals.${key}.detail` as never)} +
+
+ ))} +
+
+ ) +} + +function IwoooSAssetHeatVisual() { + const t = useTranslations('iwooos.professionalSecurityExperience') + const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } + + return ( +
+
+ {assetHeatCells.map(cell => ( +
+
+ + {cell.level} +
+
+
+ {t(`assetHeat.cells.${cell.key}.title` as never)} +
+
+ {t(`assetHeat.cells.${cell.key}.caption` as never)} +
+
+
+ ))} +
+
+ ) +} + +function IwoooSResponseFlowVisual() { + const t = useTranslations('iwooos.professionalSecurityExperience') + const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } + + return ( +
+
+ {responseFlowSteps.map(item => { + const Icon = item.icon + return ( +
+
+ + {item.step} +
+
+
+ {t(`responseFlow.steps.${item.key}.title` as never)} +
+
+ {t(`responseFlow.steps.${item.key}.caption` as never)} +
+
+
+
+
+
+ ) + })} +
+
+ ) +} + function IwoooSFastProgressSummary() { const t = useTranslations('iwooos.fastProgress') return ( @@ -12555,6 +12913,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) { + SECURITY_MIRROR_PROGRESS_GUARD_OK +python3 scripts/security/source-control-owner-response-guard.py -> SOURCE_CONTROL_OWNER_RESPONSE_GUARD_OK +git diff --check -> pass +pnpm --dir apps/web run typecheck -> pass +NEXT_PUBLIC_API_URL=https://awoooi.wooo.work pnpm --dir apps/web run build -> pass + +local production browser smoke: + /zh-TW/iwooos desktop 1440: + professionalVisible=true + dashboardVisible=true + hasAttackPathSvg=true + selectedTab=攻擊路徑 + horizontalOverflow=false + /zh-TW/iwooos mobile 390: + professionalVisible=true + dashboardVisible=true + hasAttackPathSvg=true + selectedTab=攻擊路徑 + horizontalOverflow=false + screenshots: + /tmp/iwooos-professional-security-desktop-final-20260531.png + /tmp/iwooos-professional-security-mobile-final-20260531.png +``` + +**進度邊界**: + +- IwoooS 使用者可理解性與專業視覺化向前推進;整體資安 headline 仍維持 `61%`,因 runtime 執行沒有開閘。 +- Kali `192.168.0.112` 仍只讀納管;未執行 full-upgrade、autoremove、reboot、active scan 或 `/execute`。 +- 下一個讓 headline progress 真正上升的條件仍是 S4.9 owner response、redacted finding ingestion、GitHub primary evidence 或 active runtime gate。 + ## 2026-05-31|Web probe health endpoint 收斂 rollout Smoke 警告 **背景**: diff --git a/docs/security/iwooos-posture-projection.snapshot.json b/docs/security/iwooos-posture-projection.snapshot.json index 39bc0012d..69047bd84 100644 --- a/docs/security/iwooos-posture-projection.snapshot.json +++ b/docs/security/iwooos-posture-projection.snapshot.json @@ -34,6 +34,11 @@ "block_candidate_count": 1, "visual_command_dashboard_widget_count": 13, "visual_command_dashboard_first_layer": true, + "professional_security_experience_first_layer": true, + "professional_security_experience_tab_count": 3, + "professional_security_experience_attack_path_node_count": 6, + "professional_security_experience_asset_heat_cell_count": 8, + "professional_security_experience_response_flow_step_count": 5, "long_form_sections_default_collapsed": true, "owner_response_validation_received_count": 0, "owner_response_validation_accepted_count": 0, @@ -247,6 +252,7 @@ "display_security_posture", "display_progress_estimate", "display_visual_command_dashboard", + "display_professional_security_experience", "display_awooop_read_only_landing_readiness", "display_awooop_cross_session_handoff_packets", "display_progress_hold_movement_gates", diff --git a/scripts/security/security-mirror-progress-guard.py b/scripts/security/security-mirror-progress-guard.py index 90ca4967f..d7c356f19 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -268,6 +268,59 @@ def validate(root: Path) -> None: list(web_messages_zh["iwooos"]["visualCommandDashboard"]["gates"].keys()), key, ) + for text in [ + "IwoooSProfessionalSecurityExperience", + 'data-testid="iwooos-professional-security-experience"', + "IwoooSAttackPathVisual", + "IwoooSAssetHeatVisual", + "IwoooSResponseFlowVisual", + "visualExperienceTabs", + "attackPathNodes", + "assetHeatCells", + "responseFlowSteps", + "role=\"tab\"", + "aria-selected", + ]: + assert_text_contains("iwooos_projection_page.professional_security_experience", iwooos_projection_page, text) + for key in [ + "eyebrow", + "title", + "subtitle", + "tabsLabel", + "tabs", + "attackPath", + "assetHeat", + "responseFlow", + ]: + assert_contains( + "web_messages.zh-TW.iwooos.professionalSecurityExperience", + list(web_messages_zh["iwooos"]["professionalSecurityExperience"].keys()), + key, + ) + for key in ["attackPath", "assetHeat", "responseFlow"]: + assert_contains( + "web_messages.zh-TW.iwooos.professionalSecurityExperience.tabs", + list(web_messages_zh["iwooos"]["professionalSecurityExperience"]["tabs"].keys()), + key, + ) + for key in ["publicWeb", "apiRuntime", "projectSource", "devHosts", "kali112", "runtimeGate"]: + assert_contains( + "web_messages.zh-TW.iwooos.professionalSecurityExperience.attackPath.nodes", + list(web_messages_zh["iwooos"]["professionalSecurityExperience"]["attackPath"]["nodes"].keys()), + key, + ) + for key in ["awoooi", "awooop", "iwooos", "websites", "kali112", "devHosts", "githubPrimary", "runtimeActions"]: + assert_contains( + "web_messages.zh-TW.iwooos.professionalSecurityExperience.assetHeat.cells", + list(web_messages_zh["iwooos"]["professionalSecurityExperience"]["assetHeat"]["cells"].keys()), + key, + ) + for key in ["observe", "triage", "evidence", "approval", "runtime"]: + assert_contains( + "web_messages.zh-TW.iwooos.professionalSecurityExperience.responseFlow.steps", + list(web_messages_zh["iwooos"]["professionalSecurityExperience"]["responseFlow"]["steps"].keys()), + key, + ) for key in [ "eyebrow", "title", @@ -1700,6 +1753,30 @@ def validate(root: Path) -> None: "iwooos_projection.summary.visual_command_dashboard_first_layer", iwooos_projection["summary"]["visual_command_dashboard_first_layer"], ) + assert_true( + "iwooos_projection.summary.professional_security_experience_first_layer", + iwooos_projection["summary"]["professional_security_experience_first_layer"], + ) + assert_equal( + "iwooos_projection.summary.professional_security_experience_tab_count", + iwooos_projection["summary"]["professional_security_experience_tab_count"], + 3, + ) + assert_equal( + "iwooos_projection.summary.professional_security_experience_attack_path_node_count", + iwooos_projection["summary"]["professional_security_experience_attack_path_node_count"], + 6, + ) + assert_equal( + "iwooos_projection.summary.professional_security_experience_asset_heat_cell_count", + iwooos_projection["summary"]["professional_security_experience_asset_heat_cell_count"], + 8, + ) + assert_equal( + "iwooos_projection.summary.professional_security_experience_response_flow_step_count", + iwooos_projection["summary"]["professional_security_experience_response_flow_step_count"], + 5, + ) assert_true( "iwooos_projection.summary.long_form_sections_default_collapsed", iwooos_projection["summary"]["long_form_sections_default_collapsed"], @@ -5767,6 +5844,7 @@ def validate(root: Path) -> None: "display_security_posture", "display_progress_estimate", "display_visual_command_dashboard", + "display_professional_security_experience", "display_awooop_read_only_landing_readiness", "display_awooop_cross_session_handoff_packets", "display_progress_hold_movement_gates",