ANTIGRAVITY 提的 24/7 Cloudflare Pages 遷移,結論是對的。但它那句「把 repo 根目錄設為 build output directory」,會讓 1799 個檔案——含六處明文憑證、soul.md、投資邏輯板、額度儀表板——同時變成公開可 curl。
← 回 HITL 提案板SS1 依賴 → Cloudflare Pageshitl.superportia.dev
裁決:方向對,執行細節不可照做
要解的問題很單純:夏哥在外面想看提案板,得先確認家裡那台 Mac 醒著。
整條鏈裡有三個必須同時活著的環節。任何一個掛掉,外網就是 502。
launchd/com.superportia.cloudflared-cacoch.plist,tunnel UUID 16855065-…。ingress 規則在 ~/.cloudflared/config.yml——不在 repo 裡,換機器就得重建。cacoch.server:app 綁 127.0.0.1:8789。repo 裡有兩份指向同一 port 的 plist(cacoch-server 與 cacoch-center),疑似新舊未清理,本身就是潛在衝突源。變成跟 2026-taxi.pages.dev 同一個模式:
ANTIGRAVITY 的方向就是這個,完全正確。分歧只在「dist/ 裡放什麼」這一步——見 06 分頁。
這一格是整個方案能成立的地基:風險來自「多發布了什麼」,不是「發布這件事」。
很多人會直覺以為「搬上 Pages = 變公開」。實際上這兩個路徑今天就已經是公開、免登入的:
| 來源 | 內容 |
|---|---|
AGENTS.md:175 | 「Agent URL: hitl.superportia.dev/hitl/(無 CF Access)」 |
cacoch-selftest.sh:104 | 註記 no CF Access,直接 curl 拿 200 |
.claude/rules/L3-archive/corrections.md:1100 | 實測:cacoch. 302 到 Access 登入頁,hitl. 200 直開 |
cacoch/server.py | /hitl mount 沒有任何 auth middleware |
換言之:只要有人知道網址(或掃到 DNS record),今天就能看到整個板與所有提案。遷到 Pages 不會讓這件事變得更糟或更好。
xysb-lab/superportia-ops 確認為 PRIVATE(gh repo view 回報 isPrivate: true)。但這兩件事在 Cloudflare Pages 的設計裡是分開的:
| 層 | private 管得到嗎 |
|---|---|
| 建置端(Pages clone 你的 repo) | 管得到——需要授權才讀得到私有原始碼。2026-Taxi 就是這樣跑的。 |
| 發布端(Pages 服務出的靜態檔) | 管不到——輸出目錄下的東西一律公開可 curl。 |
所以 private 這件事,讓 Track A 憑證的急迫性從 P0 降到 P1(目前只有 repo 授權者看得到),但對 Finding A 沒有任何緩解作用。
技術上可以在 Pages 專案前面掛 Access,變成要登入才能看。但那會直接違背 hitl. 子網域當初存在的理由。
.claude/rules/L3-archive/corrections.md:1100 記錄了這段歷史:曾經誤導夏哥去 cacoch.superportia.dev/hitl/,實測 302 到 Access 登入頁;正確的是 hitl.superportia.dev/hitl/,200 直開。這個子網域就是為了讓 agent 不用處理 Access token 也能 curl 才切出來的。
dist/ 是唯一解——不能靠登入牆兜底。提案宣稱已檢查過、無 file 協定與 localhost 硬編。實測仍有三處,而且還牽出第二個沒人提的風險。
| 檔案:行 | 違規 | git |
|---|---|---|
portal/hrs-ict-killzones-tfo-50k-proposal.html:417,424,431 | 圖片 src 指向本機絕對路徑(file 協定) | 未追蹤 |
portal/hrs-think9-3tier-tutorial-proposal.html:389,396,403 | 同上 | 未追蹤 |
portal/sector-heatmap/decision-log-20260709.html:265 | 連結指向 localhost 的 8788 埠 | 已追蹤 · 已修 |
諷刺的是,這三種樣式正是 scripts/hrs-gate.sh:148-162 明文要擋的——閘門早就寫好了,只是這些頁在閘門生效前就入庫了。
/ 開頭的參數改寫成 Windows 路徑,第 4–7 項檢查(registry / ssotNote / 相對 url / cover)恆為 FAIL,等於從來沒真的驗過任何一張卡。本次已於 scripts/hrs-gate.sh 修正(加 MSYS_NO_PATHCONV=1)。SS1 目前用 StaticFiles 直接掛檔案系統,所以沒進 git 的頁面照樣服務得出去。Cloudflare Pages 讀的是 git。
這兩頁遷移當下會從活著變成 404,而且不會有任何錯誤訊息告訴你——CI 綠燈、部署成功、頁面消失。這類故障最難事後追。
decision-log-20260709.html:265 的 localhost 連結/portal/sector-heatmap/。/portal/…、然後 git add;要廢就明確刪掉,不要留在檔案系統裡當幽靈。scripts/build-pages.sh 的 fail-fast 掃描。bash scripts/hrs-gate.sh audit,現在閘門修好了,這次的結果才有意義。提案只檢查了 hitl/index.html。那頁確實 100% 靜態相容——但真正會批准的不是那頁。
hitl/index.html:213 唯一的網路呼叫是 fetch('/hitl/hitl-registry.json'),純唯讀;拖拽與看板狀態全存 localStorage。這頁搬過去完全沒問題。
會回寫 registry 的是另一頁:
| 功能 | Pages 上的狀態 |
|---|---|
| 看板瀏覽、拖拽、分類、封面 | ✅ 正常(純前端 + localStorage) |
| 匯出 JSON、我的暫存、重設為預設 | ✅ 正常 |
POST /api/hitl/approve(改狀態為已批准) | ❌ 404,沒有後端 |
POST /api/hitl/update(改 nextAction / feedback / stage) | ❌ 404 |
自動備份到 hitl/backups/ | ❌ 隨 API 一起消失 |
注意 hitl/index.html:190 的 STATUS_ICON 仍會正確顯示「已批准」等狀態——它只是顯示 registry 給的值。所以壞掉不會很明顯,會像是「按了沒反應」。
Phase 1(本次):批准 = 改 hitl-registry.json + git push,15–30 秒全球生效。SS1 在線時仍可用原本的 API,只是改成寫完自動 commit,讓兩條路收斂到同一個 SSoT。好處是批准動作有 git 歷史可追,比現在的原子寫入+時間戳備份更清楚。
Phase 2(另案,本次不做):加 Cloudflare Pages Functions + GitHub API 寫回,才能在手機上直接點批准。要處理 token 保管與 CSRF,範圍比這次大,所以拆開。
原提案 Step 3 寫「刪除/停用 SS1 上舊有的 tunnel 綁定」。照字面執行會誤殺兩個服務。
cacoch/server.py:63-73 用 Host header 分流,同一顆 uvicorn(127.0.0.1:8789)同時服務:
| Host | 導向 | 保護 |
|---|---|---|
hitl.superportia.dev | /hitl/ 提案板 | 無(刻意) |
cacoch.superportia.dev | portal 首頁 / cacoch 手帳 | CF Access |
a.nqio.dev | hierarchy board | — |
另外 api.superportia.dev / ubi.superportia.dev 走的是不同的 ubi-mcp named tunnel,n8n.superportia.dev 在 Hetzner VPS 上獨立跑,這三個不受影響。
launchd/com.superportia.cloudflared-cacoch.plist → cacoch.superportia.dev 與 a.nqio.dev 一起 502。cloudflared tunnel delete)→ 不可逆,UUID 與 CNAME 全部要重建,而 ingress 設定檔 ~/.cloudflared/config.yml 不在 repo 裡,沒有備份可還原。POST /api/hitl/approve(見 04 分頁)。hitl.superportia.dev 的 CNAME 從 <uuid>.cfargotunnel.com 改指 Pages 專案。~/.cloudflared/config.yml、不動 cacoch. 與 a.nqio.dev 的紀錄。~/.cloudflared/config.yml 複製一份進 repo(去識別化後)當災難復原文件——這是目前唯一沒版控的關鍵設定。curl -sI https://cacoch.superportia.dev/ 仍回 302(Access 登入頁)= 沒被波及。Cloudflare Pages 會把 output directory 底下的東西原封不動當靜態網站發佈——沒有 auth、沒有過濾。設成 repo 根目錄,等於把整包貼到公網。
git ls-files 回報 1799 個檔案。裡面有還在用、可直接盜用的明文憑證。此頁本身是公開頁面,所以只列類型與位置,不列任何值:
| 位置 | 憑證類型 |
|---|---|
scripts/market-analyst/run_pipeline.sh:7 | DeepSeek API key(硬編) |
scripts/telegram-antigravity-bridge.py:34 | Telegram Bot Token(預設 fallback) |
launchd/com.superportia.telegram-antigravity-bridge.plist:19 | 同一組 Token,第二處 |
mcp_config.json:22 | Google OAuth Client Secret |
mcp_config.json:89 | Supabase access token |
.mcp.json:10 | 內部 CLOUD_UB token |
另有 89 處類似樣式的命中屬誤判(sk-notification-…、sk-cost-gate-… 這類任務代稱),已逐一排除,不列入上表。
curl 到 /mcp_config.json、/soul.md、/quota-dashboard.html,而且吃 CDN 快取。soul.md、constitution/(FLOOR、LANDMINES)、50-Decisions/、memory/(session checkpoints、verified knowledge)、data/usage-log.jsonl(逐任務成本與 token 用量)。
backups/ 底下 74 個檔案把上述治理文件重複備份了五到六份,檔名本身還帶出 SS1 的 macOS 帳號與完整家目錄結構(~/.claude、~/.codex、~/.gemini、~/.hermes)。
| 檔案 | 公開後暴露什麼 |
|---|---|
quota-dashboard.html | 六大 AI 平台額度、燒用速度、自動化排程 |
investment-logic-board.html | 個人投資邏輯藍圖 |
claude-command-chain.html | 五殼二橋指揮鏈拓樸(opsec) |
cacoch-fleet-ssot.html / cacoch-crossover-topology.html | 艦隊架構與交叉拓樸 |
council_report_2026*.html × 5 | 決策議會原始思維紀錄 |
現行 .gitignore 擋的是 .env、gcp-oauth.keys.json、*.keys.json——也就是「常見憑證檔名樣式」。它擋不住的正好是洩漏落點的兩類:
mcp_config.json、.mcp.json 的檔名長得完全無害,不符合任何憑證命名樣式。soul.md、constitution/、backups/ 從來就不被視為「機密檔案」,也沒人想過它們會變成網頁。dist/。加上一道「命中即 exit 1」的掃描當第二層。技術上確實可以在五分鐘內按完那幾個按鈕。問題是第六分鐘。
最後一行是重點:憑證被盜用沒有告警。DeepSeek 額度被燒、Telegram bot 被接管、Supabase 資料被讀,都要等你自己發現。
| 項目 | 若照原提案做 | 目前實況(repo private) |
|---|---|---|
| 六處憑證 | P0 · 已洩漏,立刻輪替 | P1 · 排程處理 |
| 治理文件 / 儀表板 | P0 · 不可逆外流 | P3 · 僅 repo 授權者可見 |
| 白名單管線 | — | P1 · 遷移的前置條件 |
右欄是今天的狀態。那六處憑證現在就已經在 git 歷史裡了——repo 是 private 所以急迫性降一級,但只要 repo 曾被任何人 clone 過、或未來誤設為 public,風險就回來。
一句話原則:Pages 只發布一個 dist/,內容嚴格等於今天已經公開的那兩個路徑。
$DEEPSEEK_API_KEY 等)。scripts/telegram-antigravity-bridge.py:34 與 launchd/…bridge.plist:19。mcp_config.json、.mcp.json、litellm_config.yaml 加入 .gitignore 並 git rm --cached,各附一份 .example 範本。scripts/vault-autocommit.sh 裡已經有偵測憑證外洩的 regex——把它接進 pre-commit,讓它對自己所在的 repo 也生效。scripts/build-pages.sh(已完成)產出:
git ls-files 決定,不是掃檔案系統——Pages 讀 git,未追蹤檔的落差在 build 期就會現形。排除所有 *.bak* 與 hitl/backups/。
dist/ 內若出現 file 協定、localhost、憑證樣式、或白名單哨兵(mcp_config.json / soul.md / memory/ 等),直接 exit 1。實跑:223 檔 18M,零命中。.github/workflows/deploy-pages.yml,用 wrangler action 直傳 dist,不用 Pages 的 GitHub 整合。可用 paths: [hitl/**, portal/**] 過濾,避開免費版每月 500 次構建上限;repo 其餘部分連 clone 都不會進 Cloudflare。hitl.superportia.dev 改指 Pages。其餘見 05 分頁。Phase 1 走 git push,Phase 2(Pages Functions)另案。細節見 04 分頁。
| 檢查 | 期望 |
|---|---|
bash scripts/build-pages.sh | 只有 hitl/ + portal/ + index.html,掃描 exit 0 |
bash scripts/hrs-gate.sh audit | 0 FAIL |
curl -sI …/hitl/ | 200 |
curl -sI …/mcp_config.json | 404 ← 最關鍵 |
curl -sI https://cacoch.superportia.dev/ | 302(未被波及) |
| 關閉 SS1,手機開板與任一提案頁 | 全部正常 |
/mcp_config.json 回 404,才代表白名單真的生效;回 200 就代表白名單沒接上,必須立刻下線並輪替憑證。「可行性 100%」講的是能不能做到,
不是做了之後會怎樣。 兩者之間的距離,就是 1799 個檔案。
這次真正的收穫不是「Pages 能不能用」——它當然能用。是發現我們有一個已經寫好、但套用不完全的閘門:hrs-gate.sh 早就在擋 file 協定與 localhost 硬編,可是 portal 裡仍有三個頁面帶著這些東西活到今天;vault-autocommit.sh 裡有一段偵測 API key 外洩的 regex,而它所在的這個 repo 就躺著六處明文憑證。更狠的是,這支閘門在 SS2 上因為 Git Bash 的路徑轉換,第 4–7 項檢查恆為 FAIL——它根本沒真的驗過任何一張卡(本次已修)。
規範存在,不等於規範生效。所以修正方案裡最重要的不是 dist/ 目錄本身,是那個「命中即 exit 1」的掃描步驟——把「記得排除敏感檔案」這件需要紀律的事,換成一道機器擋板。