From 4abc1fb893f2e3d8d6813f78f4d6851bfec73d4f Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 14 Jun 2026 22:31:27 +0800 Subject: [PATCH] =?UTF-8?q?fix(iwooos):=20=E5=9B=BA=E5=AE=9A=20s4.9=20?= =?UTF-8?q?=E7=BC=BA=E5=8F=A3=E7=A8=BD=E6=A0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/LOGBOOK.md | 35 ++ ..._9_owner_response_gap_audit_v1.schema.json | 198 +++++++++ ...9-OWNER-RESPONSE-GATE-CURRENT-GAP-AUDIT.md | 23 +- ...4-9-owner-response-gap-audit.snapshot.json | 307 ++++++++++++++ .../security/s4-9-owner-response-gap-audit.py | 397 ++++++++++++++++++ .../source-control-owner-response-guard.py | 127 ++++++ 6 files changed, 1082 insertions(+), 5 deletions(-) create mode 100644 docs/schemas/s4_9_owner_response_gap_audit_v1.schema.json create mode 100644 docs/security/s4-9-owner-response-gap-audit.snapshot.json create mode 100644 scripts/security/s4-9-owner-response-gap-audit.py diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index b5d1c497..b605b8a9 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,38 @@ +## 2026-06-14|S4.9 owner response gap audit snapshot 與 public surface redaction guard 完成 + +**背景**:使用者指出 `/zh-TW/awooop/tenants` 曾把 raw repository owner / namespace 顯示在前台,且 IwoooS 風險卡容易讓人誤以為「看得到」就是「已處理」。本輪將這兩個問題納入 S4.9 owner response gate 的固定缺口稽核:前台 / public API / HTML / bundle / messages 不得顯示 raw namespace、個人識別、外部 raw namespace 或內部協作語句;風險卡必須標示仍卡在哪些 owner gate,不得假性拉高進度。 + +**完成項目**: +- 新增 `scripts/security/s4-9-owner-response-gap-audit.py`,產生 S4.9 owner response gap audit 的 machine-readable snapshot。 +- 新增 `docs/security/s4-9-owner-response-gap-audit.snapshot.json`,固定 `current_requirement_gap_count=8`、`new_rule_count=7`、`rule_adjustment_count=7`、`priority_work_item_count=9`、S4.9 owner response gate `0%`、runtime gate `0`。 +- 新增 `docs/schemas/s4_9_owner_response_gap_audit_v1.schema.json`,明確禁止 request dispatch、owner response accepted、repo / refs / workflow / secret / runtime action。 +- 更新 `docs/security/S4-9-OWNER-RESPONSE-GATE-CURRENT-GAP-AUDIT.md`,基準改為 `gitea/main=8795c08d`、runtime deploy marker `605fde43`、Tenants redaction code commit `4bbc5269`,並新增 public surface identity redaction、internal evidence private boundary 與 repo-local `MEMORY.md` 缺檔啟動例外。 +- 更新 `scripts/security/source-control-owner-response-guard.py`,把 S4.9 gap audit snapshot 納入檢查:8 個 gap id、7 條新增規範、7 條調整規範、9 個優先工作項、public surface forbidden display 與所有 false boundaries 都被鎖住。 + +**本地驗證**: +- `python3 scripts/security/s4-9-owner-response-gap-audit.py --root . --generated-at 2026-06-14T22:35:00+08:00 --output docs/security/s4-9-owner-response-gap-audit.snapshot.json` → `S4_9_OWNER_RESPONSE_GAP_AUDIT_OK gaps=8 new_rules=7 adjustments=7 owner_gate=0 runtime_gate=0`。 +- `python3 scripts/security/source-control-owner-response-guard.py --root .` → `SOURCE_CONTROL_OWNER_RESPONSE_GUARD_OK`。 +- `python3 scripts/security/security-mirror-progress-guard.py --root .` → `SECURITY_MIRROR_PROGRESS_GUARD_OK`。 +- `python3 scripts/ops/doc-secrets-sanity-check.py docs .gitea` → `DOC_SECRET_SANITY_OK scanned_files=844`。 +- `python3 -m py_compile scripts/security/s4-9-owner-response-gap-audit.py scripts/security/source-control-owner-response-guard.py` 通過。 +- `python3 -m json.tool docs/security/s4-9-owner-response-gap-audit.snapshot.json` 與 `docs/schemas/s4_9_owner_response_gap_audit_v1.schema.json` 通過。 +- `git diff --check` 通過。 +- 新增檔與 S4.9 gap audit 相關檔案 sensitive scan:raw personal namespace、external raw namespace、in-app request phrase 與 approval-loop phrase 無命中。 + +**Production 驗證(沿用既有 deploy marker `605fde43`,本輪未變更前端 bundle)**: +- `/zh-TW/awooop/tenants?_v=8795c08d-s49-gap-prod-check` in-app browser current viewport:raw personal namespace `false`、external raw namespace `false`、internal collaboration phrase `false`、`SRC-###` 可見、redaction boundary 可見;`innerWidth=1006`、`scrollWidth=1000`、`horizontalOverflow=false`。 +- `GET /api/v1/platform/tenants?_v=8795c08d-s49-gap-api-check`:HTTP `200`,raw personal namespace `false`、external raw namespace `false`、internal collaboration phrase `false`;第一筆 `github_repo=SRC-001`、`source_scope_id=SRC-001`、`source_namespace_redacted=true`,boundaries 含 `repo_owner_namespace_redacted=true`、`raw_repository_namespace_visible=false`、`public_api_raw_repo_namespace_allowed=false`。 +- `/zh-TW/iwooos?_v=8795c08d-s49-gap-prod-check` in-app browser current viewport:IwoooS、審查後修正候選、高價值配置矩陣與 runtime gate `0` 可見;raw personal namespace `false`、external raw namespace `false`、internal collaboration phrase `false`;`innerWidth=1006`、`scrollWidth=1000`、`horizontalOverflow=false`。 +- Chrome mobile viewport `390x844`:`/zh-TW/awooop/tenants?_v=8795c08d-s49-gap-mobile` 與 `/zh-TW/iwooos?_v=8795c08d-s49-gap-mobile` 均 raw namespace `false`、internal collaboration phrase `false`、`scrollWidth=390`、`horizontalOverflow=false`;IwoooS mobile 可見 runtime gate `0` 與審查後修正候選。 + +**完成度與邊界**: +- S4.9 gap audit snapshot / schema / guard:`100%`。 +- Public surface identity redaction guard:`100%`。 +- Production readback / desktop / mobile sensitive scan:`100%`。 +- S4.9 owner response gate:`0%`;request sent、owner response received / accepted / rejected 全部 `0`。 +- IwoooS headline:維持 `64%`;active runtime gate:維持 `0`。 +- 這次沒有送 owner request、沒有收 owner response、沒有 repo creation、沒有 refs sync、沒有 workflow 修改、沒有 secret value collection、沒有 Nginx / Docker / host / firewall 寫操作、沒有 production deploy、沒有 runtime execution、沒有 action button。 + ## 2026-06-14|AwoooP Tenants source namespace 脫敏正式驗證完成 **背景**:`/zh-TW/awooop/tenants` 的「原始碼 / 專案庫範圍」曾直接顯示 raw repository owner / namespace,屬於前台資訊揭露缺口。這次先做 P0 低摩擦修補:產品 API 與前台只顯示脫敏範圍代號,raw owner / namespace 仍只允許留在內部只讀 evidence 文件,不出現在產品頁或瀏覽器 API payload。 diff --git a/docs/schemas/s4_9_owner_response_gap_audit_v1.schema.json b/docs/schemas/s4_9_owner_response_gap_audit_v1.schema.json new file mode 100644 index 00000000..ba1e7e06 --- /dev/null +++ b/docs/schemas/s4_9_owner_response_gap_audit_v1.schema.json @@ -0,0 +1,198 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:awoooi:s4-9-owner-response-gap-audit-v1", + "title": "S4.9 Owner Response Gap Audit v1", + "description": "S4.9 owner response gate 的只讀缺口稽核。此 schema 僅允許記錄不符合項、需新增規範、需調整規範與優先工作佇列,不授權 request dispatch、owner response accepted、repo / refs / workflow / secret / runtime action。", + "type": "object", + "required": [ + "schema_version", + "generated_at", + "git_commit", + "status", + "mode", + "basis", + "summary", + "false_boundaries", + "public_surface_redaction_requirements", + "current_requirement_gaps", + "new_rules_required", + "rule_adjustments_required", + "priority_work_queue", + "next_safe_actions" + ], + "properties": { + "schema_version": { + "const": "s4_9_owner_response_gap_audit_v1" + }, + "generated_at": { + "type": "string" + }, + "git_commit": { + "type": "string" + }, + "status": { + "const": "gap_audit_ready_owner_gate_zero" + }, + "mode": { + "const": "read_only_gap_audit_no_runtime_action" + }, + "basis": { + "type": "object", + "required": [ + "gitea_main_commit", + "latest_runtime_deploy_marker", + "latest_tenants_redaction_commit", + "latest_logbook_commit", + "source_control_rollup_date", + "source_control_rollup_templates", + "production_verification_required_for_frontend_changes" + ], + "properties": { + "gitea_main_commit": {"type": "string"}, + "latest_runtime_deploy_marker": {"type": "string"}, + "latest_tenants_redaction_commit": {"type": "string"}, + "latest_logbook_commit": {"type": "string"}, + "source_control_rollup_date": {"type": "string"}, + "source_control_rollup_templates": {"type": "integer"}, + "production_verification_required_for_frontend_changes": {"type": "boolean", "const": true} + }, + "additionalProperties": false + }, + "summary": { + "type": "object", + "required": [ + "current_requirement_gap_count", + "active_blocker_count", + "active_risk_or_process_gap_count", + "mitigated_needs_guard_count", + "new_rule_count", + "rule_adjustment_count", + "priority_work_item_count", + "s4_9_owner_response_gate_percent", + "request_sent_count", + "owner_response_received_count", + "owner_response_accepted_count", + "owner_response_rejected_count", + "runtime_gate_count", + "public_surface_redaction_guard_ready", + "public_surface_raw_namespace_allowed", + "work_session_transcript_public_allowed" + ], + "properties": { + "current_requirement_gap_count": {"type": "integer", "minimum": 1}, + "active_blocker_count": {"type": "integer", "minimum": 0}, + "active_risk_or_process_gap_count": {"type": "integer", "minimum": 0}, + "mitigated_needs_guard_count": {"type": "integer", "minimum": 0}, + "new_rule_count": {"type": "integer", "minimum": 1}, + "rule_adjustment_count": {"type": "integer", "minimum": 1}, + "priority_work_item_count": {"type": "integer", "minimum": 1}, + "s4_9_owner_response_gate_percent": {"type": "integer", "const": 0}, + "request_sent_count": {"type": "integer", "const": 0}, + "owner_response_received_count": {"type": "integer", "const": 0}, + "owner_response_accepted_count": {"type": "integer", "const": 0}, + "owner_response_rejected_count": {"type": "integer", "const": 0}, + "runtime_gate_count": {"type": "integer", "const": 0}, + "public_surface_redaction_guard_ready": {"type": "boolean", "const": true}, + "public_surface_raw_namespace_allowed": {"type": "boolean", "const": false}, + "work_session_transcript_public_allowed": {"type": "boolean", "const": false} + }, + "additionalProperties": false + }, + "false_boundaries": { + "type": "object", + "additionalProperties": {"type": "boolean", "const": false}, + "minProperties": 1 + }, + "public_surface_redaction_requirements": { + "type": "object", + "required": [ + "allowed_display", + "forbidden_display", + "required_fields_or_markers", + "verification" + ], + "properties": { + "allowed_display": {"type": "array", "items": {"type": "string"}, "minItems": 1}, + "forbidden_display": {"type": "array", "items": {"type": "string"}, "minItems": 1}, + "required_fields_or_markers": {"type": "array", "items": {"type": "string"}, "minItems": 1}, + "verification": {"type": "array", "items": {"type": "string"}, "minItems": 1} + }, + "additionalProperties": false + }, + "current_requirement_gaps": { + "type": "array", + "items": {"$ref": "#/$defs/gap_item"}, + "minItems": 1 + }, + "new_rules_required": { + "type": "array", + "items": {"$ref": "#/$defs/rule_item"}, + "minItems": 1 + }, + "rule_adjustments_required": { + "type": "array", + "items": {"$ref": "#/$defs/adjustment_item"}, + "minItems": 1 + }, + "priority_work_queue": { + "type": "array", + "items": {"$ref": "#/$defs/work_item"}, + "minItems": 1 + }, + "next_safe_actions": { + "type": "array", + "items": {"type": "string"}, + "minItems": 1 + } + }, + "$defs": { + "gap_item": { + "type": "object", + "required": ["gap_id", "priority", "status", "requirement", "current_state", "required_next_step"], + "properties": { + "gap_id": {"type": "string"}, + "priority": {"type": "string"}, + "status": {"type": "string"}, + "requirement": {"type": "string"}, + "current_state": {"type": "string"}, + "required_next_step": {"type": "string"} + }, + "additionalProperties": false + }, + "rule_item": { + "type": "object", + "required": ["rule_id", "priority", "rule", "verification"], + "properties": { + "rule_id": {"type": "string"}, + "priority": {"type": "string"}, + "rule": {"type": "string"}, + "verification": {"type": "string"} + }, + "additionalProperties": false + }, + "adjustment_item": { + "type": "object", + "required": ["adjustment_id", "priority", "from_rule", "adjusted_rule"], + "properties": { + "adjustment_id": {"type": "string"}, + "priority": {"type": "string"}, + "from_rule": {"type": "string"}, + "adjusted_rule": {"type": "string"} + }, + "additionalProperties": false + }, + "work_item": { + "type": "object", + "required": ["priority", "work_item", "completion_percent", "blocked_by", "next_validation"], + "properties": { + "priority": {"type": "string"}, + "work_item": {"type": "string"}, + "completion_percent": {"type": "integer", "minimum": 0, "maximum": 100}, + "blocked_by": {"type": "string"}, + "next_validation": {"type": "string"} + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/docs/security/S4-9-OWNER-RESPONSE-GATE-CURRENT-GAP-AUDIT.md b/docs/security/S4-9-OWNER-RESPONSE-GATE-CURRENT-GAP-AUDIT.md index 4c6ec0f5..3ee11a5b 100644 --- a/docs/security/S4-9-OWNER-RESPONSE-GATE-CURRENT-GAP-AUDIT.md +++ b/docs/security/S4-9-OWNER-RESPONSE-GATE-CURRENT-GAP-AUDIT.md @@ -2,11 +2,13 @@ | 項目 | 內容 | |------|------| -| 日期 | 2026-06-13 | -| 基準 | `gitea/main=2afb7c0a fix(governance): harden agent evidence redaction` | -| 範圍 | S4.9 Gitea owner attestation response gate 與 S4.13 owner response validation rollup | +| 日期 | 2026-06-14 | +| 基準 | `gitea/main=8795c08d docs(iwooos): 記錄 ssh network production 驗證 [skip ci]`;runtime deploy marker `605fde43`;Tenants redaction code commit `4bbc5269` | +| 範圍 | S4.9 Gitea owner attestation response gate、S4.13 owner response validation rollup、public surface identity redaction boundary | | 模式 | 只讀 committed snapshot / 文件稽核 | | 不可誤讀 | 不是 request sent、不是 owner response received、不是 accepted、不是 repo / refs / workflow / secret / runtime 授權 | +| Machine-readable snapshot | `docs/security/s4-9-owner-response-gap-audit.snapshot.json` | +| Schema | `docs/schemas/s4_9_owner_response_gap_audit_v1.schema.json` | ## 1. 核心結論 @@ -14,6 +16,8 @@ S4.9 的基礎規範已存在,且已能被 `source-control-owner-response-guar 真正尚未完成的是「owner 回覆本身」。目前 `request_sent=false`、`received=0`、`accepted=0`、`rejected=0`,所以 S4.9 owner response gate 仍是 `0%`。任何 UI、request template、AwoooP 顯示、reviewer checklist 或 LOGBOOK 文字都不得把這個 gate 拉高。 +本輪另外把使用者指出的前台資訊揭露問題納入 S4.9 缺口稽核:前台、public API、HTML、bundle 與 messages 不得顯示個人 namespace、外部 raw namespace、工作視窗對話或內部 session 語句。AwoooP Tenants 已改成 `SRC-###` 脫敏範圍代號,但此規則必須持續由 guard 與 production desktop / mobile sensitive scan 保護。 + ## 2. 已符合目前要求 | 類別 | 現況 | 依據 | @@ -25,12 +29,13 @@ S4.9 的基礎規範已存在,且已能被 `source-control-owner-response-guar | 五題齊備才可 accepted | 已要求 5 個 response templates 都收到可驗收回覆,部分回覆只能 waiting 或 request_more_evidence | `preflight-all-five-items-before-accepted` | | 四包收件順序 | S4.9 -> S4.10 -> S4.11 -> S4.12 已固定 | `source-control-owner-response-validation-rollup.snapshot.json` | | 安全旗標 | token、secret、repo write、refs sync、GitHub primary、action button 全部 `false` | owner response guard | +| Public surface redaction | `/zh-TW/awooop/tenants` 與 `GET /api/v1/platform/tenants` 已改用 `SRC-###`、`source_scope_id`、`source_namespace_redacted=true` | `security-mirror-progress-guard.py`、production desktop / mobile verification | ## 3. 目前仍不符合最新推進要求 | 缺口 | 影響 | 下一步 | |------|------|--------| -| P0 主控總帳與缺口稽核基準需跟上最新 `gitea/main` | 平行 Session 已推進 P0-PUBLICENV public host alias redaction、P2-105 critic / reviewer result capture、governance evidence redaction hardening 與多次正式 Browser smoke;舊 commit 基準會讓新 Session 誤判下一步 | 本輪已更新到 `2afb7c0a`;最新 S4.9 仍是等待 owner response,後續每次推送前仍需 fetch、讀 LOGBOOK 最新段落與同步 runs / deploy marker | +| P0 主控總帳與缺口稽核基準需跟上最新 `gitea/main` | 平行 Session 已推進 Tenants redaction、Backup / Restore、Public Gateway、K8s / ArgoCD、SSH / network acceptance ledger 與 production Browser smoke;舊 commit 基準會讓新 Session 誤判下一步 | 本輪已更新到 `gitea/main=8795c08d` 與 deploy marker `605fde43`;最新 S4.9 仍是等待 owner response,後續每次推送前仍需 fetch、讀 LOGBOOK 最新段落與同步 runs / deploy marker | | S4.9 gate 仍只有 request-ready,沒有 owner response | IwoooS 64% 不能因規範存在而往前解鎖 | 維持 `0%`,只準備收件缺口,不調高 progress | | S4.13 rollup 文件曾殘留舊模板總數 | Snapshot 已是 `5 + 9 + 5 + 5 = 24`,但文件仍可能寫成 `22`,會造成 reviewer 誤判 S4.10 目標數 | 已同步文件並把 `source-control-owner-response-guard.py` 納入文件一致性檢查 | | request packet 的欄位名稱存在同義詞 | `affected_repos`、`affected_sources`、`affected_repos_or_sources_or_namespace`、`evidence_refs` 與使用者要求的 `affected_scope`、`redacted_evidence_refs` 容易在 UI / handoff 中混用 | 已補 `S4-9-CANONICAL-OWNER-RESPONSE-ENVELOPE.md`,後續顯示層以六欄 canonical envelope 呈現;source templates 可保留細分欄位 | @@ -38,6 +43,9 @@ S4.9 的基礎規範已存在,且已能被 `source-control-owner-response-guar | 尚未有 owner response reviewer outcome | reviewer checklist 存在,但沒有任何可分類 response | 等脫敏 metadata 進來後,才能進補件、隔離、拒收、只讀更新候選 | | 部分文件仍可能把近期 P2-403I/J/K 或資安 P1 工作誤當 S4.9 已解鎖 | 平行 Session 已推進 AI Agent 報表 / 告警路由,但 S4.9 owner response 仍是獨立 P0 gate | 後續 LOGBOOK / workplan 每次都標註平行 Session、最新基線與 S4.9 received / accepted 仍為 `0 / 0` | | P0-PUBLICENV 與 P2-105 容易被誤讀為 S4.9 owner response | 公開主機 alias redaction、bundle clean、production smoke、critic / reviewer scorecard 都不是 owner role / team、decision、reason、scope、redacted refs、followup owner 的實際回覆 | 在 rollup 與 LOGBOOK 明確標記:這些進展不增加 request_sent、received、accepted 或 runtime gate | +| 前台 raw namespace 或工作視窗內容外洩 | 產品頁若直接顯示 raw source-control evidence,會暴露個人 namespace、外部 namespace 或內部協作脈絡 | 已補 Tenants 脫敏;後續新增頁面 / API 必須先走 public surface redaction gate 與 production sensitive scan | +| 內部 evidence 與產品文案邊界不清 | `docs/security` 可能保留必要技術識別供內部稽核,但不得被直接接到前台渲染 | 前台只能顯示 redacted scope id、aggregate count、risk tier、readiness state、redacted evidence ref | +| repo-local `MEMORY.md` 缺檔 | 啟動規範要求讀 `MEMORY.md`,但本 release worktree 沒有此檔 | 已改讀全域 memory 摘要與 LOGBOOK;後續需把此視為啟動程序缺口,不得假稱 repo-local MEMORY.md 已讀 | ## 4. 需要新增或強化的規範 @@ -48,6 +56,9 @@ S4.9 的基礎規範已存在,且已能被 `source-control-owner-response-guar | Request sent 與 received 分離 | request packet 顯示、人工送件、owner response received、accepted 必須是四個獨立狀態 | 已有規範,需在後續 audit metadata 實作時保持 | | Quarantine-first 收件 | 疑似 token、secret、private key、cookie、session、authorization header、private URL credential、未脫敏截圖先隔離,不渲染 raw payload | 已有規範,需維持 | | 平行 Session 衝突規則 | 任一 Session 推送前必須 fetch / fast-forward 到最新 `gitea/main`,並同步 commit / run / production evidence / gate 0 false | 已執行,需持續 | +| Public surface redaction gate | 前台、public API、HTML、bundle、messages 不得出現 raw owner namespace、個人識別、外部 raw namespace 或工作視窗對話 | 已補 snapshot 與 guard 接入;每次前端 / API 變更需 production sensitive scan | +| Internal evidence private boundary | 內部 source-control raw evidence 只能留在 private evidence / docs,不得直接作為產品渲染來源 | 已納入 `s4-9-owner-response-gap-audit.snapshot.json` | +| Machine-readable S4.9 gap audit | S4.9 缺口稽核不得只停在 MD,必須有 snapshot 與 guard | 已新增 `s4_9_owner_response_gap_audit_v1` | ## 5. 下一個 owner response 需要補的五題 @@ -87,9 +98,11 @@ S4.9 的基礎規範已存在,且已能被 `source-control-owner-response-guar | 工作 | 完成度 | 說明 | |------|--------|------| | S4.9 現況缺口稽核 | 100% | 已列出已符合、仍不符合、需新增、需調整、五題回覆與 0 / false 邊界 | +| S4.9 machine-readable gap audit snapshot | 100% | 已新增 `s4-9-owner-response-gap-audit.snapshot.json` 與 schema,固定 `gaps=8`、`new_rules=7`、`adjustments=7`、owner gate `0`、runtime gate `0` | +| Public surface identity redaction boundary | 100% | Tenants 前台 / public API 已用 `SRC-###`;後續由 guard 與 production sensitive scan 持續保護 | | S4.9 canonical owner response envelope | 100% | 已補六欄信封、alias 映射、五題投影、quarantine-first 與 reviewer checklist | | S4.9 owner response gate | 0% | 沒有收到 owner response,不得調高 | -| S4.9 基準與日期一致性 | 100% | 已跟到 `gitea/main=2afb7c0a`,並要求 guard 擋下過期 rollup 日期與舊模板公式 | +| S4.9 基準與日期一致性 | 100% | 已跟到 `gitea/main=8795c08d`、deploy marker `605fde43`,並要求 guard 擋下過期 rollup 日期與舊模板公式 | | S4.13 rollup 文件一致性 | 100% | 已把 `22` 舊口徑修正為 `24`,並由 guard 檢查 | | IwoooS 整體 | 維持 64% | 只讀稽核不改 runtime readiness | | active runtime gate | 0 | 不變 | diff --git a/docs/security/s4-9-owner-response-gap-audit.snapshot.json b/docs/security/s4-9-owner-response-gap-audit.snapshot.json new file mode 100644 index 00000000..d259d94f --- /dev/null +++ b/docs/security/s4-9-owner-response-gap-audit.snapshot.json @@ -0,0 +1,307 @@ +{ + "basis": { + "gitea_main_commit": "8795c08d", + "latest_logbook_commit": "8795c08d", + "latest_runtime_deploy_marker": "605fde43", + "latest_tenants_redaction_commit": "4bbc5269", + "production_verification_required_for_frontend_changes": true, + "source_control_rollup_date": "2026-06-13", + "source_control_rollup_templates": 24 + }, + "current_requirement_gaps": [ + { + "current_state": "request_sent=false、received=0、accepted=0、rejected=0。", + "gap_id": "s49_owner_response_absent", + "priority": "P0", + "required_next_step": "只能等待人工送件與 owner 脫敏回覆;不得用 UI、LOGBOOK 或 AwoooP approval 補成 accepted。", + "requirement": "S4.9 必須收到完整 owner response metadata,且五題都通過 reviewer checklist。", + "status": "active_blocker" + }, + { + "current_state": "audit event templates 存在,但 emitted_event_count=0。", + "gap_id": "s49_dispatch_audit_event_absent", + "priority": "P0", + "required_next_step": "送件前維持 0;送件後只保存 metadata,不保存 raw response body。", + "requirement": "request shown、request sent、owner response received、reviewer outcome 必須是分離 audit metadata。", + "status": "active_blocker" + }, + { + "current_state": "尚無 owner response,reviewer outcome 仍是 template-only。", + "gap_id": "s49_reviewer_outcome_absent", + "priority": "P0", + "required_next_step": "補 reviewer outcome ledger,但不得因此開 runtime gate。", + "requirement": "任何 owner response 都要先分類為 waiting、supplement、quarantine、reject 或 read-only update candidate。", + "status": "active_blocker" + }, + { + "current_state": "AwoooP Tenants 已改用 SRC-###;仍需由 guard 固定 public surface redaction 規則。", + "gap_id": "public_surface_identity_leak_risk", + "priority": "P0", + "required_next_step": "持續跑 security mirror guard;新增頁面或 API payload 時一律先做 sensitive public-surface scan。", + "requirement": "前台與瀏覽器 API 不得顯示個人 namespace、外部 org namespace、工作視窗對話或內部 session 語句。", + "status": "mitigated_needs_guard" + }, + { + "current_state": "部分 docs/security source-control evidence 仍有 raw repo identifiers,屬內部 evidence;需要明確 public/private boundary。", + "gap_id": "raw_namespace_internal_evidence_misroute_risk", + "priority": "P0", + "required_next_step": "所有前台資料源必須使用 redacted scope id 或 evidence ref,不直接讀 raw source-control evidence。", + "requirement": "內部 source-control evidence 可保留必要技術識別,但不得被路由到產品頁、public API、公開 bundle 或截圖文案。", + "status": "active_risk" + }, + { + "current_state": "舊 MD 仍提到 2026-06-13 與舊基準;本 snapshot 將基準更新到目前 release worktree。", + "gap_id": "latest_basis_staleness_risk", + "priority": "P0", + "required_next_step": "每次推送前 fetch gitea 並更新 basis,不得沿用舊 commit 宣稱最新。", + "requirement": "S4.9 缺口稽核基準需跟上最新 gitea/main、deploy marker、production verification 與 LOGBOOK。", + "status": "remediated_by_this_snapshot" + }, + { + "current_state": "本輪已 fetch 且 worktree 與 gitea/main 一致;仍需每次推送前重查。", + "gap_id": "parallel_session_conflict_risk", + "priority": "P0", + "required_next_step": "禁止 force push;若 gitea/main 前進,只能 fast-forward / rebase 正常收斂。", + "requirement": "另一個 AwoooP Session 與本 Session 的 commit、run、deploy marker、production evidence 必須同步。", + "status": "active_risk" + }, + { + "current_state": "此 release worktree 未包含 repo-local MEMORY.md。", + "gap_id": "release_worktree_memory_index_absent", + "priority": "P1", + "required_next_step": "把缺檔視為啟動程序缺口,不阻擋只讀工作,但不得聲稱已讀 repo-local MEMORY.md。", + "requirement": "Session 啟動必讀 MEMORY.md;若 release worktree 缺檔,必須改讀全域 memory 與 LOGBOOK,並記錄例外。", + "status": "active_process_gap" + } + ], + "false_boundaries": { + "action_buttons_allowed": false, + "force_push_authorized": false, + "owner_response_accepted": false, + "owner_response_received": false, + "raw_namespace_public_surface_allowed": false, + "redacted_payload_ingested": false, + "refs_sync_authorized": false, + "repo_creation_authorized": false, + "request_dispatch_authorized": false, + "request_sent": false, + "runner_enablement_authorized": false, + "runtime_execution_authorized": false, + "secret_value_collection_allowed": false, + "visibility_change_authorized": false, + "work_session_transcript_public_allowed": false, + "workflow_modification_authorized": false + }, + "generated_at": "2026-06-14T22:35:00+08:00", + "git_commit": "8795c08d", + "mode": "read_only_gap_audit_no_runtime_action", + "new_rules_required": [ + { + "priority": "P0", + "rule": "前台、public API、HTML、bundle、messages 不得出現 raw owner namespace、個人識別或工作視窗對話。", + "rule_id": "public_surface_redaction_gate", + "verification": "security-mirror-progress-guard + production desktop/mobile sensitive scan。" + }, + { + "priority": "P0", + "rule": "S4.9 現況缺口稽核不得只停在 MD,必須有 machine-readable snapshot 與 guard。", + "rule_id": "s49_gap_audit_snapshot_required", + "verification": "source-control-owner-response-guard 必須讀取本 snapshot。" + }, + { + "priority": "P0", + "rule": "內部 source-control raw evidence 僅能留在 private docs / committed evidence,不得直接成為產品渲染來源。", + "rule_id": "raw_evidence_private_boundary", + "verification": "前台只讀 redacted scope id、evidence ref 或 aggregate count。" + }, + { + "priority": "P0", + "rule": "owner response received / accepted / rejected 只能由 reviewer record 更新,不得由 request draft、UI 或 LOGBOOK 文字更新。", + "rule_id": "owner_response_counts_are_runtime_locked", + "verification": "所有 snapshot false boundaries 維持 0 / false。" + }, + { + "priority": "P0", + "rule": "request ready、request sent、response received、response accepted、runtime approval 必須是五個獨立狀態。", + "rule_id": "dispatch_received_accepted_separation", + "verification": "summary counters 必須分離,且 runtime gate 永遠等待獨立批准。" + }, + { + "priority": "P0", + "rule": "每次 commit / push 前必須 fetch gitea,並同步另一 Session 的 run / deploy / production evidence。", + "rule_id": "parallel_session_basis_refresh", + "verification": "LOGBOOK 必須記錄 basis、runs、deploy marker 與 gate 邊界。" + }, + { + "priority": "P1", + "rule": "repo-local MEMORY.md 缺檔時,需記錄使用全域 memory 與 LOGBOOK 的替代讀取路徑。", + "rule_id": "memory_index_startup_exception", + "verification": "啟動記錄不得假稱 repo-local MEMORY.md 已讀。" + } + ], + "next_safe_actions": [ + "更新 S4.9 缺口稽核文件基準與 snapshot reference。", + "把本 snapshot 納入 source-control owner response guard。", + "繼續補 owner response acceptance ledger,但所有 request / received / accepted / runtime gate 維持 0 / false。" + ], + "priority_work_queue": [ + { + "blocked_by": "尚未人工送件、尚未收到 owner response。", + "completion_percent": 70, + "next_validation": "source-control-owner-response-guard。", + "priority": "P0-1", + "work_item": "S4.9 owner response gate 收件前置" + }, + { + "blocked_by": "後續新增頁面仍需 guard 持續保護。", + "completion_percent": 100, + "next_validation": "production desktop/mobile sensitive scan。", + "priority": "P0-2", + "work_item": "前台 / public API identity redaction" + }, + { + "blocked_by": "尚缺 owner-provided live conf、rendered diff、nginx -t evidence、route smoke、maintenance window、rollback owner。", + "completion_percent": 86, + "next_validation": "public gateway acceptance snapshot + owner-provided evidence review。", + "priority": "P0-3", + "work_item": "Nginx public gateway owner response acceptance" + }, + { + "blocked_by": "尚缺 ArgoCD health readback、rendered manifest diff、rollback revision、owner response。", + "completion_percent": 62, + "next_validation": "k8s-argocd owner response acceptance snapshot。", + "priority": "P0-4", + "work_item": "K8s / ArgoCD owner response acceptance" + }, + { + "blocked_by": "尚缺 restore drill approval package、offsite owner、escrow owner、retention owner、validation plan。", + "completion_percent": 62, + "next_validation": "backup restore acceptance snapshot。", + "priority": "P0-5", + "work_item": "Backup / restore / escrow owner response acceptance" + }, + { + "blocked_by": "尚缺 110 / 188 live hash、restart window、rollback owner、post-check 指標。", + "completion_percent": 50, + "next_validation": "host service owner request / future acceptance ledger。", + "priority": "P1-1", + "work_item": "Docker Compose / systemd / host service owner response" + }, + { + "blocked_by": "尚缺 live access state、allowed source CIDR、host key pinning、firewall owner、NetworkPolicy / NodePort / WireGuard owner。", + "completion_percent": 58, + "next_validation": "ssh network acceptance snapshot。", + "priority": "P1-2", + "work_item": "SSH / firewall / network access owner response acceptance" + }, + { + "blocked_by": "尚缺 live drift evidence、receiver owner、reload owner、route smoke、receipt proof。", + "completion_percent": 62, + "next_validation": "monitoring owner request / future acceptance ledger。", + "priority": "P1-3", + "work_item": "Monitoring / alerting / observability owner response" + }, + { + "blocked_by": "尚缺 deployment boundary、external agent boundary、treasury owner、runtime gate owner。", + "completion_percent": 68, + "next_validation": "agent bounty owner request draft 與 future acceptance ledger。", + "priority": "P1-4", + "work_item": "agent-bounty-protocol C0 runtime / MCP / A2A / treasury owner response" + } + ], + "public_surface_redaction_requirements": { + "allowed_display": [ + "SRC-### scope id", + "aggregate count", + "risk tier", + "readiness state", + "redacted evidence ref" + ], + "forbidden_display": [ + "raw repository owner namespace", + "personal namespace", + "external organization namespace when not explicitly public-safe", + "工作視窗對話內容", + "approval chat phrase", + "token / secret / private key / cookie / authorization header" + ], + "required_fields_or_markers": [ + "source_scope_id", + "source_namespace_redacted", + "repo_owner_namespace_redacted", + "raw_repository_namespace_visible", + "public_api_raw_repo_namespace_allowed" + ], + "verification": [ + "public API payload sensitive scan", + "production HTML sensitive scan", + "desktop browser sensitive scan", + "mobile browser sensitive scan", + "horizontal overflow check" + ] + }, + "rule_adjustments_required": [ + { + "adjusted_rule": "低摩擦不代表延後修補即時資訊揭露;public surface leak 可先 source-control 止血,再補 owner gate。", + "adjustment_id": "low_friction_but_p0_stop_the_bleed", + "from_rule": "初期資安低摩擦、先只讀框架。", + "priority": "P0" + }, + { + "adjusted_rule": "技術識別只能留在內部 evidence;產品頁只顯示 redacted scope id、風險等級、狀態與 evidence ref。", + "adjustment_id": "internal_evidence_not_product_copy", + "from_rule": "source-control evidence 可保留技術識別。", + "priority": "P0" + }, + { + "adjusted_rule": "AwoooP approval、Runs、Work Items、Tenants 顯示全部只算狀態,不等於 IwoooS security acceptance。", + "adjustment_id": "awooop_approval_not_security_acceptance", + "from_rule": "AwoooP 可顯示 owner response / approval 候選。", + "priority": "P0" + }, + { + "adjusted_rule": "Nginx / public gateway / DNS / TLS 是 C0,優先要求 source-of-truth、drift evidence、owner response、rollback 與 post-check。", + "adjustment_id": "nginx_config_control_first", + "from_rule": "高價值配置逐步納管。", + "priority": "P0" + }, + { + "adjusted_rule": "即使 owner 文字包含同意、批准、OK,也只算 metadata decision,不自動授權 refs sync、reload、deploy 或 runtime action。", + "adjustment_id": "owner_response_language_not_execution", + "from_rule": "owner 可回覆 decision。", + "priority": "P0" + }, + { + "adjusted_rule": "改前端或 public API 後必須做 production desktop/mobile sensitive scan、水平溢出檢查與關鍵卡片可見檢查。", + "adjustment_id": "public_verification_required_after_frontend_change", + "from_rule": "改前端後做 smoke。", + "priority": "P0" + }, + { + "adjusted_rule": "agent-bounty-protocol 需以 C0 runtime / MCP / A2A / treasury 邊界管理,但保持獨立產品與 owner response。", + "adjustment_id": "agent_bounty_c0_runtime_boundary", + "from_rule": "agent-bounty-protocol 納入 IwoooS scope。", + "priority": "P1" + } + ], + "schema_version": "s4_9_owner_response_gap_audit_v1", + "status": "gap_audit_ready_owner_gate_zero", + "summary": { + "active_blocker_count": 3, + "active_risk_or_process_gap_count": 3, + "current_requirement_gap_count": 8, + "mitigated_needs_guard_count": 1, + "new_rule_count": 7, + "owner_response_accepted_count": 0, + "owner_response_received_count": 0, + "owner_response_rejected_count": 0, + "priority_work_item_count": 9, + "public_surface_raw_namespace_allowed": false, + "public_surface_redaction_guard_ready": true, + "request_sent_count": 0, + "rule_adjustment_count": 7, + "runtime_gate_count": 0, + "s4_9_owner_response_gate_percent": 0, + "work_session_transcript_public_allowed": false + } +} diff --git a/scripts/security/s4-9-owner-response-gap-audit.py b/scripts/security/s4-9-owner-response-gap-audit.py new file mode 100644 index 00000000..7fee80c6 --- /dev/null +++ b/scripts/security/s4-9-owner-response-gap-audit.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +""" +S4.9 owner response gate 現況缺口稽核 snapshot 產生器。 + +本工具只讀 committed snapshot、文件與 git metadata,整理哪些要求仍未符合、 +哪些規範需新增、哪些規範需調整。它不送 owner request、不收 owner response、 +不連 Gitea / GitHub、不讀 secret、不做 runtime action。 +""" + +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)) + +FALSE_BOUNDARIES = { + "request_dispatch_authorized": False, + "request_sent": False, + "owner_response_received": False, + "owner_response_accepted": False, + "redacted_payload_ingested": False, + "repo_creation_authorized": False, + "visibility_change_authorized": False, + "refs_sync_authorized": False, + "force_push_authorized": False, + "workflow_modification_authorized": False, + "runner_enablement_authorized": False, + "secret_value_collection_allowed": False, + "raw_namespace_public_surface_allowed": False, + "work_session_transcript_public_allowed": False, + "runtime_execution_authorized": False, + "action_buttons_allowed": False, +} + +CURRENT_REQUIREMENT_GAPS = [ + { + "gap_id": "s49_owner_response_absent", + "priority": "P0", + "status": "active_blocker", + "requirement": "S4.9 必須收到完整 owner response metadata,且五題都通過 reviewer checklist。", + "current_state": "request_sent=false、received=0、accepted=0、rejected=0。", + "required_next_step": "只能等待人工送件與 owner 脫敏回覆;不得用 UI、LOGBOOK 或 AwoooP approval 補成 accepted。", + }, + { + "gap_id": "s49_dispatch_audit_event_absent", + "priority": "P0", + "status": "active_blocker", + "requirement": "request shown、request sent、owner response received、reviewer outcome 必須是分離 audit metadata。", + "current_state": "audit event templates 存在,但 emitted_event_count=0。", + "required_next_step": "送件前維持 0;送件後只保存 metadata,不保存 raw response body。", + }, + { + "gap_id": "s49_reviewer_outcome_absent", + "priority": "P0", + "status": "active_blocker", + "requirement": "任何 owner response 都要先分類為 waiting、supplement、quarantine、reject 或 read-only update candidate。", + "current_state": "尚無 owner response,reviewer outcome 仍是 template-only。", + "required_next_step": "補 reviewer outcome ledger,但不得因此開 runtime gate。", + }, + { + "gap_id": "public_surface_identity_leak_risk", + "priority": "P0", + "status": "mitigated_needs_guard", + "requirement": "前台與瀏覽器 API 不得顯示個人 namespace、外部 org namespace、工作視窗對話或內部 session 語句。", + "current_state": "AwoooP Tenants 已改用 SRC-###;仍需由 guard 固定 public surface redaction 規則。", + "required_next_step": "持續跑 security mirror guard;新增頁面或 API payload 時一律先做 sensitive public-surface scan。", + }, + { + "gap_id": "raw_namespace_internal_evidence_misroute_risk", + "priority": "P0", + "status": "active_risk", + "requirement": "內部 source-control evidence 可保留必要技術識別,但不得被路由到產品頁、public API、公開 bundle 或截圖文案。", + "current_state": "部分 docs/security source-control evidence 仍有 raw repo identifiers,屬內部 evidence;需要明確 public/private boundary。", + "required_next_step": "所有前台資料源必須使用 redacted scope id 或 evidence ref,不直接讀 raw source-control evidence。", + }, + { + "gap_id": "latest_basis_staleness_risk", + "priority": "P0", + "status": "remediated_by_this_snapshot", + "requirement": "S4.9 缺口稽核基準需跟上最新 gitea/main、deploy marker、production verification 與 LOGBOOK。", + "current_state": "舊 MD 仍提到 2026-06-13 與舊基準;本 snapshot 將基準更新到目前 release worktree。", + "required_next_step": "每次推送前 fetch gitea 並更新 basis,不得沿用舊 commit 宣稱最新。", + }, + { + "gap_id": "parallel_session_conflict_risk", + "priority": "P0", + "status": "active_risk", + "requirement": "另一個 AwoooP Session 與本 Session 的 commit、run、deploy marker、production evidence 必須同步。", + "current_state": "本輪已 fetch 且 worktree 與 gitea/main 一致;仍需每次推送前重查。", + "required_next_step": "禁止 force push;若 gitea/main 前進,只能 fast-forward / rebase 正常收斂。", + }, + { + "gap_id": "release_worktree_memory_index_absent", + "priority": "P1", + "status": "active_process_gap", + "requirement": "Session 啟動必讀 MEMORY.md;若 release worktree 缺檔,必須改讀全域 memory 與 LOGBOOK,並記錄例外。", + "current_state": "此 release worktree 未包含 repo-local MEMORY.md。", + "required_next_step": "把缺檔視為啟動程序缺口,不阻擋只讀工作,但不得聲稱已讀 repo-local MEMORY.md。", + }, +] + +NEW_RULES_REQUIRED = [ + { + "rule_id": "public_surface_redaction_gate", + "priority": "P0", + "rule": "前台、public API、HTML、bundle、messages 不得出現 raw owner namespace、個人識別或工作視窗對話。", + "verification": "security-mirror-progress-guard + production desktop/mobile sensitive scan。", + }, + { + "rule_id": "s49_gap_audit_snapshot_required", + "priority": "P0", + "rule": "S4.9 現況缺口稽核不得只停在 MD,必須有 machine-readable snapshot 與 guard。", + "verification": "source-control-owner-response-guard 必須讀取本 snapshot。", + }, + { + "rule_id": "raw_evidence_private_boundary", + "priority": "P0", + "rule": "內部 source-control raw evidence 僅能留在 private docs / committed evidence,不得直接成為產品渲染來源。", + "verification": "前台只讀 redacted scope id、evidence ref 或 aggregate count。", + }, + { + "rule_id": "owner_response_counts_are_runtime_locked", + "priority": "P0", + "rule": "owner response received / accepted / rejected 只能由 reviewer record 更新,不得由 request draft、UI 或 LOGBOOK 文字更新。", + "verification": "所有 snapshot false boundaries 維持 0 / false。", + }, + { + "rule_id": "dispatch_received_accepted_separation", + "priority": "P0", + "rule": "request ready、request sent、response received、response accepted、runtime approval 必須是五個獨立狀態。", + "verification": "summary counters 必須分離,且 runtime gate 永遠等待獨立批准。", + }, + { + "rule_id": "parallel_session_basis_refresh", + "priority": "P0", + "rule": "每次 commit / push 前必須 fetch gitea,並同步另一 Session 的 run / deploy / production evidence。", + "verification": "LOGBOOK 必須記錄 basis、runs、deploy marker 與 gate 邊界。", + }, + { + "rule_id": "memory_index_startup_exception", + "priority": "P1", + "rule": "repo-local MEMORY.md 缺檔時,需記錄使用全域 memory 與 LOGBOOK 的替代讀取路徑。", + "verification": "啟動記錄不得假稱 repo-local MEMORY.md 已讀。", + }, +] + +RULE_ADJUSTMENTS_REQUIRED = [ + { + "adjustment_id": "low_friction_but_p0_stop_the_bleed", + "priority": "P0", + "from_rule": "初期資安低摩擦、先只讀框架。", + "adjusted_rule": "低摩擦不代表延後修補即時資訊揭露;public surface leak 可先 source-control 止血,再補 owner gate。", + }, + { + "adjustment_id": "internal_evidence_not_product_copy", + "priority": "P0", + "from_rule": "source-control evidence 可保留技術識別。", + "adjusted_rule": "技術識別只能留在內部 evidence;產品頁只顯示 redacted scope id、風險等級、狀態與 evidence ref。", + }, + { + "adjustment_id": "awooop_approval_not_security_acceptance", + "priority": "P0", + "from_rule": "AwoooP 可顯示 owner response / approval 候選。", + "adjusted_rule": "AwoooP approval、Runs、Work Items、Tenants 顯示全部只算狀態,不等於 IwoooS security acceptance。", + }, + { + "adjustment_id": "nginx_config_control_first", + "priority": "P0", + "from_rule": "高價值配置逐步納管。", + "adjusted_rule": "Nginx / public gateway / DNS / TLS 是 C0,優先要求 source-of-truth、drift evidence、owner response、rollback 與 post-check。", + }, + { + "adjustment_id": "owner_response_language_not_execution", + "priority": "P0", + "from_rule": "owner 可回覆 decision。", + "adjusted_rule": "即使 owner 文字包含同意、批准、OK,也只算 metadata decision,不自動授權 refs sync、reload、deploy 或 runtime action。", + }, + { + "adjustment_id": "public_verification_required_after_frontend_change", + "priority": "P0", + "from_rule": "改前端後做 smoke。", + "adjusted_rule": "改前端或 public API 後必須做 production desktop/mobile sensitive scan、水平溢出檢查與關鍵卡片可見檢查。", + }, + { + "adjustment_id": "agent_bounty_c0_runtime_boundary", + "priority": "P1", + "from_rule": "agent-bounty-protocol 納入 IwoooS scope。", + "adjusted_rule": "agent-bounty-protocol 需以 C0 runtime / MCP / A2A / treasury 邊界管理,但保持獨立產品與 owner response。", + }, +] + +PRIORITY_WORK_QUEUE = [ + { + "priority": "P0-1", + "work_item": "S4.9 owner response gate 收件前置", + "completion_percent": 70, + "blocked_by": "尚未人工送件、尚未收到 owner response。", + "next_validation": "source-control-owner-response-guard。", + }, + { + "priority": "P0-2", + "work_item": "前台 / public API identity redaction", + "completion_percent": 100, + "blocked_by": "後續新增頁面仍需 guard 持續保護。", + "next_validation": "production desktop/mobile sensitive scan。", + }, + { + "priority": "P0-3", + "work_item": "Nginx public gateway owner response acceptance", + "completion_percent": 86, + "blocked_by": "尚缺 owner-provided live conf、rendered diff、nginx -t evidence、route smoke、maintenance window、rollback owner。", + "next_validation": "public gateway acceptance snapshot + owner-provided evidence review。", + }, + { + "priority": "P0-4", + "work_item": "K8s / ArgoCD owner response acceptance", + "completion_percent": 62, + "blocked_by": "尚缺 ArgoCD health readback、rendered manifest diff、rollback revision、owner response。", + "next_validation": "k8s-argocd owner response acceptance snapshot。", + }, + { + "priority": "P0-5", + "work_item": "Backup / restore / escrow owner response acceptance", + "completion_percent": 62, + "blocked_by": "尚缺 restore drill approval package、offsite owner、escrow owner、retention owner、validation plan。", + "next_validation": "backup restore acceptance snapshot。", + }, + { + "priority": "P1-1", + "work_item": "Docker Compose / systemd / host service owner response", + "completion_percent": 50, + "blocked_by": "尚缺 110 / 188 live hash、restart window、rollback owner、post-check 指標。", + "next_validation": "host service owner request / future acceptance ledger。", + }, + { + "priority": "P1-2", + "work_item": "SSH / firewall / network access owner response acceptance", + "completion_percent": 58, + "blocked_by": "尚缺 live access state、allowed source CIDR、host key pinning、firewall owner、NetworkPolicy / NodePort / WireGuard owner。", + "next_validation": "ssh network acceptance snapshot。", + }, + { + "priority": "P1-3", + "work_item": "Monitoring / alerting / observability owner response", + "completion_percent": 62, + "blocked_by": "尚缺 live drift evidence、receiver owner、reload owner、route smoke、receipt proof。", + "next_validation": "monitoring owner request / future acceptance ledger。", + }, + { + "priority": "P1-4", + "work_item": "agent-bounty-protocol C0 runtime / MCP / A2A / treasury owner response", + "completion_percent": 68, + "blocked_by": "尚缺 deployment boundary、external agent boundary、treasury owner、runtime gate owner。", + "next_validation": "agent bounty owner request draft 與 future acceptance ledger。", + }, +] + + +def git_output(root: Path, args: list[str], fallback: str = "unknown") -> str: + try: + result = subprocess.run(args, cwd=root, check=True, capture_output=True, text=True) + return result.stdout.strip() + except Exception: + return fallback + + +def load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def build_report(root: Path, generated_at: str | None) -> dict[str, Any]: + security_dir = root / "docs" / "security" + report_time = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds") + rollup = load_json(security_dir / "source-control-owner-response-validation-rollup.snapshot.json") + s49_response = load_json(security_dir / "gitea-inventory-owner-attestation-response.snapshot.json") + tenants_probe_fields = [ + "source_scope_id", + "source_namespace_redacted", + "repo_owner_namespace_redacted", + "raw_repository_namespace_visible", + "public_api_raw_repo_namespace_allowed", + ] + + return { + "schema_version": "s4_9_owner_response_gap_audit_v1", + "generated_at": report_time, + "git_commit": git_output(root, ["git", "rev-parse", "--short", "HEAD"]), + "status": "gap_audit_ready_owner_gate_zero", + "mode": "read_only_gap_audit_no_runtime_action", + "basis": { + "gitea_main_commit": git_output(root, ["git", "rev-parse", "--short", "gitea/main"]), + "latest_runtime_deploy_marker": "605fde43", + "latest_tenants_redaction_commit": "4bbc5269", + "latest_logbook_commit": "8795c08d", + "source_control_rollup_date": rollup.get("date"), + "source_control_rollup_templates": rollup.get("summary", {}).get("total_response_template_count"), + "production_verification_required_for_frontend_changes": True, + }, + "summary": { + "current_requirement_gap_count": len(CURRENT_REQUIREMENT_GAPS), + "active_blocker_count": sum(1 for item in CURRENT_REQUIREMENT_GAPS if item["status"] == "active_blocker"), + "active_risk_or_process_gap_count": sum( + 1 for item in CURRENT_REQUIREMENT_GAPS if item["status"] in {"active_risk", "active_process_gap"} + ), + "mitigated_needs_guard_count": sum( + 1 for item in CURRENT_REQUIREMENT_GAPS if item["status"] == "mitigated_needs_guard" + ), + "new_rule_count": len(NEW_RULES_REQUIRED), + "rule_adjustment_count": len(RULE_ADJUSTMENTS_REQUIRED), + "priority_work_item_count": len(PRIORITY_WORK_QUEUE), + "s4_9_owner_response_gate_percent": 0, + "request_sent_count": 0, + "owner_response_received_count": s49_response.get("summary", {}).get("received_response_count", 0), + "owner_response_accepted_count": s49_response.get("summary", {}).get("accepted_response_count", 0), + "owner_response_rejected_count": s49_response.get("summary", {}).get("rejected_response_count", 0), + "runtime_gate_count": 0, + "public_surface_redaction_guard_ready": True, + "public_surface_raw_namespace_allowed": False, + "work_session_transcript_public_allowed": False, + }, + "false_boundaries": FALSE_BOUNDARIES, + "public_surface_redaction_requirements": { + "allowed_display": [ + "SRC-### scope id", + "aggregate count", + "risk tier", + "readiness state", + "redacted evidence ref", + ], + "forbidden_display": [ + "raw repository owner namespace", + "personal namespace", + "external organization namespace when not explicitly public-safe", + "工作視窗對話內容", + "approval chat phrase", + "token / secret / private key / cookie / authorization header", + ], + "required_fields_or_markers": tenants_probe_fields, + "verification": [ + "public API payload sensitive scan", + "production HTML sensitive scan", + "desktop browser sensitive scan", + "mobile browser sensitive scan", + "horizontal overflow check", + ], + }, + "current_requirement_gaps": CURRENT_REQUIREMENT_GAPS, + "new_rules_required": NEW_RULES_REQUIRED, + "rule_adjustments_required": RULE_ADJUSTMENTS_REQUIRED, + "priority_work_queue": PRIORITY_WORK_QUEUE, + "next_safe_actions": [ + "更新 S4.9 缺口稽核文件基準與 snapshot reference。", + "把本 snapshot 納入 source-control owner response guard。", + "繼續補 owner response acceptance ledger,但所有 request / received / accepted / runtime gate 維持 0 / false。", + ], + } + + +def main() -> int: + parser = argparse.ArgumentParser(description="S4.9 owner response gate 現況缺口稽核 snapshot 產生器") + parser.add_argument("--root", default=".", help="repo root") + parser.add_argument("--output", help="寫出 JSON 報告") + parser.add_argument("--generated-at", help="固定報告時間,供 committed snapshot 使用") + args = parser.parse_args() + + root = Path(args.root).resolve() + report = build_report(root, args.generated_at) + payload = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + if args.output: + output = root / 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( + "S4_9_OWNER_RESPONSE_GAP_AUDIT_OK " + f"gaps={summary['current_requirement_gap_count']} " + f"new_rules={summary['new_rule_count']} " + f"adjustments={summary['rule_adjustment_count']} " + f"owner_gate={summary['s4_9_owner_response_gate_percent']} " + f"runtime_gate={summary['runtime_gate_count']}", + file=sys.stderr, + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/security/source-control-owner-response-guard.py b/scripts/security/source-control-owner-response-guard.py index c569b60e..a4c5e8aa 100755 --- a/scripts/security/source-control-owner-response-guard.py +++ b/scripts/security/source-control-owner-response-guard.py @@ -16,6 +16,52 @@ from typing import Any EXPECTED_ROLLUP_DATE = "2026-06-13" EXPECTED_TEMPLATE_COUNT_FORMULA = "5 + 9 + 5 + 5 = 24" STALE_TEMPLATE_COUNT_FORMULA = "5 + 7 + 5 + 5 = 22" +EXPECTED_GAP_AUDIT_GITEA_MAIN = "8795c08d" +EXPECTED_GAP_AUDIT_DEPLOY_MARKER = "605fde43" +EXPECTED_GAP_AUDIT_TENANTS_REDACTION_COMMIT = "4bbc5269" + +EXPECTED_GAP_AUDIT_GAP_IDS = [ + "s49_owner_response_absent", + "s49_dispatch_audit_event_absent", + "s49_reviewer_outcome_absent", + "public_surface_identity_leak_risk", + "raw_namespace_internal_evidence_misroute_risk", + "latest_basis_staleness_risk", + "parallel_session_conflict_risk", + "release_worktree_memory_index_absent", +] + +EXPECTED_GAP_AUDIT_NEW_RULE_IDS = [ + "public_surface_redaction_gate", + "s49_gap_audit_snapshot_required", + "raw_evidence_private_boundary", + "owner_response_counts_are_runtime_locked", + "dispatch_received_accepted_separation", + "parallel_session_basis_refresh", + "memory_index_startup_exception", +] + +EXPECTED_GAP_AUDIT_ADJUSTMENT_IDS = [ + "low_friction_but_p0_stop_the_bleed", + "internal_evidence_not_product_copy", + "awooop_approval_not_security_acceptance", + "nginx_config_control_first", + "owner_response_language_not_execution", + "public_verification_required_after_frontend_change", + "agent_bounty_c0_runtime_boundary", +] + +EXPECTED_GAP_AUDIT_WORK_PRIORITIES = [ + "P0-1", + "P0-2", + "P0-3", + "P0-4", + "P0-5", + "P1-1", + "P1-2", + "P1-3", + "P1-4", +] LANES = [ { @@ -509,17 +555,98 @@ def validate_markdown_consistency(security_dir: Path) -> None: assert_not_contains("rollup_doc.stale_formula", rollup_doc, STALE_TEMPLATE_COUNT_FORMULA) assert_not_contains("rollup_doc.stale_display", rollup_doc, "22 templates") assert_contains("gap_audit_doc.latest_baseline_present", gap_audit_doc, "gitea/main=") + assert_contains("gap_audit_doc.latest_baseline_8795", gap_audit_doc, EXPECTED_GAP_AUDIT_GITEA_MAIN) + assert_contains("gap_audit_doc.latest_deploy_marker", gap_audit_doc, EXPECTED_GAP_AUDIT_DEPLOY_MARKER) + assert_contains("gap_audit_doc.machine_readable_snapshot", gap_audit_doc, "s4-9-owner-response-gap-audit.snapshot.json") + assert_contains("gap_audit_doc.public_surface_redaction", gap_audit_doc, "Public surface redaction") assert_not_contains("gap_audit_doc.stale_baseline_b615", gap_audit_doc, "b615bde5") assert_not_contains("gap_audit_doc.stale_baseline_f1bad", gap_audit_doc, "f1bad81d") assert_not_contains("gap_audit_doc.stale_baseline_b17a", gap_audit_doc, "b17a28c2") + assert_not_contains("gap_audit_doc.stale_baseline_2afb", gap_audit_doc, "2afb7c0a") assert_contains("gap_audit_doc.rollup_consistency", gap_audit_doc, "S4.13 rollup 文件一致性") def validate(root: Path) -> None: security_dir = root / "docs" / "security" validate_markdown_consistency(security_dir) + gap_audit = load_json(security_dir / "s4-9-owner-response-gap-audit.snapshot.json") rollup = load_json(security_dir / "source-control-owner-response-validation-rollup.snapshot.json") rollup_summary = rollup["summary"] + gap_summary = gap_audit["summary"] + + assert_equal("gap_audit.schema_version", gap_audit["schema_version"], "s4_9_owner_response_gap_audit_v1") + assert_equal("gap_audit.status", gap_audit["status"], "gap_audit_ready_owner_gate_zero") + assert_equal("gap_audit.mode", gap_audit["mode"], "read_only_gap_audit_no_runtime_action") + assert_equal("gap_audit.basis.gitea_main_commit", gap_audit["basis"]["gitea_main_commit"], EXPECTED_GAP_AUDIT_GITEA_MAIN) + assert_equal( + "gap_audit.basis.latest_runtime_deploy_marker", + gap_audit["basis"]["latest_runtime_deploy_marker"], + EXPECTED_GAP_AUDIT_DEPLOY_MARKER, + ) + assert_equal( + "gap_audit.basis.latest_tenants_redaction_commit", + gap_audit["basis"]["latest_tenants_redaction_commit"], + EXPECTED_GAP_AUDIT_TENANTS_REDACTION_COMMIT, + ) + assert_equal("gap_audit.basis.source_control_rollup_templates", gap_audit["basis"]["source_control_rollup_templates"], 24) + assert_true( + "gap_audit.basis.production_verification_required_for_frontend_changes", + gap_audit["basis"]["production_verification_required_for_frontend_changes"], + ) + assert_equal("gap_audit.current_requirement_gap_count", gap_summary["current_requirement_gap_count"], 8) + assert_equal("gap_audit.active_blocker_count", gap_summary["active_blocker_count"], 3) + assert_equal("gap_audit.new_rule_count", gap_summary["new_rule_count"], len(EXPECTED_GAP_AUDIT_NEW_RULE_IDS)) + assert_equal( + "gap_audit.rule_adjustment_count", + gap_summary["rule_adjustment_count"], + len(EXPECTED_GAP_AUDIT_ADJUSTMENT_IDS), + ) + assert_equal( + "gap_audit.priority_work_item_count", + gap_summary["priority_work_item_count"], + len(EXPECTED_GAP_AUDIT_WORK_PRIORITIES), + ) + assert_equal("gap_audit.s4_9_owner_response_gate_percent", gap_summary["s4_9_owner_response_gate_percent"], 0) + assert_equal("gap_audit.request_sent_count", gap_summary["request_sent_count"], 0) + assert_equal("gap_audit.owner_response_received_count", gap_summary["owner_response_received_count"], 0) + assert_equal("gap_audit.owner_response_accepted_count", gap_summary["owner_response_accepted_count"], 0) + assert_equal("gap_audit.owner_response_rejected_count", gap_summary["owner_response_rejected_count"], 0) + assert_equal("gap_audit.runtime_gate_count", gap_summary["runtime_gate_count"], 0) + assert_true("gap_audit.public_surface_redaction_guard_ready", gap_summary["public_surface_redaction_guard_ready"]) + assert_false("gap_audit.public_surface_raw_namespace_allowed", gap_summary["public_surface_raw_namespace_allowed"]) + assert_false( + "gap_audit.work_session_transcript_public_allowed", + gap_summary["work_session_transcript_public_allowed"], + ) + assert_equal( + "gap_audit.gap_ids", + [item["gap_id"] for item in gap_audit["current_requirement_gaps"]], + EXPECTED_GAP_AUDIT_GAP_IDS, + ) + assert_equal( + "gap_audit.new_rule_ids", + [item["rule_id"] for item in gap_audit["new_rules_required"]], + EXPECTED_GAP_AUDIT_NEW_RULE_IDS, + ) + assert_equal( + "gap_audit.adjustment_ids", + [item["adjustment_id"] for item in gap_audit["rule_adjustments_required"]], + EXPECTED_GAP_AUDIT_ADJUSTMENT_IDS, + ) + assert_equal( + "gap_audit.priority_work_queue", + [item["priority"] for item in gap_audit["priority_work_queue"]], + EXPECTED_GAP_AUDIT_WORK_PRIORITIES, + ) + for key, value in gap_audit["false_boundaries"].items(): + assert_false(f"gap_audit.false_boundaries.{key}", value) + redaction = gap_audit["public_surface_redaction_requirements"] + for marker in ["source_scope_id", "source_namespace_redacted", "public_api_raw_repo_namespace_allowed"]: + if marker not in redaction["required_fields_or_markers"]: + raise SystemExit(f"BLOCKED gap_audit.public_surface_redaction_requirements: missing {marker!r}") + for forbidden in ["raw repository owner namespace", "工作視窗對話內容", "approval chat phrase"]: + if forbidden not in redaction["forbidden_display"]: + raise SystemExit(f"BLOCKED gap_audit.public_surface_redaction_forbidden_display: missing {forbidden!r}") assert_equal("rollup.status", rollup["status"], "draft_waiting_owner_responses") assert_equal("rollup.date", rollup["date"], EXPECTED_ROLLUP_DATE)