From 68da769c1b5874d262c4b3f0f7e5b6aa18a92bbe Mon Sep 17 00:00:00 2001 From: uncle6me-web Date: Sat, 6 Jun 2026 19:11:28 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(harness):=20guard=20hook=20=E5=8D=80?= =?UTF-8?q?=E5=88=86=E3=80=8C=E5=9F=B7=E8=A1=8C=20acr=20push=E3=80=8Dvs?= =?UTF-8?q?=E3=80=8C=E5=B1=95=E7=A4=BA=E6=8C=87=E4=BB=A4=E3=80=8D=EF=BC=88?= =?UTF-8?q?=E5=A3=93=E6=B8=AC=20=C2=A79.5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 舊版 grep "acr push" 連「在 echo/heredoc 裡提到 push 字串」都擋, 導致 CC 連「把可貼上的指令印給使用者看」都做不到 → 反而違反 user-cc-harness §0.5「擋下必須印出正路」自身鐵則(連選②都印不出)。 修法(啟發式,誠實標明非完美,mindset §7): - awk 抽掉 heredoc 主體 → 剩下「實際執行命令列」才看 push 是否在命令位置 (行首 / ; & | && ||)。真執行擋、純展示放行。 - block 訊息補上選②可貼指令(符合 §0.5)。 - shell 層無法 100% 區分執行/展示,偷渡(bash< --- cli/harness/hooks/arcrun-guard.sh | 42 ++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/cli/harness/hooks/arcrun-guard.sh b/cli/harness/hooks/arcrun-guard.sh index e279d31..cf7388a 100644 --- a/cli/harness/hooks/arcrun-guard.sh +++ b/cli/harness/hooks/arcrun-guard.sh @@ -28,10 +28,46 @@ block() { # ── 硬擋:未經人類同意的暴露動作(明確越界,mindset §5)────────────── # 非互動環境下 CC 自己跑「部署對外 webhook / push recipe」= 替人類決定公開。 +# +# 壓測 §9.5 修正(A6):舊版 grep "acr push" 連「在 echo/heredoc 裡**提到** push 字串」 +# 都擋 → CC 連「把可貼上的指令印給使用者看」都做不到,反而違反 §0.5「擋下必指正路」鐵則。 +# 故先判斷此命令是「展示指令」還是「真的執行 push」,只擋後者。 +# +# 誠實限制(mindset §7):shell 命令層**無法 100% 乾淨區分**「執行」與「展示」 +# (例:`echo "跑 acr push"` 與 `acr push` 對單純 grep 都命中)。以下是**啟發式**, +# 目的是降低誤殺、讓「印出正路指令」這條合法用途能通,不是完美防護。邊角情況可能漏判, +# 但漏判方向是「偏向放行展示」——而真正的暴露(執行)仍受 TTY/env 把關。 if echo "$CMD" | grep -qE "acr (push|recipe push)\b"; then - if [ ! -t 0 ] && [ "${ARCRUN_HUMAN_CONFIRMED:-}" != "1" ]; then - block "在非互動環境自動執行暴露動作(acr push / recipe push 會讓東西可被外部呼叫)" \ - "把這動作交給人類在終端機執行,或先讓使用者明示同意。不要替使用者決定公開。" + # 目標(壓測 §9.5):擋「真的執行 push」,放行「把 push 指令印給使用者看」。 + # + # 作法:把 heredoc 主體(cat/印出用的多行文字)先抽掉,剩下的才是「實際會被 shell 執行的命令列」, + # 再看 push 是否出現在那裡的「命令位置」(行首 / ; & | && || 之後)。 + # 執行 → 命中:`acr push x`、`cd f && acr push x`、`echo done; acr push x` + # 展示 → 不命中:`echo "跑 acr push x"`、`cat <\`。或使用者先在對話明示同意後親自於終端機執行。不要替使用者決定公開。" + fi fi fi From d2048e26a768836cffd8d0eb195d6b2c0627db13 Mon Sep 17 00:00:00 2001 From: uncle6me-web Date: Sat, 6 Jun 2026 21:09:49 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(cypher):=20auth=20primitive=20=E6=94=B9?= =?UTF-8?q?=E8=B5=B0=20service=20binding=EF=BC=88=E8=A7=A3=20self-hosted?= =?UTF-8?q?=20CF=201042=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 壓測階段 11:self-hosted 帳號 cypher 用 fetch(workers.dev) 打同帳號 auth worker 被 CF 子請求限制回 1042,service account token 換不到 → 表單寫不進 Google Sheets。token/解密鏈本身正常(直打 auth worker 回真 ya29)。 架構演化(richblack 2026-06-06 拍板):用戶產生的是 recipe(KV 資料,不 deploy), primitive 是平台固定基礎設施、用戶不新增 → 解除「auth primitive 禁 service binding」 舊禁令。service binding 是 CF 內部 RPC,繞開同 zone 522 + 同帳號 workers.dev 1042。 - wrangler.toml:加 SVC_AUTH_STATIC_KEY/SERVICE_ACCOUNT/OAUTH2(已部署者;mtls 未部署留註解) - auth-dispatcher.ts:binding 優先 svc.fetch(),無 binding fallback fetch(workers.dev) - types.ts:4 個 optional SVC_AUTH_* - deploy.ts 無需改:stripOfficialOnlyBindings 不碰 services,tier1 auth 先於 tier2 cypher - 已驗證 self-hosted(leo21c)13 邏輯零件 binding 實綁成功,auth binding 走同路 規範同步:rule 02 / 03 / CLAUDE.md / pre-bash-guard 例外。SDD: Phase 7。tsc exit 0。 Co-Authored-By: Claude Opus 4.8 --- .../src/actions/auth-dispatcher.ts | 42 ++++++++++++++----- cypher-executor/src/types.ts | 6 +++ cypher-executor/wrangler.toml | 18 ++++++++ 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/cypher-executor/src/actions/auth-dispatcher.ts b/cypher-executor/src/actions/auth-dispatcher.ts index f9b1e69..e1de675 100644 --- a/cypher-executor/src/actions/auth-dispatcher.ts +++ b/cypher-executor/src/actions/auth-dispatcher.ts @@ -20,10 +20,23 @@ import type { Bindings } from '../types'; import { resolveAuthRecipe, resolveRecipe } from '../routes/recipes'; import { wasmWorkerUrl } from '../lib/component-loader'; +import type { ServiceBinding } from '../types'; /** 對應 Phase 1-4 會部署的 auth primitive Worker */ const SUPPORTED_PRIMITIVES = new Set(['static_key', 'service_account', 'oauth2']); +/** + * primitive 名 → service binding key(Phase 7,2026-06-06)。 + * 比照 component-loader 的邏輯零件:有 binding 走 CF 內部 RPC(繞開同 zone 522 + 同帳號 workers.dev 1042), + * 無 binding(如 self-hosted 未綁、或 mtls 未部署)fallback 到 fetch(workers.dev)。 + */ +const AUTH_BINDING_MAP: Record = { + static_key: 'SVC_AUTH_STATIC_KEY', + service_account: 'SVC_AUTH_SERVICE_ACCOUNT', + oauth2: 'SVC_AUTH_OAUTH2', + mtls: 'SVC_AUTH_MTLS', +}; + /** auth primitive 本身的 componentId(避免自引用) */ const AUTH_PRIMITIVE_IDS = new Set([ 'auth_static_key', @@ -62,18 +75,27 @@ export async function tryAuthDispatch( if (!recipe) return null; if (!SUPPORTED_PRIMITIVES.has(recipe.primitive)) return null; - // 走新路徑:HTTP POST 到對應 auth primitive Worker - // 走 workers.dev 避開同 zone 死鎖(P0 #9) - const primitiveUrl = wasmWorkerUrl(`auth_${recipe.primitive}`, env.WORKER_SUBDOMAIN); - const res = await fetch(primitiveUrl, { + // 呼叫對應 auth primitive Worker(Phase 7,2026-06-06): + // binding 優先(CF 內部 RPC,繞開同 zone 522 + 同帳號 workers.dev 子請求 1042,壓測階段 11), + // 無 binding(self-hosted 未綁 / mtls 未部署)fallback 到 fetch(workers.dev)。比照 component-loader makeLogicRunner。 + const reqInit = { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - action: 'authenticate', - api_key: apiKey, - service, - }), - }); + body: JSON.stringify({ action: 'authenticate', api_key: apiKey, service }), + }; + + const bindingKey = AUTH_BINDING_MAP[recipe.primitive]; + const svc = bindingKey ? (env[bindingKey] as ServiceBinding | undefined) : undefined; + + let res: Response; + if (svc) { + // service binding:用任意 URL,CF 內部 RPC 直送目標 Worker(不經公網) + res = await svc.fetch(new Request('https://auth-primitive/', reqInit)); + } else { + // fallback:公網 workers.dev(自架未綁 binding / 開發環境 / mtls) + const primitiveUrl = wasmWorkerUrl(`auth_${recipe.primitive}`, env.WORKER_SUBDOMAIN); + res = await fetch(primitiveUrl, reqInit); + } if (!res.ok) { const text = await res.text().catch(() => ''); diff --git a/cypher-executor/src/types.ts b/cypher-executor/src/types.ts index 698c309..ea4e5c9 100644 --- a/cypher-executor/src/types.ts +++ b/cypher-executor/src/types.ts @@ -21,6 +21,12 @@ export type Bindings = { SVC_DATE_OPS: ServiceBinding; SVC_VALIDATE_JSON: ServiceBinding; // SVC_AI_TRANSFORM_* 已移除(Phase 2 刪 ai_transform 零件 + wrangler.toml service binding) + // Auth primitive Service Bindings(Phase 7,2026-06-06):繞開 self-hosted 同帳號 workers.dev 子請求 1042。 + // optional:auth_mtls 尚未部署(無 binding);無 binding 時 auth-dispatcher fallback 到 fetch(workers.dev)。 + SVC_AUTH_STATIC_KEY?: ServiceBinding; + SVC_AUTH_SERVICE_ACCOUNT?: ServiceBinding; + SVC_AUTH_OAUTH2?: ServiceBinding; + SVC_AUTH_MTLS?: ServiceBinding; // KV Context Store:節點 output 透過 KV 傳遞,解決同名欄位衝突 EXEC_CONTEXT: KVNamespace; // Recipe Store:API recipe 定義(key: recipe:{canonical_id} 或 idx:{hash_id}) diff --git a/cypher-executor/wrangler.toml b/cypher-executor/wrangler.toml index b1ad7eb..b294da7 100644 --- a/cypher-executor/wrangler.toml +++ b/cypher-executor/wrangler.toml @@ -91,6 +91,24 @@ service = "arcrun-date-ops" binding = "SVC_VALIDATE_JSON" service = "arcrun-validate-json" +# Auth primitive service bindings(credential-primitives-wasm Phase 7,2026-06-06) +# 為何:auth-dispatcher 原用 fetch(workers.dev) 打同帳號 auth worker,self-hosted 帳號踩 CF 1042 +# (壓測階段 11)。service binding 是 CF 內部 RPC,繞開同 zone 522 + 同帳號 1042。 +# 範圍:只綁「已部署」的 auth worker。auth_mtls 尚未部署(.component-builds 無、官方 404), +# 綁不存在的 worker 會讓 deploy 報 "referenced Worker not found"(見上 ai_transform 教訓), +# 故 mtls 待它部署後再加。auth-dispatcher 對無 binding 的 primitive 自動 fallback fetch。 +[[services]] +binding = "SVC_AUTH_STATIC_KEY" +service = "arcrun-auth-static-key" + +[[services]] +binding = "SVC_AUTH_SERVICE_ACCOUNT" +service = "arcrun-auth-service-account" + +[[services]] +binding = "SVC_AUTH_OAUTH2" +service = "arcrun-auth-oauth2" + # ai_transform_compile / ai_transform_run 已於 Phase 2(2026-05-29)刪除 # (Arcrun 是 AI 呼叫的工具,工作流不該內嵌 AI 節點)。對應 worker 已 wrangler delete, # service binding 一併移除(否則 deploy 報 referenced Worker not found)。