From c35f064d2a38887022a0d40603501f373464a96a Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 15 Jun 2026 07:01:58 +0800 Subject: [PATCH] =?UTF-8?q?test(iwooos):=20=E6=96=B0=E5=A2=9E=E4=BE=9B?= =?UTF-8?q?=E6=87=89=E9=8F=88=20owner=20policy=20gate=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IWOOOS-CONFIG-CONTROL-INVENTORY.md | 4 +- .../security/PACKAGE-SUPPLY-CHAIN-BASELINE.md | 44 ++- .../PACKAGE-SUPPLY-CHAIN-OWNER-POLICY-GATE.md | 99 ++++++ .../SECURITY-SUPPLY-CHAIN-PROGRESS.md | 4 +- ...pply-chain-owner-policy-gate.snapshot.json | 310 ++++++++++++++++++ .../security-mirror-dry-run.snapshot.json | 33 +- ...package-supply-chain-owner-policy-guard.py | 211 ++++++++++++ .../security-mirror-progress-guard.py | 11 +- 8 files changed, 703 insertions(+), 13 deletions(-) create mode 100644 docs/security/PACKAGE-SUPPLY-CHAIN-OWNER-POLICY-GATE.md create mode 100644 docs/security/package-supply-chain-owner-policy-gate.snapshot.json create mode 100644 scripts/security/package-supply-chain-owner-policy-guard.py diff --git a/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md b/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md index 5db7d62c..c4e04094 100644 --- a/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md +++ b/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md @@ -193,7 +193,7 @@ Nginx 是目前必須最先資安控管的配置,原因是它同時控制公 | P2 | AWOOOI / AwoooP / IwoooS frontend runtime config | `apps/web/next.config.js`、`apps/web/src/lib/config.ts`、i18n | web owner | NEXT_PUBLIC public-domain only、no internal transcript, desktop/mobile smoke | | P2 | VibeWork product boundary | VibeWork owner docs / future evidence refs | VibeWork owner | independent product boundary、repo / deploy / admin / backup scope | | P2 | StockPlatform / Tsenyang / Bitan / VTuber routes | Nginx templates、product runbooks | product owner | domain / admin / API / backup / owner matrix | -| P2 | Package / supply-chain baselines | `pnpm-lock.yaml`、`package.json`、`pyproject.toml`、`requirements.txt`、Dockerfiles、docker-compose、`docs/security/PACKAGE-SUPPLY-CHAIN-BASELINE.md`、`docs/security/package-supply-chain-baseline.snapshot.json` | repo / registry owner | package manager policy、lockfile owner、Python lock policy、CVE / license / SBOM window、image digest evidence、registry owner、rollback owner | +| P2 | Package / supply-chain baselines | `pnpm-lock.yaml`、`package.json`、`pyproject.toml`、`requirements.txt`、Dockerfiles、docker-compose、`docs/security/PACKAGE-SUPPLY-CHAIN-BASELINE.md`、`docs/security/package-supply-chain-baseline.snapshot.json`、`docs/security/PACKAGE-SUPPLY-CHAIN-OWNER-POLICY-GATE.md`、`docs/security/package-supply-chain-owner-policy-gate.snapshot.json` | repo / registry owner | package manager policy、lockfile owner、Python lock policy、requirements pinning policy、Docker digest pinning policy、compose image digest policy、CVE / license / SBOM window、registry owner、rollback owner | | P3 | Runbook / endpoint docs / snapshots | `docs/reference/*`、`docs/runbooks/*`、`docs/security/*.snapshot.json` | doc owner | no secret value, stale endpoint flag, owner-reviewed evidence refs | 2026-06-14 P0-20 已新增 `docs/security/K8S-ARGOCD-MANIFEST-INVENTORY.md` 與 `docs/security/k8s-argocd-manifest-inventory.snapshot.json`,把 K8s / ArgoCD / Velero / monitoring repo source 固定為 `files=49`、`c0=36`、`yaml=45`、`unique_kinds=20`、`blocked_actions=13` 的只讀清冊。P0-21 再新增 `docs/security/K8S-ARGOCD-OWNER-REQUEST-DRAFT.md` 與 `docs/security/k8s-argocd-owner-request-draft.snapshot.json`,將四個 scan group 轉成 `drafts=4`、`c0=3`、`owner_fields=11` 的 owner request draft。2026-06-15 P0-25 再新增 `docs/security/K8S-ARGOCD-OWNER-RESPONSE-ACCEPTANCE.md` 與 `docs/security/k8s-argocd-owner-response-acceptance.snapshot.json`,固定 `candidates=4`、`c0=3`、`owner_fields=11`、`reviewer_checks=12`、`outcome_lanes=7`、`blocked_actions=18` 的 owner response acceptance 只讀帳本。2026-06-15 再新增 `docs/security/K8S-ARGOCD-CHANGE-EVIDENCE-ACCEPTANCE.md` 與 `docs/security/k8s-argocd-change-evidence-acceptance.snapshot.json`,固定 `candidates=4`、`c0=3`、`write_capable=4`、`required_evidence_fields=18`、`reviewer_checks=18`、`outcome_lanes=8`、`blocked_actions=28` 的 GitOps 變更證據驗收只讀帳本。這些都不是 live cluster read、ArgoCD API read、ArgoCD sync、kubectl action、Helm upgrade、secret collection、manual pod restart、scale workload、RBAC / NetworkPolicy change、restore backup、production write 或 runtime gate。 @@ -211,6 +211,7 @@ Nginx 是目前必須最先資安控管的配置,原因是它同時控制公 9. DNS / TLS / certbot owner response 只能收脫敏 metadata ref、coverage basis、expiry metadata、renewal owner、ACME route owner、maintenance window、rollback owner 與 validation plan;不得因 owner 回覆而自動做 DNS query、live TLS probe、certbot renew、Nginx reload、route smoke、DNS record 變更、certificate path 變更或 ACME route 變更。 10. Public / admin / API / frontend runtime config 變更必須先通過 affected route、auth boundary、API readback、CORS diff、frontend env diff、i18n redaction、desktop / mobile smoke、sensitive string scan、rollback owner 與 post-check evidence;前台不得顯示 raw owner namespace、repo slug、內部狀態碼、內部協作內容或未脫敏截圖。 11. 高價值配置控管必須能由 `scripts/security/iwooos-config-control-guard.py` 集中驗證;guard 通過只代表 repo snapshot 基線完整,不代表 owner response、live evidence、reload、restart、workflow / secret / runner 變更、backup / restore、scan、runtime 或 deploy 授權。 +12. Package / Docker 供應鏈修復前必須先通過 owner policy gate;Python lockfile、requirements pinning、Docker digest pinning、compose digest、CVE / license / SBOM 只能先收脫敏 owner metadata,不得因 baseline 或 gate 通過而 install、upgrade、rewrite lockfile、pull / build / push image、登入 registry、修改 workflow、部署或開 runtime gate。 ## 5. 需要調整的既有規範 @@ -255,6 +256,7 @@ Nginx 是目前必須最先資安控管的配置,原因是它同時控制公 | K8s / ArgoCD GitOps 變更證據驗收 | `100%` | 已新增 `k8s_argocd_change_evidence_acceptance_v1`,4 個 candidate、3 個 C0、4 個 write-capable、18 個 reviewer checks、8 條 outcome lanes、28 類 blocked action;成熟度 `62% -> 64%` | | Public / Admin / API runtime config 變更證據驗收 | `100%` | 已新增 `public_runtime_config_change_evidence_acceptance_v1`,6 個 candidate、5 個 C0、21 個 reviewer checks、8 條 outcome lanes、32 類 blocked action;成熟度 `62% -> 64%`;raw namespace / repo slug / 內部狀態碼 / 內部協作內容外洩列為拒收或隔離條件 | | Package / Docker supply-chain repo-only baseline | `100%` | 已新增 `package_supply_chain_baseline_v1`,盤點 `package_json=6`、`pyproject=4`、`requirements=2`、`dockerfiles=2`、`compose=6`、`gaps=5`;不 install、不掃 CVE、不改 image、不部署 | +| Package / Docker supply-chain owner policy gate | `100%` | 已新增 `package_supply_chain_owner_policy_gate_v1`,6 個 owner policy request、2 個 C0、8 個 owner 欄位、12 個 reviewer checks、20 類 blocked action;request_sent / received / accepted / runtime / action 仍為 `0 / false` | | Backup / restore / escrow owner request draft | `100%` | 已將 38 個 backup / restore / escrow surface 轉成 owner request draft;request sent / received / accepted、backup run、restore run、offsite sync、remote delete、escrow marker write、retention change 仍為 0 | | CI blocking / workflow gate | `0%` | 本階段刻意不修改 `.gitea/workflows`,避免初期資安流程摩擦過大 | | owner-provided live Nginx file compare | `70%` | 工具可吃 owner 匯出的 live conf 檔比較;本階段不主動 SSH 取得 | diff --git a/docs/security/PACKAGE-SUPPLY-CHAIN-BASELINE.md b/docs/security/PACKAGE-SUPPLY-CHAIN-BASELINE.md index af13eeea..ca848a72 100644 --- a/docs/security/PACKAGE-SUPPLY-CHAIN-BASELINE.md +++ b/docs/security/PACKAGE-SUPPLY-CHAIN-BASELINE.md @@ -6,6 +6,7 @@ | 狀態 | `repo_only_inventory_ready_needs_owner_policy` | | 腳本 | `scripts/security/package-supply-chain-baseline.py` | | Snapshot | `docs/security/package-supply-chain-baseline.snapshot.json` | +| Owner policy gate | `docs/security/PACKAGE-SUPPLY-CHAIN-OWNER-POLICY-GATE.md` / `docs/security/package-supply-chain-owner-policy-gate.snapshot.json` | | Schema | `docs/schemas/package_supply_chain_baseline_v1.schema.json` | | 模式 | repo snapshot only,不 install、不連外、不做 CVE scan、不改 image | | runtime gate | `0` | @@ -54,7 +55,22 @@ 7. `cve_scan_window` 8. `rollback_owner` -## 5. 指令 +## 5. Owner Policy Gate + +2026-06-15 已新增 `docs/security/PACKAGE-SUPPLY-CHAIN-OWNER-POLICY-GATE.md` 與 `docs/security/package-supply-chain-owner-policy-gate.snapshot.json`,把 baseline 缺口轉成六個 owner policy request: + +| Request | 對應治理項 | 狀態 | +|---------|------------|------| +| package manager / lockfile owner | Node / pnpm lockfile owner 與更新窗口 | waiting owner policy response | +| Python lockfile policy | Python lockfile 缺席 | waiting owner policy response | +| requirements pinning policy | `requirements.txt` 未 pin | waiting owner policy response | +| Docker digest pinning policy | Dockerfile base image 與 `COPY --from` image 未 digest pin | waiting owner policy response | +| compose image digest policy | docker-compose image 未 digest pin | waiting owner policy response | +| CVE / license / SBOM window | 掃描工具、窗口與噪音處理策略未定 | waiting owner policy response | + +此 gate 只補「誰能決定、用什麼政策決定、何時驗證、誰負責 rollback」的收件前規範。request_sent、owner_response_received、owner_response_accepted、runtime_gate 與 action_button 仍全部是 `0 / false`。 + +## 6. 指令 ```bash python3 scripts/security/package-supply-chain-baseline.py \ @@ -77,7 +93,19 @@ python3 scripts/security/package-supply-chain-baseline.py \ PACKAGE_SUPPLY_CHAIN_BASELINE_OK package_json=6 pyproject=4 requirements=2 dockerfiles=2 compose=6 gaps=5 runtime_gate=0 ``` -## 6. 邊界 +Owner policy gate 驗證: + +```bash +python3 scripts/security/package-supply-chain-owner-policy-guard.py --root . +``` + +預期輸出: + +```text +PACKAGE_SUPPLY_CHAIN_OWNER_POLICY_GUARD_OK +``` + +## 7. 邊界 此 baseline 通過不代表: @@ -87,13 +115,15 @@ PACKAGE_SUPPLY_CHAIN_BASELINE_OK package_json=6 pyproject=4 requirements=2 docke - registry login、Harbor policy、image immutability 或 scanner policy 已驗收。 - workflow、runner、secret、production deploy 或 runtime gate 已授權。 -## 7. 完成度 +## 8. 完成度 | 工作 | 完成度 | 說明 | |------|--------|------| | Package / Docker supply-chain repo-only baseline | `100%` | 已新增腳本、snapshot 與人讀文件 | -| Node lockfile 基線 | `80%` | `pnpm-lock.yaml` 存在;仍需 owner policy 確認 lockfile owner / update window | -| Python lock policy | `30%` | 已盤點 pyproject / requirements;尚缺 owner policy 與 lockfile 決策 | -| Docker / compose image policy | `35%` | 已盤點 image refs;尚缺 digest pinning policy、registry owner、rollback owner | -| CVE / license / SBOM 驗證 | `0%` | 未執行外部掃描;需 owner window 與工具策略 | +| Package / Docker supply-chain owner policy gate | `100%` | 已新增 guard、snapshot 與人讀文件;六個 request 仍 waiting owner policy response | +| Node lockfile 基線 | `80%` | `pnpm-lock.yaml` 存在;owner policy gate 已補,但尚未收到 lockfile owner / update window | +| Python lock policy | `45%` | 已盤點 pyproject / requirements 並補 owner policy request;尚缺正式 owner response 與 lockfile 決策 | +| requirements pinning policy | `35%` | 已盤點 26 條未 pin entry 並補 owner policy request;尚未批准 pinning 或相容性窗口 | +| Docker / compose image policy | `45%` | 已盤點 image refs 並補 C0 owner policy request;尚缺 digest pinning policy、registry owner、rollback owner | +| CVE / license / SBOM 驗證 | `15%` | 已補 owner policy request;未執行外部掃描,需 owner window 與工具策略 | | runtime gate | `0%` | 未開啟任何執行期閘門 | diff --git a/docs/security/PACKAGE-SUPPLY-CHAIN-OWNER-POLICY-GATE.md b/docs/security/PACKAGE-SUPPLY-CHAIN-OWNER-POLICY-GATE.md new file mode 100644 index 00000000..bcdbe955 --- /dev/null +++ b/docs/security/PACKAGE-SUPPLY-CHAIN-OWNER-POLICY-GATE.md @@ -0,0 +1,99 @@ +# Package / Docker 供應鏈 Owner Policy Gate + +| 項目 | 內容 | +|------|------| +| 日期 | 2026-06-15 | +| 狀態 | `draft_waiting_owner_policy_response` | +| 腳本 | `scripts/security/package-supply-chain-owner-policy-guard.py` | +| Snapshot | `docs/security/package-supply-chain-owner-policy-gate.snapshot.json` | +| Baseline | `docs/security/package-supply-chain-baseline.snapshot.json` | +| 模式 | repo snapshot policy gate,不 install、不 upgrade、不 pull image、不 build image、不 push image | +| runtime gate | `0` | + +## 1. 目的 + +此 owner policy gate 把 Package / Docker 供應鏈 baseline 的五個缺口轉成六個 owner policy request 草稿,讓後續處理 Python lockfile、requirements pinning、Docker digest pinning、compose image digest、CVE / license / SBOM 時,都必須先有 owner role / team、policy decision、maintenance window、rollback owner 與 redacted evidence refs。 + +本 gate 是只讀治理物件,不是套件安裝、升級、pinning、CVE 掃描、SBOM 產生、image pull / build / push、registry login、workflow 修改、production deploy 或 runtime gate。 + +## 2. Owner Policy Request + +| Request ID | 分級 | 對應缺口 | 目前狀態 | +|------------|------|----------|----------| +| `supply_chain_owner_policy:package_manager_lockfile_owner` | C1 | Node / pnpm lockfile owner 與更新窗口 | waiting owner policy response | +| `supply_chain_owner_policy:python_lockfile_policy` | C1 | `python_lockfile_absent` | waiting owner policy response | +| `supply_chain_owner_policy:requirements_pin_policy` | C1 | `requirements_unpinned_entries_present` | waiting owner policy response | +| `supply_chain_owner_policy:dockerfile_digest_pin_policy` | C0 | `docker_base_images_not_all_digest_pinned`、`docker_copy_from_images_not_all_digest_pinned` | waiting owner policy response | +| `supply_chain_owner_policy:compose_image_digest_pin_policy` | C0 | `compose_images_not_all_digest_pinned` | waiting owner policy response | +| `supply_chain_owner_policy:cve_license_sbom_window` | C1 | CVE / license / SBOM tool 與執行窗口未定 | waiting owner policy response | + +## 3. Required Owner Fields + +1. `package_manager_policy` +2. `lockfile_owner` +3. `python_lock_policy` +4. `docker_base_image_policy` +5. `compose_image_policy` +6. `registry_owner` +7. `cve_scan_window` +8. `rollback_owner` + +## 4. Reviewer Checks + +1. owner role / team 必須可追溯,不能只填個人暱稱或未脫敏 repo namespace。 +2. policy decision 必須明確區分 observe、plan、approve change、reject change。 +3. Python lockfile 決策必須說明工具、產生位置與相容性驗證方式。 +4. requirements pinning 決策必須說明 pinning 原則、例外條件與 rollback owner。 +5. Docker digest pinning 決策必須說明 base image、`COPY --from` image、registry owner 與回復策略。 +6. compose image digest 決策必須說明 service 範圍、tag / digest 對照與維護窗口。 +7. CVE / license / SBOM 決策必須說明工具、掃描窗口、結果分級與噪音處理方式。 +8. evidence refs 只能是脫敏 metadata,不得包含 token、cookie、registry password、private key 或 secret value。 +9. change evidence 必須能回連到 baseline gap,不得只提供口頭批准。 +10. runtime gate 必須獨立批准;owner policy accepted 不能自動變成 runtime execution authorized。 +11. deploy / workflow / registry / image write 必須另有變更證據與 post-check。 +12. 前台呈現只能顯示脫敏產品 / 控管狀態,不得顯示 raw namespace、內部協作內容或工作視窗對話。 + +## 5. Blocked Actions + +本 gate 通過前,以下動作全部維持 blocked: + +1. install package +2. upgrade package +3. rewrite lockfile +4. change requirements pin +5. run external CVE lookup +6. run external license lookup +7. generate SBOM +8. docker pull +9. docker build +10. docker push +11. image tag change +12. image digest pin change +13. registry login +14. workflow modification +15. runner change +16. secret value collection +17. production deploy +18. runtime execution +19. action button enablement +20. treat owner policy as runtime approval + +## 6. 不可誤讀 + +- `package-supply-chain-baseline.snapshot.json` 只是 repo-only baseline。 +- `package-supply-chain-owner-policy-gate.snapshot.json` 只是 owner policy gate 草稿。 +- 所有 request_sent、owner_response_received、owner_response_accepted、runtime_execution_authorized、action_buttons_allowed 仍是 `0 / false`。 +- `owner policy gate` 通過只代表文件、snapshot 與 guard 對齊,不代表 Python lockfile、requirements pinning、Docker digest pinning、CVE / license / SBOM 已完成。 +- 本輪不 install、不 upgrade、不 pull image、不 build image、不 push image,不登入 registry、不改 workflow、不改 secret、不部署。 + +## 7. 完成度 + +| 工作 | 完成度 | 說明 | +|------|--------|------| +| Package / Docker supply-chain owner policy gate | `100%` | 已新增人讀文件、snapshot 與 guard,並可由總進度 guard 串接 | +| Node lockfile owner policy | `45%` | 已有 pnpm lockfile 與 owner policy request;尚未收到 owner response | +| Python lock policy | `45%` | 已有 Python lockfile 缺口與 owner policy request;尚未決定工具與 lockfile 策略 | +| requirements pinning policy | `35%` | 已有 26 條未 pin entry 的 policy request;尚未批准 pinning 或相容性窗口 | +| Docker / compose image policy | `45%` | 已有 C0 digest pinning policy request;尚未批准 digest 變更、registry owner 或 rollback | +| CVE / license / SBOM 驗證 | `15%` | 已定義 owner policy request;尚未批准外部掃描窗口或工具 | +| runtime gate | `0%` | 未開啟任何執行期閘門 | diff --git a/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md b/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md index 45875d28..0816dcc4 100644 --- a/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md +++ b/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md @@ -3,14 +3,14 @@ | 項目 | 內容 | |------|------| | 日期 | 2026-06-15 | -| 狀態 | IwoooS 64% 只讀治理推進中;高價值配置集中 guard 與 Package / Docker 供應鏈 repo-only baseline 已完成;CD / Runner / Secret 注入變更證據驗收與 Public / Admin / API runtime config 變更證據驗收只讀帳本已本地完成;端口 / 防火牆變更證據驗收與 K8s / ArgoCD GitOps 變更證據驗收已正式部署驗證;S4.9 owner response gate 仍是第一優先 | +| 狀態 | IwoooS 64% 只讀治理推進中;高價值配置集中 guard、Package / Docker 供應鏈 repo-only baseline 與 Package / Docker 供應鏈 owner policy gate 已完成;CD / Runner / Secret 注入變更證據驗收與 Public / Admin / API runtime config 變更證據驗收只讀帳本已本地完成;端口 / 防火牆變更證據驗收與 K8s / ArgoCD GitOps 變更證據驗收已正式部署驗證;S4.9 owner response gate 仍是第一優先 | | 本階段完成 | 資安供應鏈 contract manifest + Source Control Approval Board + Draft Reconcile Plan + Ref Detail Diff + Ref Truth Classification + Source Control Ref Truth Owner Response 收件包 + GitHub Primary Readiness Gate + GitHub Primary Rollback ADR + GitHub Target Owner Decision Response 收件包 + Gitea 認證清冊匯出請求 + Gitea 認證清冊匯入驗收契約 + Gitea 清冊覆蓋 Owner Attestation + Gitea Owner Attestation Approval Lane 對齊 + Gitea Owner Attestation Response 收件包 + Workflow / Secret Name Inventory + Workflow / Secret Name Local Evidence + Workflow / Secret Name Redacted Export Request + Workflow / Secret Name Owner Response 收件包 + Source Control Owner Response Validation Rollup + Kali 112 live integration status + Security Finding contract + Kali scan scope approval package + Security Approval Queue + S3 人工批准 Gate + S3 人工決策紀錄 + S3 人工審查封包 + S3 人工決策狀態轉移 + S3 後續 runtime gate 準備契約 + 鏡像 readiness index + 鏡像接收計畫 + 鏡像事件信封 + 鏡像路由矩陣 + 鏡像驗收契約 + 鏡像隔離契約 + 鏡像 dry-run 報告契約 + 鏡像狀態彙整契約 + IwoooS 前端態勢入口 + IwoooS posture projection contract + IwoooS 既有前端資安頁面整合 + IwoooS 覆蓋與邊界矩陣 + IwoooS 只讀資安處理旅程 + IwoooS owner evidence readiness board + IwoooS host coverage view + IwoooS host action gate matrix + IwoooS host evidence readiness board + IwoooS host evidence collection order + IwoooS host evidence intake preflight + IwoooS host evidence review outcome lanes + IwoooS host evidence review handoff packets + IwoooS host evidence reviewer checklist + IwoooS host evidence reviewer outcome lanes + IwoooS host owner decision candidate packets + IwoooS host owner decision review checklist + IwoooS host owner decision review outcome lanes + IwoooS host owner decision record draft packets + IwoooS host owner decision record draft review checklist + IwoooS host owner decision record draft review outcome lanes + IwoooS host owner decision record write-up packets + IwoooS host owner decision record write-up review checklist + IwoooS host owner decision record write-up review outcome lanes + IwoooS host owner decision record formal candidate packets + IwoooS host owner decision record formal candidate review checklist + IwoooS host owner decision record formal candidate review outcome lanes + IwoooS host owner decision record formal record queue packets + IwoooS host owner decision record formal record queue review checklist + IwoooS host owner decision record formal record queue review outcome lanes + IwoooS host owner decision record human handoff readiness packets + IwoooS host owner decision record human handoff readiness review checklist + IwoooS host owner decision record human handoff readiness review outcome lanes + IwoooS host owner decision record human record owner review candidate packets + IwoooS host owner decision record human record owner review candidate checklist + IwoooS host owner decision record human record owner review candidate outcome lanes + IwoooS host owner decision record human record owner review preparation packets + IwoooS host owner decision record human record owner review preparation checklist + IwoooS progress acceleration lanes + IwoooS owner response next-action focus + IwoooS S4.9 owner response preflight + IwoooS S4.9 owner response request templates + IwoooS progress hold movement gates + IwoooS AwoooP read-only landing readiness + IwoooS AwoooP cross-session handoff packets + AwoooP 首頁 IwoooS 資安鏡像候選 + AwoooP 工作鏈路 IwoooS 資安鏡像候選 + AwoooP 審批佇列 IwoooS owner response 只讀焦點 | | 本階段追加 | AwoooP 合約儀表板 IwoooS 資安契約只讀候選 + AwoooP 租戶管理 IwoooS 資安租戶範圍只讀候選 + AwoooP 執行監控 IwoooS 執行狀態只讀候選 + 既有安全 / 合規頁面 IwoooS 只讀反向橋接 + 告警 / 錯誤 / 授權 / 治理頁面 IwoooS 只讀反向橋接 + 稽核 / 工程審查頁面 IwoooS 深色只讀反向橋接 + IwoooS 前端資安頁面連接狀態板 + IwoooS GitHub 主要來源就緒度只讀狀態板 + AwoooP 工作鏈路 GitHub 主要來源就緒度只讀工作項 + AwoooP 合約儀表板 GitHub 主要來源就緒度合約只讀候選 + AwoooP 審批佇列 GitHub 主要來源就緒度審批邊界 + AwoooP 首頁 GitHub 主要來源就緒度只讀摘要 + AwoooP 租戶管理 GitHub 主要來源就緒度租戶範圍 + AwoooP 執行監控 GitHub 主要來源就緒度執行邊界 + IwoooS / AwoooP 資安可視區塊繁體中文呈現防護檢查 + AwoooP 執行詳情 / 審批詳情繁體中文呈現防護檢查 + AwoooP 首頁負責人回覆驗收總覽 + AwoooP 工作鏈路負責人回覆驗收只讀工作項 + AwoooP 合約儀表板負責人回覆驗收契約只讀候選 + AwoooP 審批佇列負責人回覆驗收只讀審查邊界 + AwoooP 租戶管理負責人回覆驗收租戶範圍 + AwoooP 執行監控負責人回覆驗收執行邊界 + AwoooP 執行詳情負責人回覆驗收詳情邊界 + AwoooP 審批決策負責人回覆驗收審批邊界 + IwoooS AwoooP 資安入口覆蓋狀態板 + IwoooS 階段式資安收斂節奏圖 + IwoooS 下一步人工收件作戰板 + IwoooS 人工回覆安全驗收閘道 + IwoooS 人工回覆審查結果分流 + IwoooS 人工決策準備佇列 + IwoooS 人工決策紀錄草稿防誤用 + IwoooS 人工決策正式紀錄負責人指派確認準備包 + IwoooS 人工決策正式紀錄負責人指派確認清單 + IwoooS 人工決策正式紀錄負責人指派確認結果分流 + IwoooS 人工決策正式紀錄負責人指派決策準備包 + IwoooS 人工決策正式紀錄負責人指派決策檢查清單 + IwoooS S4.9 負責人回覆封套欄位 + IwoooS S4.9 負責人回覆封套送件前檢查 + IwoooS S4.9 負責人回覆封套送件前結果分流 + IwoooS S4.9 負責人回覆送件請求草稿 + IwoooS S4.9 負責人回覆送件鏈路摘要 + IwoooS 低摩擦分階段收斂主控 + IwoooS 低摩擦下一步行動邊界 + IwoooS 64% 進度移動訊號驗收條 + IwoooS 第一個進度解鎖路徑 + IwoooS 第一解鎖證據包 + IwoooS 第一解鎖證據包預檢分流 + IwoooS 第一解鎖證據包補件路徑 + IwoooS 第一解鎖證據包補件送審前檢查 + IwoooS 第一解鎖證據包補件送審結果分流 + IwoooS 第一解鎖證據包 reviewer 指派準備包 + IwoooS 第一解鎖證據包 reviewer 指派前檢查 + IwoooS 第一解鎖證據包 reviewer 指派前檢查結果分流 + IwoooS 正式只讀 landing 與 Kali 112 只讀證據進度重估 | | 本階段追加補充 | IwoooS 目前具體工作地圖 + IwoooS 目前具體交付清單 + IwoooS 目前阻塞與解除條件 + IwoooS 三軸進度與全產品套用範圍 + IwoooS 全產品分階段套用台帳 + IwoooS 全產品 rollout 波次驗收門檻 + IwoooS 全產品 rollout 驗收結果分流 + IwoooS 全產品證據接線地圖 + IwoooS 全產品證據接線預檢 + IwoooS 全產品證據接線預檢結果分流 + IwoooS 全產品預檢補件回收台帳 + IwoooS 全產品補件重試門檻 + IwoooS 全產品重試結果分流 + IwoooS 全產品人工審查候選準備 + IwoooS 全產品人工審查候選預檢 + IwoooS 全產品人工審查候選預檢結果分流 + IwoooS 全產品人工審查候選預檢補件回收台帳 + IwoooS 全產品人工審查候選預檢補件重試門檻 + IwoooS 全產品只讀套用快照 + P2-145 owner response acceptance gate 正式驗證完成 | | P0 追加 | IwoooS P0 配置控管優先序前台正式驗證完成;Nginx public gateway、DNS / TLS / certbot、K8s / ArgoCD / production manifests、Workflow / runner / secret metadata、Public / admin / API runtime config、agent-bounty runtime / treasury 六類先列為即時風險配置;高價值配置 Gate 已補上 `k8s/nginx/**`、`scripts/ops/**/*cert*`、`scripts/ops/**/*tls*`,sample 從 `matched=0 / C0=0` 收斂到 `matched=3 / C0=2`;Gate 預設工作樹 preflight 已可讀取 staged / unstaged / untracked,本地 smoke 對臨時 `k8s/nginx/*` 檔命中 C0;Owner Packet snapshot 已同步為 `packets=3 / c0=2`,Coverage snapshot 已同步最新 patterns;IwoooS / AwoooP 前台 Owner Packet 摘要已正式驗證 `packet=3 / c0=2`,feature commit `e999c16b`、deploy marker `16c6b983`、Gitea code-review `2973` / CD `2972` success;IwoooS posture projection snapshot / schema / guard 已同步 `packet=3 / c0=2`,不再保留舊 `1 / 0` 口徑;高價值配置 Owner Packet 收件預檢已新增 `checks=9 / lanes=5 / required_fields=27 / blocked_requests=16`;高價值配置 Owner Request 草稿包已新增 `drafts=3 / handoff_fields=11 / forbidden_payloads=12 / sent=0`;Public Gateway live conf 匯出請求包已新增 `requests=3 / c0=2 / redaction_rules=8 / received=0`;Public Gateway redacted export 收件預檢已新增 `candidates=3 / c0=2 / checks=10 / rejection_guards=12 / received=0 / accepted=0`;Public Gateway rendered diff / nginx gate 草稿已新增 `candidates=3 / c0=2 / stages=7 / blocked=14 / rendered_diff=0 / runtime=0`;Public Gateway owner response acceptance 只讀帳本已新增 `candidates=3 / c0=2 / fields=12 / checks=12 / lanes=7 / blocked=18 / accepted=0 / runtime=0`;DNS / TLS / certbot Owner Confirmation Request 已新增 `requests=4 / c0=4 / fields=9 / questions=5 / guards=12 / received=0 / accepted=0`;K8s / ArgoCD manifest repo-only 清冊已新增 `files=49 / c0=36 / yaml=45 / kinds=20 / blocked=13 / runtime=0`;K8s / ArgoCD Owner Request Draft 已新增 `drafts=4 / c0=3 / fields=11 / sent=0 / runtime=0`;K8s / ArgoCD owner response acceptance 只讀帳本已新增 `candidates=4 / c0=3 / fields=11 / checks=12 / lanes=7 / blocked=18 / accepted=0 / runtime=0`;K8s / ArgoCD GitOps 變更證據驗收已新增 `candidates=4 / c0=3 / write_capable=4 / evidence_fields=18 / checks=18 / lanes=8 / blocked=28 / accepted=0 / runtime=0`;CD / Runner / Secret 注入變更證據驗收已新增 `candidates=5 / c0=4 / write_capable=5 / workflow_files=33 / secret_names=42 / runner_labels=5 / evidence_fields=19 / checks=19 / lanes=8 / blocked=32 / accepted=0 / runtime=0`;Public / Admin / API runtime config 變更證據驗收已新增 `candidates=6 / c0=5 / write_capable=6 / source_refs=20 / evidence_fields=21 / checks=21 / lanes=8 / blocked=32 / accepted=0 / runtime=0`,並把 raw namespace、repo slug、內部狀態碼與內部協作內容外洩列為拒收 / 隔離;Backup / Restore / Escrow owner response acceptance 只讀帳本已新增 `candidates=38 / write_capable=27 / fields=14 / checks=13 / lanes=7 / blocked=22 / accepted=0 / runtime=0`;SSH / Firewall / Network Access owner response acceptance 只讀帳本已新增 `candidates=16 / write_capable=6 / fields=13 / checks=15 / lanes=7 / blocked=22 / accepted=0 / runtime=0`;端口 / 防火牆變更證據驗收只讀帳本已新增 `candidates=14 / write_capable=6 / policy_or_exposure=5 / evidence_fields=16 / checks=16 / lanes=8 / blocked=24 / accepted=0 / runtime=0`;owner response / live evidence / runtime gate / action buttons 仍全部為 0 | | P0 agent-bounty 追加 | agent-bounty-protocol Owner Request Draft 已新增 `drafts=11 / control=4 / surface=7 / write_capable=8 / treasury=4 / mcp_a2a=5 / fields=22 / forbidden_inputs=25 / blocked=28 / sent=0 / runtime=0`;這是 repo / refs、deployment、data classification、MCP / A2A、cron / daemon、admin / treasury、webhook / traffic 的人工送件前草稿,不是 owner response、repo push、refs sync、workflow 修改、secret 收集、deploy、compose restart、DB migration、claim / submit、payout / withdrawal、cron / daemon、external send、host write 或 runtime gate | | P1 追加 | Docker / systemd / Host Service Owner Request Draft 已新增 `drafts=9 / write_capable=3 / fields=12 / blocked=14 / sent=0 / runtime=0`;SSH / Firewall / Network Access Owner Request Draft 已新增 `drafts=16 / write_capable=6 / fields=13 / blocked=16 / sent=0 / runtime=0`;Backup / Restore / Escrow Owner Request Draft 已新增 `drafts=38 / write_capable=27 / fields=14 / blocked=18 / sent=0 / runtime=0`;Backup / Restore / Escrow Owner Response Acceptance 已新增 `candidates=38 / write_capable=27 / reviewer_checks=13 / lanes=7 / blocked=22 / accepted=0 / runtime=0`;Monitoring / Alerting / Observability Owner Request Draft 已新增 `drafts=60 / write_capable=11 / fields=14 / blocked=24 / sent=0 / runtime=0`;上述全部仍是人工送件前草稿或只讀 acceptance 帳本,不是 owner response、live evidence、reload、restart、backup、restore、Telegram send、alert smoke、host write 或 runtime gate | -| P2 供應鏈追加 | Package / Docker 供應鏈 repo-only baseline 已新增 `package_json=6 / pyproject=4 / requirements=2 / dockerfiles=2 / compose=6 / gaps=5 / runtime=0`;缺口為 Python lockfile 缺席、requirements 未 pin、Docker base image 未全數 digest pinning、Docker `COPY --from` 外部 image 未 digest pinning、compose image 未 digest pinning;目前尚未列入 36 個正式 AwoooP 消費 contract,後續若要前台消費需同步 manifest / readiness / route / rollup / dry-run / posture projection / guard count;本輪不 install、不 upgrade、不跑 CVE、不 pull / build / push image、不改 tag、不登入 registry、不部署 | +| P2 供應鏈追加 | Package / Docker 供應鏈 repo-only baseline 已新增 `package_json=6 / pyproject=4 / requirements=2 / dockerfiles=2 / compose=6 / gaps=5 / runtime=0`;Package / Docker 供應鏈 owner policy gate 已新增 `requests=6 / c0=2 / fields=8 / checks=12 / blocked=20 / sent=0 / accepted=0 / runtime=0`;缺口為 Python lockfile 缺席、requirements 未 pin、Docker base image 未全數 digest pinning、Docker `COPY --from` 外部 image 未 digest pinning、compose image 未 digest pinning,以及 CVE / license / SBOM window 未定;目前尚未列入 36 個正式 AwoooP 消費 contract,後續若要前台消費需同步 manifest / readiness / route / rollup / dry-run / posture projection / guard count;本輪不 install、不 upgrade、不跑 CVE、不 pull / build / push image、不改 tag、不登入 registry、不部署 | | 原則 | 低摩擦分階段;文件、schema、read-only evidence 優先;不做 runtime enforcement、不切 primary | | P0 主控板 | `docs/workplans/2026-06-04-iwooos-security-governance-p0.md` | diff --git a/docs/security/package-supply-chain-owner-policy-gate.snapshot.json b/docs/security/package-supply-chain-owner-policy-gate.snapshot.json new file mode 100644 index 00000000..db372f23 --- /dev/null +++ b/docs/security/package-supply-chain-owner-policy-gate.snapshot.json @@ -0,0 +1,310 @@ +{ + "schema_version": "package_supply_chain_owner_policy_gate_v1", + "status": "draft_waiting_owner_policy_response", + "mode": "repo_snapshot_policy_gate_no_install_no_network_no_cve_scan", + "generated_at": "2026-06-15T07:05:00+08:00", + "baseline_ref": "docs/security/package-supply-chain-baseline.snapshot.json", + "baseline_git_commit": "1ab85f51", + "summary": { + "baseline_gap_count": 5, + "owner_policy_request_count": 6, + "c0_policy_request_count": 2, + "write_capable_policy_request_count": 6, + "required_owner_field_count": 8, + "reviewer_check_count": 12, + "blocked_action_count": 20, + "owner_policy_request_sent_count": 0, + "owner_response_received_count": 0, + "owner_response_accepted_count": 0, + "owner_response_rejected_count": 0, + "runtime_gate_count": 0, + "action_button_count": 0 + }, + "baseline_gaps": [ + "python_lockfile_absent", + "docker_base_images_not_all_digest_pinned", + "docker_copy_from_images_not_all_digest_pinned", + "compose_images_not_all_digest_pinned", + "requirements_unpinned_entries_present" + ], + "required_owner_fields": [ + "package_manager_policy", + "lockfile_owner", + "python_lock_policy", + "docker_base_image_policy", + "compose_image_policy", + "registry_owner", + "cve_scan_window", + "rollback_owner" + ], + "reviewer_checks": [ + "owner_role_or_team_present", + "decision_present", + "decision_reason_present", + "affected_scope_present", + "redacted_evidence_refs_present", + "followup_owner_present", + "rollback_owner_present", + "package_manager_policy_consistent", + "python_lock_policy_consistent", + "image_digest_policy_consistent", + "cve_license_sbom_window_metadata_only", + "no_runtime_or_secret_request_embedded" + ], + "blocked_actions": [ + "install_package", + "upgrade_package", + "downgrade_package", + "rewrite_lockfile", + "pin_requirements_without_owner_policy", + "run_npm_audit", + "run_pip_audit", + "run_external_cve_lookup", + "run_external_license_lookup", + "generate_sbom", + "docker_pull", + "docker_build", + "docker_push", + "change_image_tag", + "pin_image_digest_without_owner_policy", + "registry_login", + "change_workflow", + "change_secret", + "production_deploy", + "open_runtime_gate" + ], + "owner_policy_requests": [ + { + "request_id": "supply_chain_owner_policy:package_manager_lockfile_owner", + "label": "Node / pnpm package manager 與 lockfile owner policy", + "control_tier": "C1", + "status": "draft_waiting_owner_policy_response", + "baseline_gap_refs": [ + "package_manager_policy_required" + ], + "evidence_refs": [ + "package.json", + "pnpm-lock.yaml", + "docs/security/package-supply-chain-baseline.snapshot.json" + ], + "required_owner_fields": [ + "package_manager_policy", + "lockfile_owner", + "python_lock_policy", + "docker_base_image_policy", + "compose_image_policy", + "registry_owner", + "cve_scan_window", + "rollback_owner" + ], + "blocked_until": [ + "owner confirms pnpm lockfile owner", + "owner defines lockfile update window", + "rollback owner is assigned" + ], + "request_sent": false, + "owner_response_received": false, + "owner_response_accepted": false, + "runtime_gate_open": false + }, + { + "request_id": "supply_chain_owner_policy:python_lockfile_policy", + "label": "Python lockfile policy", + "control_tier": "C1", + "status": "draft_waiting_owner_policy_response", + "baseline_gap_refs": [ + "python_lockfile_absent" + ], + "evidence_refs": [ + "apps/api/pyproject.toml", + "packages/lewooogo-brain/pyproject.toml", + "packages/lewooogo-data/pyproject.toml", + "scripts/aider_watch_client/pyproject.toml", + "docs/security/package-supply-chain-baseline.snapshot.json" + ], + "required_owner_fields": [ + "package_manager_policy", + "lockfile_owner", + "python_lock_policy", + "docker_base_image_policy", + "compose_image_policy", + "registry_owner", + "cve_scan_window", + "rollback_owner" + ], + "blocked_until": [ + "owner selects poetry.lock / uv.lock / Pipfile.lock policy", + "compatibility owner confirms scope", + "rollback owner is assigned" + ], + "request_sent": false, + "owner_response_received": false, + "owner_response_accepted": false, + "runtime_gate_open": false + }, + { + "request_id": "supply_chain_owner_policy:requirements_pin_policy", + "label": "requirements pinning policy", + "control_tier": "C1", + "status": "draft_waiting_owner_policy_response", + "baseline_gap_refs": [ + "requirements_unpinned_entries_present" + ], + "evidence_refs": [ + "apps/api/requirements.txt", + "apps/sensor/requirements.txt", + "docs/security/package-supply-chain-baseline.snapshot.json" + ], + "required_owner_fields": [ + "package_manager_policy", + "lockfile_owner", + "python_lock_policy", + "docker_base_image_policy", + "compose_image_policy", + "registry_owner", + "cve_scan_window", + "rollback_owner" + ], + "blocked_until": [ + "owner defines pinning approach", + "compatibility test owner is assigned", + "rollback owner is assigned" + ], + "request_sent": false, + "owner_response_received": false, + "owner_response_accepted": false, + "runtime_gate_open": false + }, + { + "request_id": "supply_chain_owner_policy:dockerfile_digest_pin_policy", + "label": "Dockerfile base image / COPY --from digest policy", + "control_tier": "C0", + "status": "draft_waiting_owner_policy_response", + "baseline_gap_refs": [ + "docker_base_images_not_all_digest_pinned", + "docker_copy_from_images_not_all_digest_pinned" + ], + "evidence_refs": [ + "apps/api/Dockerfile", + "apps/web/Dockerfile", + "docs/security/package-supply-chain-baseline.snapshot.json" + ], + "required_owner_fields": [ + "package_manager_policy", + "lockfile_owner", + "python_lock_policy", + "docker_base_image_policy", + "compose_image_policy", + "registry_owner", + "cve_scan_window", + "rollback_owner" + ], + "blocked_until": [ + "registry owner confirms digest source", + "image compatibility owner confirms scope", + "rollback owner is assigned" + ], + "request_sent": false, + "owner_response_received": false, + "owner_response_accepted": false, + "runtime_gate_open": false + }, + { + "request_id": "supply_chain_owner_policy:compose_image_digest_pin_policy", + "label": "docker-compose image digest policy", + "control_tier": "C0", + "status": "draft_waiting_owner_policy_response", + "baseline_gap_refs": [ + "compose_images_not_all_digest_pinned" + ], + "evidence_refs": [ + "apps/api/docker-compose.test.yml", + "docker-compose.yml", + "infra/langfuse/docker-compose.yml", + "k8s/monitoring/docker-compose-110.yml", + "ops/monitoring/docker-compose.exporters.yaml", + "ops/sentry-self-hosted/docker-compose.yml", + "docs/security/package-supply-chain-baseline.snapshot.json" + ], + "required_owner_fields": [ + "package_manager_policy", + "lockfile_owner", + "python_lock_policy", + "docker_base_image_policy", + "compose_image_policy", + "registry_owner", + "cve_scan_window", + "rollback_owner" + ], + "blocked_until": [ + "registry owner confirms digest source", + "compose service owner confirms blast radius", + "rollback owner is assigned" + ], + "request_sent": false, + "owner_response_received": false, + "owner_response_accepted": false, + "runtime_gate_open": false + }, + { + "request_id": "supply_chain_owner_policy:cve_license_sbom_window", + "label": "CVE / license / SBOM 驗證窗口 policy", + "control_tier": "C1", + "status": "draft_waiting_owner_policy_response", + "baseline_gap_refs": [ + "cve_license_sbom_not_run" + ], + "evidence_refs": [ + "docs/security/PACKAGE-SUPPLY-CHAIN-BASELINE.md", + "docs/security/package-supply-chain-baseline.snapshot.json" + ], + "required_owner_fields": [ + "package_manager_policy", + "lockfile_owner", + "python_lock_policy", + "docker_base_image_policy", + "compose_image_policy", + "registry_owner", + "cve_scan_window", + "rollback_owner" + ], + "blocked_until": [ + "owner selects allowed tools", + "network / cost / runtime boundary is confirmed", + "rollback owner is assigned" + ], + "request_sent": false, + "owner_response_received": false, + "owner_response_accepted": false, + "runtime_gate_open": false + } + ], + "execution_boundaries": { + "package_installation_allowed": false, + "package_upgrade_allowed": false, + "lockfile_write_allowed": false, + "requirements_pin_change_allowed": false, + "external_cve_lookup_allowed": false, + "external_license_lookup_allowed": false, + "sbom_generation_allowed": false, + "docker_pull_allowed": false, + "docker_build_allowed": false, + "docker_push_allowed": false, + "image_tag_change_allowed": false, + "image_digest_pin_change_allowed": false, + "registry_login_allowed": false, + "workflow_modification_authorized": false, + "production_deploy_authorized": false, + "runtime_execution_authorized": false, + "action_buttons_allowed": false, + "secret_value_collection_allowed": false, + "runtime_gate_count": 0, + "not_authorization": true + }, + "operator_interpretation": [ + "此 owner policy gate 只把 package / Docker baseline 缺口轉成 owner metadata 收件草案。", + "通過此 gate 不代表可 install、upgrade、pin requirements、寫 lockfile、pull / build / push image、跑 CVE / license / SBOM 或登入 registry。", + "C0 Docker / compose digest policy 仍需 registry owner、rollback owner 與相容性證據,不得自動改 tag 或 digest。", + "owner policy request sent、received、accepted、runtime gate 與 action button 全部維持 0 / false。" + ] +} diff --git a/docs/security/security-mirror-dry-run.snapshot.json b/docs/security/security-mirror-dry-run.snapshot.json index 904a5d42..f3f20b98 100644 --- a/docs/security/security-mirror-dry-run.snapshot.json +++ b/docs/security/security-mirror-dry-run.snapshot.json @@ -169,6 +169,34 @@ "production_deploy" ] }, + { + "step_id": "CHECK_PACKAGE_SUPPLY_CHAIN_OWNER_POLICY_GUARD", + "expected_observation": "AwoooP dry-run 必須確認 Package / Docker 供應鏈 baseline 的五個缺口已轉成 owner policy gate,六個 request、兩個 C0、八個 owner 欄位、十二個 reviewer checks、二十個 blocked actions 齊備,且 request_sent / received / accepted / runtime / action 全部維持 0 / false。", + "evidence_refs": [ + "docs/security/PACKAGE-SUPPLY-CHAIN-BASELINE.md", + "docs/security/package-supply-chain-baseline.snapshot.json", + "docs/security/PACKAGE-SUPPLY-CHAIN-OWNER-POLICY-GATE.md", + "docs/security/package-supply-chain-owner-policy-gate.snapshot.json", + "scripts/security/package-supply-chain-owner-policy-guard.py" + ], + "pass_condition": "`python3 scripts/security/package-supply-chain-owner-policy-guard.py` 回傳 PACKAGE_SUPPLY_CHAIN_OWNER_POLICY_GUARD_OK,且不 install、不 upgrade、不跑外部 CVE / license lookup、不產生 SBOM、不 pull / build / push image、不登入 registry。", + "execution_allowed": false, + "blocked_actions": [ + "install_package", + "upgrade_package", + "rewrite_lockfile", + "pin_requirements_without_owner_policy", + "run_external_cve_lookup", + "run_external_license_lookup", + "generate_sbom", + "docker_pull", + "docker_build", + "docker_push", + "registry_login", + "production_deploy", + "open_runtime_gate" + ] + }, { "step_id": "CHECK_LOW_NOISE_CHANNEL", "expected_observation": "Channel Event 初期只發低噪音摘要或人工批准必要事件。", @@ -208,8 +236,8 @@ "status": "repo_snapshot_guard_pass", "date": "2026-06-15", "scope": "repo_snapshot_only", - "command": "python3 scripts/security/security-mirror-progress-guard.py && python3 scripts/security/source-control-owner-response-guard.py && python3 scripts/security/iwooos-config-control-guard.py && python3 scripts/security/iwooos-owner-gate-guard.py", - "result": "SECURITY_MIRROR_PROGRESS_GUARD_OK; SOURCE_CONTROL_OWNER_RESPONSE_GUARD_OK; IWOOOS_CONFIG_CONTROL_GUARD_OK; IWOOOS_OWNER_GATE_GUARD_OK", + "command": "python3 scripts/security/security-mirror-progress-guard.py && python3 scripts/security/source-control-owner-response-guard.py && python3 scripts/security/iwooos-config-control-guard.py && python3 scripts/security/iwooos-owner-gate-guard.py && python3 scripts/security/package-supply-chain-owner-policy-guard.py", + "result": "SECURITY_MIRROR_PROGRESS_GUARD_OK; SOURCE_CONTROL_OWNER_RESPONSE_GUARD_OK; IWOOOS_CONFIG_CONTROL_GUARD_OK; IWOOOS_OWNER_GATE_GUARD_OK; PACKAGE_SUPPLY_CHAIN_OWNER_POLICY_GUARD_OK", "validated_steps": [ "LOAD_CONTRACT_INDEXES", "CHECK_ACCEPTANCE_AND_QUARANTINE", @@ -217,6 +245,7 @@ "CHECK_OWNER_RESPONSE_GUARD", "CHECK_OWNER_GATE_GUARD", "CHECK_CONFIG_CONTROL_GUARD", + "CHECK_PACKAGE_SUPPLY_CHAIN_OWNER_POLICY_GUARD", "CONFIRM_NO_RUNTIME_ACTION" ], "runtime_actions_executed": false, diff --git a/scripts/security/package-supply-chain-owner-policy-guard.py b/scripts/security/package-supply-chain-owner-policy-guard.py new file mode 100644 index 00000000..3286bb27 --- /dev/null +++ b/scripts/security/package-supply-chain-owner-policy-guard.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +"""驗證 Package / Docker 供應鏈 owner policy gate 維持只讀邊界。 + +本 guard 只讀取 repo 內的 package / Docker 供應鏈 baseline 與 owner +policy gate snapshot,不安裝套件、不連外查 CVE / license、不產生 lockfile、 +不 pull / build / push image,也不修改 workflow、registry 或 production。 +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any + + +EXPECTED_BASELINE_GAPS = [ + "python_lockfile_absent", + "docker_base_images_not_all_digest_pinned", + "docker_copy_from_images_not_all_digest_pinned", + "compose_images_not_all_digest_pinned", + "requirements_unpinned_entries_present", +] + +EXPECTED_REQUEST_IDS = [ + "supply_chain_owner_policy:package_manager_lockfile_owner", + "supply_chain_owner_policy:python_lockfile_policy", + "supply_chain_owner_policy:requirements_pin_policy", + "supply_chain_owner_policy:dockerfile_digest_pin_policy", + "supply_chain_owner_policy:compose_image_digest_pin_policy", + "supply_chain_owner_policy:cve_license_sbom_window", +] + +EXPECTED_OWNER_FIELDS = [ + "package_manager_policy", + "lockfile_owner", + "python_lock_policy", + "docker_base_image_policy", + "compose_image_policy", + "registry_owner", + "cve_scan_window", + "rollback_owner", +] + +FALSE_BOUNDARY_KEYS = [ + "package_installation_allowed", + "package_upgrade_allowed", + "lockfile_write_allowed", + "requirements_pin_change_allowed", + "external_cve_lookup_allowed", + "external_license_lookup_allowed", + "sbom_generation_allowed", + "docker_pull_allowed", + "docker_build_allowed", + "docker_push_allowed", + "image_tag_change_allowed", + "image_digest_pin_change_allowed", + "registry_login_allowed", + "workflow_modification_authorized", + "production_deploy_authorized", + "runtime_execution_authorized", + "action_buttons_allowed", + "secret_value_collection_allowed", +] + + +def fail(message: str) -> None: + raise SystemExit(f"BLOCKED {message}") + + +def load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def assert_equal(label: str, actual: Any, expected: Any) -> None: + if actual != expected: + fail(f"{label}: expected {expected!r}, got {actual!r}") + + +def assert_path_exists(root: Path, relative_path: str) -> None: + if not (root / relative_path).exists(): + fail(f"path missing: {relative_path}") + + +def assert_text_contains(label: str, text: str, expected: str) -> None: + if expected not in text: + fail(f"{label}: missing {expected!r}") + + +def assert_false_boundaries(label: str, data: dict[str, Any]) -> None: + boundaries = data.get("execution_boundaries", {}) + for key in FALSE_BOUNDARY_KEYS: + assert_equal(f"{label}.execution_boundaries.{key}", boundaries.get(key), False) + assert_equal(f"{label}.execution_boundaries.runtime_gate_count", boundaries.get("runtime_gate_count"), 0) + assert_equal(f"{label}.execution_boundaries.not_authorization", boundaries.get("not_authorization"), True) + + +def validate_baseline(root: Path) -> dict[str, Any]: + baseline_path = root / "docs/security/package-supply-chain-baseline.snapshot.json" + assert_path_exists(root, "docs/security/package-supply-chain-baseline.snapshot.json") + baseline = load_json(baseline_path) + summary = baseline.get("summary", {}) + + assert_equal("baseline.schema_version", baseline.get("schema_version"), "package_supply_chain_baseline_v1") + assert_equal("baseline.status", baseline.get("status"), "repo_only_inventory_ready_needs_owner_policy") + assert_equal("baseline.mode", baseline.get("mode"), "repo_snapshot_only_no_install_no_network_no_cve_scan") + assert_equal("baseline.summary.package_json_count", summary.get("package_json_count"), 6) + assert_equal("baseline.summary.pyproject_count", summary.get("pyproject_count"), 4) + assert_equal("baseline.summary.requirements_file_count", summary.get("requirements_file_count"), 2) + assert_equal("baseline.summary.requirements_unpinned_entry_count", summary.get("requirements_unpinned_entry_count"), 26) + assert_equal("baseline.summary.python_lockfile_count", summary.get("python_lockfile_count"), 0) + assert_equal("baseline.summary.docker_base_digest_pinned_count", summary.get("docker_base_digest_pinned_count"), 0) + assert_equal( + "baseline.summary.compose_digest_pinned_image_ref_count", + summary.get("compose_digest_pinned_image_ref_count"), + 0, + ) + assert_equal("baseline.summary.gap_count", summary.get("gap_count"), 5) + assert_equal("baseline.gaps", baseline.get("gaps"), EXPECTED_BASELINE_GAPS) + assert_equal("baseline.owner_response_received_count", summary.get("owner_response_received_count"), 0) + assert_equal("baseline.owner_response_accepted_count", summary.get("owner_response_accepted_count"), 0) + assert_equal("baseline.runtime_gate_count", summary.get("runtime_gate_count"), 0) + assert_equal("baseline.action_button_count", summary.get("action_button_count"), 0) + + for relative_path in baseline.get("lockfiles", []): + assert_path_exists(root, relative_path) + for key in ["package_json_manifests", "pyproject_manifests", "requirements_files", "dockerfiles", "compose_files"]: + for item in baseline.get(key, []): + assert_path_exists(root, item["path"]) + return baseline + + +def validate_policy_gate(root: Path, baseline: dict[str, Any]) -> None: + gate_path = root / "docs/security/package-supply-chain-owner-policy-gate.snapshot.json" + doc_path = root / "docs/security/PACKAGE-SUPPLY-CHAIN-OWNER-POLICY-GATE.md" + assert_path_exists(root, "docs/security/package-supply-chain-owner-policy-gate.snapshot.json") + assert_path_exists(root, "docs/security/PACKAGE-SUPPLY-CHAIN-OWNER-POLICY-GATE.md") + gate = load_json(gate_path) + summary = gate.get("summary", {}) + + assert_equal("policy_gate.schema_version", gate.get("schema_version"), "package_supply_chain_owner_policy_gate_v1") + assert_equal("policy_gate.status", gate.get("status"), "draft_waiting_owner_policy_response") + assert_equal("policy_gate.mode", gate.get("mode"), "repo_snapshot_policy_gate_no_install_no_network_no_cve_scan") + assert_equal("policy_gate.baseline_ref", gate.get("baseline_ref"), "docs/security/package-supply-chain-baseline.snapshot.json") + assert_equal("policy_gate.summary.baseline_gap_count", summary.get("baseline_gap_count"), baseline["summary"]["gap_count"]) + assert_equal("policy_gate.summary.owner_policy_request_count", summary.get("owner_policy_request_count"), 6) + assert_equal("policy_gate.summary.c0_policy_request_count", summary.get("c0_policy_request_count"), 2) + assert_equal("policy_gate.summary.write_capable_policy_request_count", summary.get("write_capable_policy_request_count"), 6) + assert_equal("policy_gate.summary.required_owner_field_count", summary.get("required_owner_field_count"), 8) + assert_equal("policy_gate.summary.reviewer_check_count", summary.get("reviewer_check_count"), 12) + assert_equal("policy_gate.summary.blocked_action_count", summary.get("blocked_action_count"), 20) + assert_equal("policy_gate.summary.owner_policy_request_sent_count", summary.get("owner_policy_request_sent_count"), 0) + assert_equal("policy_gate.summary.owner_response_received_count", summary.get("owner_response_received_count"), 0) + assert_equal("policy_gate.summary.owner_response_accepted_count", summary.get("owner_response_accepted_count"), 0) + assert_equal("policy_gate.summary.owner_response_rejected_count", summary.get("owner_response_rejected_count"), 0) + assert_equal("policy_gate.summary.runtime_gate_count", summary.get("runtime_gate_count"), 0) + assert_equal("policy_gate.summary.action_button_count", summary.get("action_button_count"), 0) + assert_equal("policy_gate.baseline_gaps", gate.get("baseline_gaps"), EXPECTED_BASELINE_GAPS) + assert_equal("policy_gate.required_owner_fields", gate.get("required_owner_fields"), EXPECTED_OWNER_FIELDS) + assert_equal("policy_gate.owner_policy_request_ids", [item["request_id"] for item in gate["owner_policy_requests"]], EXPECTED_REQUEST_IDS) + + for item in gate["owner_policy_requests"]: + assert_equal(f"{item['request_id']}.status", item.get("status"), "draft_waiting_owner_policy_response") + assert_equal(f"{item['request_id']}.required_owner_fields", item.get("required_owner_fields"), EXPECTED_OWNER_FIELDS) + assert_equal(f"{item['request_id']}.request_sent", item.get("request_sent"), False) + assert_equal(f"{item['request_id']}.owner_response_received", item.get("owner_response_received"), False) + assert_equal(f"{item['request_id']}.owner_response_accepted", item.get("owner_response_accepted"), False) + assert_equal(f"{item['request_id']}.runtime_gate_open", item.get("runtime_gate_open"), False) + if not item.get("baseline_gap_refs"): + fail(f"{item['request_id']}.baseline_gap_refs: expected non-empty list") + for ref in item.get("evidence_refs", []): + assert_path_exists(root, ref) + if not item.get("blocked_until"): + fail(f"{item['request_id']}.blocked_until: expected non-empty list") + + assert_false_boundaries("policy_gate", gate) + + doc = doc_path.read_text(encoding="utf-8") + for text in [ + "owner policy gate", + "Python lockfile", + "requirements pinning", + "Docker digest pinning", + "CVE / license / SBOM", + "runtime gate", + "0 / false", + "不 install、不 upgrade、不 pull image、不 build image、不 push image", + ]: + assert_text_contains("policy_gate.doc", doc, text) + + +def validate(root: Path) -> None: + baseline = validate_baseline(root) + validate_policy_gate(root, baseline) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--root", + default=Path(__file__).resolve().parents[2], + type=Path, + help="Repository root. Defaults to the current script's repository.", + ) + args = parser.parse_args() + validate(args.root.resolve()) + print("PACKAGE_SUPPLY_CHAIN_OWNER_POLICY_GUARD_OK") + + +if __name__ == "__main__": + main() diff --git a/scripts/security/security-mirror-progress-guard.py b/scripts/security/security-mirror-progress-guard.py index 25641664..f761affd 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -79,6 +79,10 @@ def validate(root: Path) -> None: config_control_guard["validate"](root) owner_gate_guard = runpy.run_path(str(root / "scripts" / "security" / "iwooos-owner-gate-guard.py")) owner_gate_guard["validate"](root) + package_policy_guard = runpy.run_path( + str(root / "scripts" / "security" / "package-supply-chain-owner-policy-guard.py") + ) + package_policy_guard["validate"](root) manifest = load_json(security_dir / "security-supply-chain-contract-manifest.snapshot.json") readiness = load_json(security_dir / "security-mirror-readiness.snapshot.json") @@ -24992,7 +24996,7 @@ def validate(root: Path) -> None: assert_equal( "dry_run.latest_local_validation.result", local_validation["result"], - "SECURITY_MIRROR_PROGRESS_GUARD_OK; SOURCE_CONTROL_OWNER_RESPONSE_GUARD_OK; IWOOOS_CONFIG_CONTROL_GUARD_OK; IWOOOS_OWNER_GATE_GUARD_OK", + "SECURITY_MIRROR_PROGRESS_GUARD_OK; SOURCE_CONTROL_OWNER_RESPONSE_GUARD_OK; IWOOOS_CONFIG_CONTROL_GUARD_OK; IWOOOS_OWNER_GATE_GUARD_OK; PACKAGE_SUPPLY_CHAIN_OWNER_POLICY_GUARD_OK", ) assert_contains("dry_run.latest_local_validation.validated_steps", local_validation["validated_steps"], "CHECK_PROGRESS_GUARD") assert_contains( @@ -25010,6 +25014,11 @@ def validate(root: Path) -> None: local_validation["validated_steps"], "CHECK_CONFIG_CONTROL_GUARD", ) + assert_contains( + "dry_run.latest_local_validation.validated_steps", + local_validation["validated_steps"], + "CHECK_PACKAGE_SUPPLY_CHAIN_OWNER_POLICY_GUARD", + ) assert_false("dry_run.latest_local_validation.runtime_actions_executed", local_validation["runtime_actions_executed"]) assert_false("dry_run.latest_local_validation.payloads_ingested", local_validation["payloads_ingested"]) assert_false("dry_run.latest_local_validation.production_ingestion_enabled", local_validation["production_ingestion_enabled"])