diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 09ae01d53..c98bc1ee5 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -8,10 +8,12 @@ - `/zh-TW/governance?tab=automation-inventory` 的 runner contract 區塊已脫敏 `actions.runner.*` 類 systemd 服務名稱與專案來源字串,不再把 owner namespace 放到頁面。 - `/zh-TW/security-compliance` 展開式 boundary code 區塊已改為人讀文字,避免這個舊入口成為 IwoooS 整合後的旁路洩漏面。 - `apps/web/messages/zh-TW.json` 與 `apps/web/messages/en.json` 已移除可被前端取用的 raw gate / contract 文案;鏡像內容維持繁體中文。 +- 追加 `security-mirror-progress-guard.py` 前台敏感字串防回歸:messages 不得含 raw owner namespace、`blocked_waiting_*`、`blockers=`、raw gate、source-control contract id 或工作視窗片語;核心前台頁需使用 `publicBoundaryText` / `publicContractText` / `redactPublicIdentifier`。 **本地驗證**: - JSON parse:`apps/web/messages/zh-TW.json`、`apps/web/messages/en.json` 通過。 - `python3 scripts/security/security-mirror-progress-guard.py --root .` → `SECURITY_MIRROR_PROGRESS_GUARD_OK`。 +- 追加防回歸 guard 後再次執行 `python3 -m py_compile scripts/security/security-mirror-progress-guard.py` 與 `python3 scripts/security/security-mirror-progress-guard.py --root .` 通過。 - `python3 scripts/security/source-control-owner-response-guard.py --root .` → `SOURCE_CONTROL_OWNER_RESPONSE_GUARD_OK`。 - `python3 scripts/ops/doc-secrets-sanity-check.py docs .gitea` → `DOC_SECRET_SANITY_OK scanned_files=858`。 - `pnpm --dir apps/web typecheck` 通過。 diff --git a/scripts/security/security-mirror-progress-guard.py b/scripts/security/security-mirror-progress-guard.py index 34ff63f64..6829fe999 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -244,12 +244,26 @@ def validate(root: Path) -> None: code_review_page = ( root / "apps" / "web" / "src" / "app" / "[locale]" / "code-review" / "page.tsx" ).read_text(encoding="utf-8") + governance_automation_inventory_tab = ( + root + / "apps" + / "web" + / "src" + / "app" + / "[locale]" + / "governance" + / "tabs" + / "automation-inventory-tab.tsx" + ).read_text(encoding="utf-8") errors_panel = (root / "apps" / "web" / "src" / "components" / "panels" / "ErrorsPanel.tsx").read_text( encoding="utf-8" ) iwooos_bridge = ( root / "apps" / "web" / "src" / "components" / "security" / "iwooos-read-only-bridge.tsx" ).read_text(encoding="utf-8") + public_security_redaction = ( + root / "apps" / "web" / "src" / "lib" / "public-security-redaction.ts" + ).read_text(encoding="utf-8") sidebar = (root / "apps" / "web" / "src" / "components" / "layout" / "sidebar.tsx").read_text( encoding="utf-8" ) @@ -259,6 +273,93 @@ def validate(root: Path) -> None: web_messages_zh = load_json(root / "apps" / "web" / "messages" / "zh-TW.json") web_messages_en = load_json(root / "apps" / "web" / "messages" / "en.json") + public_message_text = "\n".join( + [ + json.dumps(web_messages_zh, ensure_ascii=False), + json.dumps(web_messages_en, ensure_ascii=False), + ] + ) + for forbidden in [ + "owenhytsai", + "nexu-io", + "blocked_waiting_", + "blockers=", + "runtime_execution_authorized=false", + "action_buttons_allowed=false", + "execution_router_linked=false", + "repo_creation=false", + "repo_creation_authorized=false", + "github_primary_switch_authorized=false", + "source_control_owner_response_validation_rollup_v1", + "gitea_inventory_owner_attestation_response_v1", + "github_target_owner_decision_response_v1", + "source_control_ref_truth_owner_response_v1", + "source_control_workflow_secret_name_owner_response_v1", + "批准!", + "工作視窗", + "My request for Codex", + ]: + assert_text_not_contains("web_messages.public_frontstage_sensitive_text", public_message_text, forbidden) + + public_frontend_source_text = "\n".join( + [ + iwooos_projection_page, + security_compliance_page, + awooop_home_page, + awooop_work_items_page, + awooop_tenants_page, + awooop_runs_page, + awooop_run_detail_page, + awooop_approvals_page, + awooop_approval_detail_page, + awooop_contracts_page, + code_review_page, + governance_automation_inventory_tab, + iwooos_bridge, + public_security_redaction, + ] + ) + for forbidden in [ + "owenhytsai", + "nexu-io", + "blocked_waiting_", + "批准!", + "工作視窗", + "My request for Codex", + ]: + assert_text_not_contains("frontend.public_sensitive_source_text", public_frontend_source_text, forbidden) + + for label, text in [ + ("awooop_home_page", awooop_home_page), + ("awooop_runs_page", awooop_runs_page), + ("awooop_run_detail_page", awooop_run_detail_page), + ("awooop_approvals_page", awooop_approvals_page), + ("awooop_approval_detail_page", awooop_approval_detail_page), + ("awooop_contracts_page", awooop_contracts_page), + ("code_review_page", code_review_page), + ("security_compliance_page", security_compliance_page), + ("iwooos_bridge", iwooos_bridge), + ]: + assert_text_contains(f"{label}.public_boundary_redaction", text, "publicBoundaryText") + + for label, text in [ + ("awooop_home_page", awooop_home_page), + ("awooop_runs_page", awooop_runs_page), + ("awooop_run_detail_page", awooop_run_detail_page), + ("awooop_approvals_page", awooop_approvals_page), + ("awooop_approval_detail_page", awooop_approval_detail_page), + ("awooop_contracts_page", awooop_contracts_page), + ]: + assert_text_contains(f"{label}.public_contract_redaction", text, "publicContractText") + + assert_text_contains( + "governance_automation_inventory_tab.runner_identifier_redaction", + governance_automation_inventory_tab, + "redactPublicIdentifier", + ) + assert_text_not_contains("public_security_redaction.owner_namespace_literal", public_security_redaction, "owenhytsai") + assert_text_not_contains("public_security_redaction.external_namespace_literal", public_security_redaction, "nexu-io") + manifest_count = manifest["contract_count"] readiness_summary = readiness["summary"] rollup_summary = rollup["summary"]