diff --git a/apps/api/src/services/public_redaction.py b/apps/api/src/services/public_redaction.py index e05cc25c..69b20d9c 100644 --- a/apps/api/src/services/public_redaction.py +++ b/apps/api/src/services/public_redaction.py @@ -40,10 +40,35 @@ _PRIVATE_LAN_RE = re.compile(r"192\.168\.0\.\d{1,3}(?::\d{1,5})?") _WORK_CONTEXT_REPLACEMENTS = { "工作視窗": "內部協作環境", + "對話內容": "內部協作內容", "批准!繼續": "內部短訊指令", "批准!": "內部短訊指令", + "In app browser": "內部瀏覽器狀態", + "My request for Codex": "內部協作請求", + "browser_context": "redacted_browser_context", + "codex_user_message": "redacted_user_message", + "prompt_text": "redacted_prompt_text", "source_thread_id": "redacted_thread_id", "codex_delegation": "redacted_delegation", + "raw prompt": "未脫敏提示內容", + "raw_prompt": "redacted_prompt", + "private reasoning": "私有推理內容", + "private_reasoning": "redacted_private_reasoning", + "chain of thought": "推理鏈內容", + "chain_of_thought": "redacted_chain_of_thought", + "raw payload": "原始載荷", + "raw_payload": "redacted_payload", + "raw Telegram payload": "原始 Telegram 載荷", + "raw_telegram_payload": "redacted_telegram_payload", + "raw tool output": "原始工具輸出", + "raw_tool_output": "redacted_tool_output", + "authorization header": "授權標頭", + "authorization_header": "redacted_authorization_header", + "secret value": "機密明文", + "secret_value": "redacted_secret_value", + "work window transcript": "內部協作逐字稿", + "work_window_transcript": "redacted_work_window_transcript", + "internal collaboration transcript": "內部協作逐字稿", } diff --git a/apps/api/tests/test_public_redaction.py b/apps/api/tests/test_public_redaction.py index 6aedb0a4..caf5e5c0 100644 --- a/apps/api/tests/test_public_redaction.py +++ b/apps/api/tests/test_public_redaction.py @@ -46,6 +46,23 @@ def test_redact_public_lan_text_replaces_internal_work_context_terms() -> None: assert "內部協作環境" in redacted +def test_redact_public_lan_text_replaces_sensitive_evidence_terms() -> None: + redacted = redact_public_lan_text( + "raw payload / private reasoning / authorization header / secret value / " + "raw tool output / work window transcript" + ) + + assert "raw payload" not in redacted + assert "private reasoning" not in redacted + assert "authorization header" not in redacted + assert "secret value" not in redacted + assert "raw tool output" not in redacted + assert "work window transcript" not in redacted + assert "原始載荷" in redacted + assert "授權標頭" in redacted + assert "機密明文" in redacted + + def test_public_monitoring_tool_payload_drops_internal_probe_url() -> None: payload = public_monitoring_tool_payload( { diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index b8fd9219..96fc9e11 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -4243,7 +4243,7 @@ "verifier": "verifier live: {value}", "send": "send: {value}", "directApi": "direct API: {value}", - "secret": "secret value: {value}" + "secret": "機密明文: {value}" }, "labels": { "owner": "owner: {value}", @@ -4294,7 +4294,7 @@ "queueWrite": "queue write: {value}", "send": "send: {value}", "directApi": "direct API: {value}", - "secret": "secret value: {value}", + "secret": "機密明文: {value}", "verifier": "verifier live: {value}", "resultWrite": "result write: {value}" }, @@ -4350,7 +4350,7 @@ "queueWrite": "queue write: {value}", "send": "send: {value}", "productionWrite": "prod write: {value}", - "secret": "secret value: {value}", + "secret": "機密明文: {value}", "resultWrite": "result write: {value}", "verifier": "verifier live: {value}" }, @@ -4405,7 +4405,7 @@ "queueWrite": "queue write: {value}", "send": "send: {value}", "productionWrite": "prod write: {value}", - "secret": "secret value: {value}", + "secret": "機密明文: {value}", "destructive": "destructive: {value}", "liveExecution": "live execution: {value}", "opensLive": "opens live: {value}", @@ -4468,7 +4468,7 @@ "queueWrite": "queue write: {value}", "send": "send: {value}", "productionWrite": "prod write: {value}", - "secret": "secret value: {value}", + "secret": "機密明文: {value}", "destructive": "destructive: {value}", "liveReadback": "live readback: {value}", "resultWrite": "result write: {value}", @@ -15652,7 +15652,7 @@ "dataClass": { "title": "任務、agent 與 webhook 資料分級", "missing": "尚未標示 task、solution、agent reputation、traffic、webhook、admin 與 settlement 的資料分級。", - "next": "只收欄位類型與脫敏摘要,不收 raw payload、未脫敏互動內容、cookie 或 token。" + "next": "只收欄位類型與脫敏摘要,不收原始載荷、未脫敏互動內容、cookie 或 token。" }, "sourceRepo": { "title": "版本來源與 dirty workspace 判定", diff --git a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx index 1e1c33d2..ea8b65a1 100644 --- a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx +++ b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx @@ -112,12 +112,43 @@ const PUBLIC_TEXT_HOST_ALIASES: Record = { const PRIVATE_LAN_PREFIX_PATTERN = ['192', '168', '0'].join('\\.') const PRIVATE_LAN_TEXT_PATTERN = new RegExp(`(?:https?:\\/\\/)?${PRIVATE_LAN_PREFIX_PATTERN}\\.(\\d{1,3})(?::(\\d{1,5}))?`, 'g') +const PUBLIC_TEXT_REPLACEMENTS: Array<[RegExp, string]> = [ + [/工作視窗/g, '內部協作環境'], + [/對話內容/g, '內部協作內容'], + [/批准!繼續/g, '內部短訊指令'], + [/批准!/g, '內部短訊指令'], + [/In app browser/gi, '內部瀏覽器狀態'], + [/My request for Codex/gi, '內部協作請求'], + [/browser_context/gi, 'redacted_browser_context'], + [/codex_user_message/gi, 'redacted_user_message'], + [/prompt_text/gi, 'redacted_prompt_text'], + [/raw prompt/gi, '未脫敏提示內容'], + [/raw_prompt/gi, 'redacted_prompt'], + [/private reasoning/gi, '私有推理內容'], + [/private_reasoning/gi, 'redacted_private_reasoning'], + [/chain of thought/gi, '推理鏈內容'], + [/chain_of_thought/gi, 'redacted_chain_of_thought'], + [/raw Telegram payload/gi, '原始 Telegram 載荷'], + [/raw_telegram_payload/gi, 'redacted_telegram_payload'], + [/raw tool output/gi, '原始工具輸出'], + [/raw_tool_output/gi, 'redacted_tool_output'], + [/raw payload/gi, '原始載荷'], + [/raw_payload/gi, 'redacted_payload'], + [/authorization header/gi, '授權標頭'], + [/authorization_header/gi, 'redacted_authorization_header'], + [/secret value/gi, '機密明文'], + [/secret_value/gi, 'redacted_secret_value'], + [/work window transcript/gi, '內部協作逐字稿'], + [/work_window_transcript/gi, 'redacted_work_window_transcript'], + [/internal collaboration transcript/gi, '內部協作逐字稿'], +] function redactPublicText(value: string): string { - return value.replace(PRIVATE_LAN_TEXT_PATTERN, (_match, octet: string, port: string | undefined) => { + const redactedLan = value.replace(PRIVATE_LAN_TEXT_PATTERN, (_match, octet: string, port: string | undefined) => { if (port) return PUBLIC_TEXT_ENDPOINT_ALIASES[`${octet}:${port}`] ?? PUBLIC_TEXT_HOST_ALIASES[octet] ?? 'host:internal-node' return PUBLIC_TEXT_HOST_ALIASES[octet] ?? 'host:internal-node' }) + return PUBLIC_TEXT_REPLACEMENTS.reduce((text, [pattern, replacement]) => text.replace(pattern, replacement), redactedLan) } function toneColor(tone: 'ok' | 'warn' | 'danger' | 'neutral') { @@ -3871,165 +3902,6 @@ export function AutomationInventoryTab() { -
-
-
- - - {t('criticReviewerResultCapture.title')} - -
- -
- -
- } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> -
- -
-
- {t('criticReviewerResultCapture.truthTitle')} - - {criticReviewerResultCapture.score_truth.truth_note} - -
- - - - -
-
- -
- {t('criticReviewerResultCapture.boundaryTitle')} - - {t('criticReviewerResultCapture.boundarySummary', { - critic: criticReviewerRuntimeCriticScores, - reviewer: criticReviewerRuntimeReviewerScores, - capture: criticReviewerRuntimeCaptureWrites, - learning: criticReviewerLearningWrites, - trust: criticReviewerTrustWrites, - send: criticReviewerTelegramSends, - })} - -
- - - - -
-
-
- -
- {visibleCriticReviewerScorecards.map(scorecard => ( -
-
- - {scorecard.display_name} - - -
-
- - - - - -
- - {scorecard.failure_if_missing} - -
- ))} -
- -
- {visibleResultCaptureContracts.map(contract => ( -
-
- - {contract.display_name} - - -
-
- - - - -
- - {contract.blocker_summary} - -
- ))} -
- -
- {visibleCriticReviewerPromotionGates.map(gate => ( -
-
- - {gate.display_name} - - -
- - {gate.required_before} - - - {gate.failure_if_missing} - - -
- ))} -
- -
- {visibleCriticReviewerCandidateRoutes.map(route => ( -
-
- - {route.display_name} - - -
-
- - - - -
- - {route.next_gate} - -
- ))} -
-
-
@@ -7071,6 +6943,19 @@ export function AutomationInventoryTab() { grid-template-columns: 1fr !important; } + .automation-inventory-tab-root { + padding: 12px !important; + max-width: 100%; + overflow-x: hidden; + } + + .automation-inventory-tab-root button, + .automation-inventory-tab-root [role='button'], + .automation-inventory-tab-root span, + .automation-inventory-tab-root div { + max-width: 100%; + } + .automation-inventory-live-read-card-grid > *, .automation-inventory-live-read-kpi-grid > *, .automation-inventory-service-health-notification-rule-grid > *, diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index 6ecf2126..a283ccee 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -22,6 +22,51 @@ const getApiBaseUrl = (): string => { } const API_BASE_URL = getApiBaseUrl() +const PUBLIC_TEXT_REPLACEMENTS: Array<[RegExp, string]> = [ + [/工作視窗/g, '內部協作環境'], + [/對話內容/g, '內部協作內容'], + [/批准!繼續/g, '內部短訊指令'], + [/批准!/g, '內部短訊指令'], + [/In app browser/gi, '內部瀏覽器狀態'], + [/My request for Codex/gi, '內部協作請求'], + [/browser_context/gi, 'redacted_browser_context'], + [/codex_user_message/gi, 'redacted_user_message'], + [/prompt_text/gi, 'redacted_prompt_text'], + [/raw prompt/gi, '未脫敏提示內容'], + [/raw_prompt/gi, 'redacted_prompt'], + [/private reasoning/gi, '私有推理內容'], + [/private_reasoning/gi, 'redacted_private_reasoning'], + [/chain of thought/gi, '推理鏈內容'], + [/chain_of_thought/gi, 'redacted_chain_of_thought'], + [/raw Telegram payload/gi, '原始 Telegram 載荷'], + [/raw_telegram_payload/gi, 'redacted_telegram_payload'], + [/raw tool output/gi, '原始工具輸出'], + [/raw_tool_output/gi, 'redacted_tool_output'], + [/raw payload/gi, '原始載荷'], + [/raw_payload/gi, 'redacted_payload'], + [/authorization header/gi, '授權標頭'], + [/authorization_header/gi, 'redacted_authorization_header'], + [/secret value/gi, '機密明文'], + [/secret_value/gi, 'redacted_secret_value'], + [/work window transcript/gi, '內部協作逐字稿'], + [/work_window_transcript/gi, 'redacted_work_window_transcript'], + [/internal collaboration transcript/gi, '內部協作逐字稿'], +] + +function redactPublicResponseText(value: string): string { + return PUBLIC_TEXT_REPLACEMENTS.reduce((text, [pattern, replacement]) => text.replace(pattern, replacement), value) +} + +function redactPublicResponsePayload(value: T): T { + if (typeof value === 'string') return redactPublicResponseText(value) as T + if (Array.isArray(value)) return value.map(item => redactPublicResponsePayload(item)) as T + if (value && typeof value === 'object') { + return Object.fromEntries( + Object.entries(value).map(([key, nested]) => [key, redactPublicResponsePayload(nested)]) + ) as T + } + return value +} export class ApiError extends Error { constructor( @@ -43,7 +88,11 @@ async function handleResponse(response: Response): Promise { error.message || response.statusText ) } - return response.json() + const payload = await response.json() + if (response.url.includes('/agents/')) { + return redactPublicResponsePayload(payload) as T + } + return payload } export const apiClient = {