第 4 批:清版控裡的憑證殘留(10 件 → 4 張新卡 + 1 張沿用既有卡)

§1

這批一句話

這批 10 件裡有 8 件據報密碼/金鑰已撤銷換發(決策者原則九),剩下的工作是把版控裡那些字串清乾淨(含對話紀錄、腳本、交接文件);另外 2 件(M03-9 派工表明文殘留、M23-3 Google 金鑰)本身還沒處理完,需要多一步程式修正或金鑰重設。這批現在就能開,不依賴其他批——清字串是收尾動作,跟前面批次的程式修正互不阻擋。

§2

🔴 本棒(開卡前座標補查)改了什麼,派工前必讀

座標全部補齊了(每件的檔案清單、grep 指令、命中檔數都寫進各卡),但查證同時翻掉幾個前提:

  1. 「已撤銷換發」這句對不上現況——下列三把與現行 .env 完全一致,也就是很可能根本沒換:
    • 資料庫管理員密碼(4-2):.env 的 DB_PASSWORD =版控殘留值,而這個帳號是 BYPASSRLS、四處通行
    • 系統管理員登入密碼(4-1 的 #72):就是 docs/claude/memory/reference_dev_login.md 現行那組
    • Google 的 AI 服務金鑰(4-1 的 #11 四把之一):.env 的 GOOGLE_API_KEY =版控殘留值 所以這幾件的性質可能不是「打掃」而是「洞還在」。 已寫成 D-b4-2 與各件的 ⚠️ 註記,清字串前要先實查換沒換。
  2. 件數與實數有落差(開卡照實數寫):#10「26 個檔」實查 8 檔、「4 把通行證」只找到 1 把真值+1 把假值(另 2 把 BE 與套件都零命中);#72「三支腳本」實數 5 支;#12「249 檔」實數 250 檔;#74「15 檔」實數 14 檔。
  3. 多找到兩個容易漏的落點:docs/features-site/site/search/search_index.json(MkDocs 搜尋索引,憑證原文在裡面)、docs/claude/memory/ 下 6 份 memory(每個 session 都會讀)。
  4. 查到但判定不必清的:TURNSTILE_SECRET_KEY(30 檔)是 Cloudflare 官方公開的測試 dummy key、glpat-fake…(6 檔)是範例值——標記為假陽性,免得下次掃描再撈一次。
  5. 全部落在 BE 主專案,套件 monorepo 對這批所有憑證特徵字串零命中——不需要開套件側 worktree(4-3 那張是程式修正、另當別論)。
  6. 本批一律只清「受版控的檔」,git grep 就是正確的範圍。本棒另跑過一次「整個工作目錄」的 grep 對照,資料庫密碼從 250 檔(受版控)變成 547 檔——多出來的 297 檔全部是 gitignored 的本機產物,已逐項確認 git check-ignore 都通過,不會被 commit、不在本批範圍:
    • .claude/ 250 檔(本機 session 暫存)
    • CLAUDE-SECURITY-*/ 的掃描產物 20 檔(RESULTS.jsonl / .sarif / .md)—— ⚠️ 這是掃描工具自己把撈到的憑證原文寫進報告檔,雖然沒進版控,但那些目錄躺在 repo 根目錄下、每跑一次掃描就多一份。建議順手回報決策者「掃描產物要不要定期清」(屬本機衛生、不是本批工作)
    • reports/allure-results/ 21 檔(測試報告)、.env / .env.bak / .env.bak-20260814-pre1g / docker/production/.env / docker/production/guidant.env(設定檔本體,本來就該有值、不要動) 驗收一律用 git grep,不要用 grep -r——後者會把上面這些算進來,永遠驗不到零命中。
§3

卡片清單

卡 卡名(做什麼,不寫代號) repo/套件 涵蓋 SUMMARY # 建議 model/effort 序列組
4-1 清掉版控裡的憑證殘留字串(外部平台通行證/AI服務金鑰/系統管理員密碼/交接文件帳密,共 5 件) BE 主專案(docs/conversation-history/、scripts/、docs/analysis/、docs/features/FR-039*/handoff/、docs/claude/memory/;套件側全部零命中,已查證) #10、#11、#72、#73、#122 sonnet/medium A
4-2 資料庫管理員密碼(250 檔殘留+堵住還在寫入的那 1 支產生器) BE 主專案(已查證無套件側) #12(含其中的 M16-7、M17-8 兩個具體實例) sonnet/medium A
4-3 移除掃描派工資料表裡的明文帳密殘留(程式修正,不是清字串) jedi-detection 套件(寫入只有 1 行,兩條路徑共用) #70 sonnet/medium B
4-4 Google 雲端硬碟金鑰:清殘留字串+(等決策)金鑰重設 BE 主專案(14 檔全在 docs/conversation-history/;套件側零命中,已查證) #74 sonnet/medium A
(沿用) 打包前端映像檔腳本內嵌 Nexus 帳密 — #75 — —

(#75 直接沿用既有卡 CM-1998,不重開、不寫小節;序列組欄位留空。)


§4

卡 4-1:清掉版控裡的憑證殘留字串(外部平台通行證/AI服務金鑰/系統管理員密碼/交接文件帳密)

  • 範圍:BE 主專案(已查證:五件的殘留全在 BE,落在 docs/conversation-history/、scripts/、docs/analysis/、docs/features/FR-039*/handoff/、docs/claude/memory/、docs/features-site/;套件 monorepo 對五件的憑證特徵字串全部零命中,不需開套件側 worktree);worktree 名建議 wt-fix-b4-cred-strings;涵蓋 #10、#11、#72、#73、#122
  • 每件的修法(白話,一件一小節):

🔴 本節所有清單都是「用 key 名或憑證前 4 碼 grep」查出來的路徑與檔數,計畫檔內不寫任何憑證值。runner 開工時自己從 .env(或報告內位置)取值再 grep,不要把值貼進 commit message、Notion 卡或回報。 ⚠️ 本棒查證的一個共同發現:這批「已撤銷」的憑證裡,有幾把與現行 .env 的值一模一樣(見各件的 ⚠️ 註記)。所以清字串前要先確認該值到底換過沒有——若沒換,清版控只是打掃,洞還在。

  • #10 外部程式碼平台的存取通行證(4 把)殘留在 26 個版控檔案裡
    • 修法:密碼已全數撤銷(無效),純粹是清掉現存檔案裡的舊字串;清完後這幾把通行證的字串在 repo 裡要零命中。
    • 🔴 入口清單(已查證):
      • grep 指令:git grep -lIE 'glpat-[A-Za-z0-9_-]{15,}'(GitLab personal access token 的固定前綴)
      • 命中 8 個檔(不是 26;26 可能是「行數」或含 features-site 產出的舊數): docs/conversation-history/2026-04-28-to-04-30-survey-answer-arc/part-verbatim-01-of-03.md docs/conversation-history/2026-04-28-to-04-30-survey-answer-arc/part-verbatim-03-of-03.md docs/conversation-history/2026-05-06-my-tasks-completion-gate/part-01-of-05-bug-fix-handover-and-standards-audit.md docs/conversation-history/2026-05-08-project-flow-engine-arc/part-01-of-11-m0-spike-and-spec-amend.md docs/conversation-history/2026-05-13-spec2-phase-e-decision/part-01-of-01-phase-e-defer-and-phase-f-kickoff.md docs/conversation-history/2026-05-14-to-05-15-full/part-04-of-09-37cd69d1-prompt-session.md docs/conversation-history/2026-05-22/ssp-edit-in-project/25ad9e8d-docs-features-ssp-edit-in-project-handof.md docs/conversation-history/2026-05-22/ssp-import-export-phase2/7bed5ff9-session-docs-features-ssp-import-ex.md
      • distinct token 只有 2 個、不是 4 個:一個前綴 glpat-fake…(這把是假值/範例,不是真憑證,6 個檔)、一個前綴 glpat-raUg…(2 個檔,這把才要清)。runner 用 git grep -hoIE 'glpat-[A-Za-z0-9_-]{15,}' | sort -u 自己取出兩個值比對。
      • GitHub 側零命中:git grep -lIE 'ghp_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{30,}|gho_[A-Za-z0-9]{30,}' → 0 檔;glrt- / gldt- 亦 0。所以「4 把」裡至少 2 把在本 repo 找不到——可能在套件 monorepo 或已被清掉。⚠️ 套件 monorepo 也查了:glpat- 零命中。 建議開卡時註明「4 把只找到 1 把真值+1 把假值,其餘 2 把查無,請 M15 報告作者或 CM-1607 工單補位置」。
      • 另有 env var 名的大量命中(GITLAB_PRIVATE_TOKEN 152 處、GITHUB_PRIVATE_TOKEN 195 處)——那是變數名不是值,不必清。
    • 功能不能壞:這些字串本身已失效,清除不影響任何執行中功能;只要小心別誤刪同一份文件裡其他還有效的段落。
  • #11 一個測試設定檔裡的四把對外 AI 服務金鑰(工單 CM-1607)
    • 修法:金鑰已撤銷換發,清掉殘留字串。
    • 🔴 入口清單(已查證):
      • 原檔 .env.test 已刪(commit 5746cef1,2026-09-08)——要清的是對話紀錄裡的殘留。
      • grep 指令:git grep -lIE 'sk-ant-[A-Za-z0-9_-]{20,}|sk-proj-[A-Za-z0-9_-]{20,}|AIza[A-Za-z0-9_-]{30,}'
      • 命中 9 個檔(與 FR-079 B2 F2 報的「九檔」一致): docs/conversation-history/2026-04-28-to-04-30-survey-answer-arc/part-verbatim-01-of-03.md(B2 報告點名的精確位置是此檔 :3651) docs/conversation-history/2026-05-19/ssp-import-export-phase2-A0.1/e500cf61.md docs/conversation-history/2026-05-27/participant-picker-framework-drive-fixes/0043-617066cd-session-projectplannin.md docs/conversation-history/2026-05-29/conversation-history-housekeeping/1035-c5c2e417-5-28.md docs/conversation-history/2026-05-31/evidence-classify-reports/1029-d0b63dd1-feature-ai-analysis-result-landing.md docs/conversation-history/2026-06-02/db-erd-tooling-convergence/2024-d2a67903.md docs/conversation-history/2026-06-04/erd-db-diagram/2024-d2a67903-db-db-diagram-mcp.md docs/conversation-history/2026-06-14/fr038-oscal-redesign-wave1/2034-3fc2f2b1-oscal-v1-2-2-schema-c.md docs/conversation-history/2026-06-14/fr038-oscal-redesign-wave1/2034-6219587d-oscal-v1-2-2-schema-c.md
      • 四家分別是:Anthropic(sk-ant-…,1 個 distinct)/OpenAI(sk-proj-…,1 個)/Google(AIza…,1 個)/LangChain LangSmith(lsv2_pt_…,本棒未單獨 grep,runner 補跑 git grep -lIE 'lsv2_(pt|sk)_[A-Za-z0-9]{20,}')
      • ⚠️ AIza… 那把與現行 .env 的 GOOGLE_API_KEY 完全一致(本棒比對過,命中 9 檔)——Google 這把很可能沒真的換。B2 報告也寫「commit 5746cef1 宣稱已撤銷,但那次只刪了 .env.test,撤銷與否無法從程式碼查證」。開卡務必寫「清字串前先到 Google Cloud Console 實查這把還有效嗎」。Anthropic/OpenAI 那兩把與 .env 不同(.env 內的值在版控零命中),這兩把可信已換。
    • 功能不能壞:純字串清理(原設定檔已不存在)。⚠️ 但若 Google 那把確認仍有效,清字串不等於補洞,要回報決策者決定要不要換發。
  • #72 系統管理員帳號密碼寫死在腳本裡(工單 CM-1608)
    • 修法:清掉腳本裡「環境變數沒設就用這個密碼」的預設值寫死字串。這件不只是清字串、是程式修正:改成「沒設就報錯」而不是「沒設就靜默用真密碼」。
    • 🔴 入口清單(已查證):
      • grep 指令:先從 scripts/migrate_2026-08-09_fr062_existing_tenant_licenses.py:80 取那個 12 字元的預設值(前 4 碼 Bill),再 git grep -lIF -- '<該值>'
      • 腳本實數是 5 支、不是 3 支(材料寫「三支腳本」偏少,開卡要照實寫 5 支):
        • scripts/migrate_2026-08-09_fr062_existing_tenant_licenses.py:80 — SEED_PASS = os.getenv("FR062_SEED_PASS", "<真密碼>")(B2 報告 F14 點名的就是這支;:127 拿它去打真的 /login)
        • scripts/seed_2026-08-01_fr059_detection_profiles.py:65 — SEED_PASS = os.getenv("FR059_SEED_PASS", "<真密碼>")(:150 同樣打 /login)
        • scripts/e2e_test_module_frame_with_docx.py:38 — TEST_PASS = "<真密碼>"(連 env var 都沒有,純寫死)
        • scripts/smoke_test_ssp_docx_parser.py:34 — TEST_PASS = "<真密碼>"(純寫死)
        • scripts/smoke_test_ssp_docx_preselect.py:34 — TEST_PASS = "<真密碼>"(純寫死)
        • 分兩種修法:前兩支拿掉 os.getenv 的第二個參數,未設時 raise 並印「請設 FR0xx_SEED_PASS」;後三支改成 os.environ["..."](缺就自然炸)或同樣 getenv+raise。
      • 同一組密碼的其他殘留(純清字串那半):共 151 個受版控檔案,分布: docs/conversation-history/ 63 檔/docs/features-site/ 39 檔/docs/features/ 38 檔/docs/claude/ 4 檔/docs/system-design/ 2 檔/scripts/ 5 檔(即上面那 5 支) docs/claude/ 那 4 檔要逐一改(是 memory,會被每個 session 讀): docs/claude/memory/reference_dev_login.md:11/:12(登入帳密對照表) docs/claude/memory/feedback_playwright_screenshot_for_blind_fe.md:17 docs/claude/memory/project_fr039_distributed_file_agent.md:16 docs/claude/memory/reference_architecture_quick_notes.md:36
      • ⚠️ 這組密碼與現行 DEV 環境的登入密碼一致(docs/claude/memory/reference_dev_login.md 就是現行對照表,帳號 blsadmin / blsit)——「已換發」的說法對不上。開卡要寫「清字串前先確認 DEV 這組帳密換過沒有」。
      • 🔴 docs/features-site/ 那 39 檔是 MkDocs 產出的副本與 HTML——不要手改,改完來源 md 重跑 build 即可(M23 報告「網頁版不用手改」講的就是這個)。
    • 功能不能壞:改成「沒設環境變數就報錯」後,要手測確認有設環境變數的正常路徑(DEV/CI)不會被擋——安全措施不能擋正常操作者。那五支腳本至少各跑一次「有設變數」的路徑。
  • #73 套件倉庫與檔案儲存服務帳密寫在一份交接文件裡
    • 修法:帳密已換發,清掉該交接文件裡的殘留字串(M17 與 M15 模組頁引用的是同一份,清一次即可)。
    • 🔴 入口清單(已查證,FR-079 B2 F3 給的位置核對成立):
      • 來源檔:docs/features/FR-039-2606-distributed-file-agent/handoff/2026-06-18-FR039-handoff.md:89(Nexus 私有 PyPI 帳密,admin/<pw> 形式)+ 同檔 :59(一條 UPDATE public.… 指令裡嵌著 MinIO 的 access key / secret key / endpoint 192.168.50.171:9002)
      • grep 指令:git grep -lIF -- 'admin/<那個密碼>' → 6 個檔: docs/features/FR-039-2606-distributed-file-agent/handoff/2026-06-18-FR039-handoff.md(來源) docs/features/FR-039-2606-distributed-file-agent/handoff/2026-06-18-FR039-handoff.html(同目錄產出的 HTML) docs/features-site/docs/FR-039-2606-distributed-file-agent/handoff/2026-06-18-FR039-handoff.md(站台鏡射副本) docs/features-site/site/FR-039-2606-distributed-file-agent/handoff/2026-06-18-FR039-handoff/index.html(站台產出) docs/features-site/site/search/search_index.json(MkDocs 搜尋索引,憑證原文也在裡面——最容易漏的一個) docs/conversation-history/2026-06-19/fr039-agent-auth-v2/2331-5f8f5906-session-docs-features-fr-039.md
      • 只 grep 密碼部分(不含 admin/ 前綴)→ 13 個檔(多出來的 7 檔是那組密碼單獨出現的地方,runner 要一起看)
      • 🔴 處理順序:先改 docs/features/…handoff.md 來源 → 重跑 doc-site-build 產 HTML 與 features-site(含 search_index.json)→ 最後清 conversation-history。只改 md 不重 build,HTML 與搜尋索引裡的原文還在。
      • ⚠️ 那份文件自己註明「憑證檔是 gitignored」——但被寫在文件裡的那組憑證本身就在 commit 裡,這句註記是誤導,清的時候順手改掉。
    • 功能不能壞:純文件清理,無程式邏輯風險。但 Nexus 這把是供應鏈的根(有發布權就能推含後門的 jedi-common),若查出還沒換發要立刻回報。
  • #122 測試設定檔裡的簽發登入憑證金鑰+資料庫密碼+檔案儲存金鑰(工單 CM-1607;決策者已裁定開發環境那把簽發金鑰不需更換)
    • 修法:清殘留字串(簽發金鑰那把決策者裁「不需更換」,只清字串)。
    • 🔴 入口清單(已查證):
      • 簽發登入憑證金鑰(JWT):從 .env 的 JWT_SECRET_KEY 取值(36 字元,前 4 碼 b542)→ git grep -lIF -- '<值>' → 30 個檔,全在 docs/conversation-history/(與 FR-079 B2 F12 報的「34 處」一致——那是行數,檔數 30)。B2 點名的精確位置:docs/conversation-history/2026-05-20/ssp-import-export-phase2/1802c4fb-a3-writing-plans.md:6908 ⚠️ 與現行 .env 完全一致、是還在用的金鑰(B2 已確認,本棒複核仍一致)。決策者裁「DEV 這把不換」的理由是 scripts/installer/install.sh:1252 每套安裝各自跑 openssl rand -base64 32(gen_key 在 :211),外流的只是我們開發機那把、不會跟著出貨。
      • 資料庫密碼:同第 4-2 卡那組(cmmgr/cm_app),不要在這張卡重複清——4-2 卡負責。
      • 檔案儲存金鑰(MinIO):同 #73 那份 handoff 內的那組,由 #73 那半負責,本件不重複。
      • 另一把同形狀的:.env 的 TURNSTILE_SECRET_KEY(前 4 碼 1x00)→ 30 個檔(28 檔在 conversation-history、config/ 1 檔、repo 根 1 檔)。⚠️ 1x00… 是 Cloudflare 官方公開的測試用 dummy key(永遠通過),不是真憑證 —— 本件不必清,但開卡要註明「查到但判定非憑證」,免得 runner 白做或下次掃描再撈出來當假陽性。
      • 本棒確認零殘留、不必處理的:ANTHROPIC_API_KEY(版控 0 檔)/DETECTION_TOOL_ENCRYPTION_KEY(0 檔)/AI_PROVIDER_ENCRYPTION_KEY(0 檔)
    • 功能不能壞:純字串清理。簽發金鑰那把本機 .env 與部署文件裡還在用,只清版控對話紀錄裡的殘留、不要動 .env(動了所有人當下的登入 token 全失效)。
  • 這張卡的手測總清單(批次驗:五件一次清完、一次驗完,不要逐件來回):
    • 五件的憑證值各自 git grep -c -I -F -- '<值>' 零命中(值自己從 .env 或報告位置取,不要寫進卡片或 commit message)
    • 五支腳本(#72)各跑一次「有設環境變數」的正常路徑,確認沒被新加的報錯擋住;再各跑一次「沒設」確認會明確報錯而非靜默用真密碼
    • #73 的處理順序驗收:改完 docs/features/FR-039*/handoff/*.md → 重跑 doc-site-build → 再 grep 一次,確認 .html、docs/features-site/docs/、docs/features-site/site/ 與 search_index.json 四處都乾淨
    • docs/claude/memory/ 那 6 份(#72 的 4 份+#12 的 2 份)逐一開檔確認密碼改成「請查 .env」而非整段刪掉(那些是還在用的操作說明)
    • 抽查 2 個被清過的對話紀錄檔,確認憑證以外內容沒被誤刪
  • 需決策者先裁的:
    • D-b4-1:清版控殘留字串範圍要不要含「git 歷史」(用 git filter-repo 之類改寫 history)還是只清「現存檔案」?改寫歷史風險較高(會動到所有人的 commit hash)。我的建議:這批只清現存檔案的殘留字串,不改寫 git 歷史(憑證本身已失效,改歷史的成本與風險不成比例)。
§5

卡 4-2:資料庫管理員密碼(250 檔殘留+堵住還在寫入的那 1 支產生器)

  • 範圍:BE 主專案(已查證實數 250 檔,全在 BE;套件 monorepo 零命中);worktree 名建議 wt-fix-b4-db-admin-pwd;涵蓋 #12(M23-1 主案,合併 M16-7=M17-8 兩個具體實例)
  • 每件的修法(白話,一件一小節):
    • #12 資料庫管理員密碼(可繞過所有客戶隔離)散在 249 個版控檔案裡,其中一份湊成可直接複製貼上的連線指令
      • 修法:模組頁三步驟裡的「先堵住再寫進去的路」與「清掉已經寫進去的」這兩步材料標「未修」——這張卡要做的是:① 找出現在還有哪個地方會把這組密碼繼續寫進新的版控檔案,把那個來源改成不寫死密碼(改用「密碼請查 .env」的占位字樣,照 CLAUDE.md 憑證規範);② 清掉既有檔案裡的殘留字串。
      • 🔴 入口清單(已查證):
        • 取值方式(runner 自己做,不要貼進任何文件):.env 的 DB_PASSWORD(9 字元,前 4 碼 jedi)。REDIS_PASSWORD 與它是同一組值(M23 報告說「這組密碼同時也是快取服務的密碼」,本棒比對確認兩者相同)——所以清一次就兩件一起解決。
        • grep 指令:git grep -lIF -- '<該值>' → 250 個受版控檔案(M23 報告說 249,本棒重數 250,差 1 檔屬正常漂移)
        • 分布:docs/conversation-history/ 112 檔/docs/features/ 67 檔/docs/features-site/ 65 檔/docs/issues/ 2 檔/docs/claude/ 2 檔/docs/system-design/ 1 檔/docs/analysis/ 1 檔 (行數層級:git grep -c -I -F -- '<值>' 加總 2,012 行)
        • 🔴 「還在寫入的那條路」找到了,只有 1 支程式: docs/system-design/scripts/generate_db_schema_docx.py:34 — user="cm_app", password="<真密碼>" 直接寫死在連線參數裡。這支是產生資料庫結構 DOCX 的產生器,每跑一次就把密碼帶進產出。這就是 M16 第 7 條「一支產生文件的腳本把完整資料庫連線資訊寫死」那支。 修法:改讀 os.environ(缺就 raise),或沿用 repo 既有 .env 讀法。
        • docs/claude/ 那 2 檔(memory,每個 session 都會讀,優先清): docs/claude/memory/project_drive_sync_test_data.md:18(192.168.50.188:25432 / guidant_ai_stg / cm_app / <pw>) docs/claude/memory/project_ssp_doc_parser_progress.md:101(cm_app + cmmgr 同密碼 <pw>)
        • M23 報告點名「湊成可直接複製貼上的連線指令」那份:docs/analysis/2026-05-28-poc-db-migration-plan.md:73(export PGPASSWORD='<pw>')+ :95/:99(另兩處文字描述)。FR-079 B2 F1 也是指這一檔一行,優先處理。
        • 🔴 docs/features-site/ 那 65 檔不要手改——MkDocs 產出的鏡射副本與 HTML(含 docs/features-site/site/search/search_index.json,憑證原文也在搜尋索引裡)。改完來源 md 重跑 doc-site-build 即可。docs/features/ 下的 .html 同理。
        • 其他環境的同組密碼(唯讀盤點,不在本卡動):M23 報告寫這組密碼四處通行——DEV、出貨基線庫(188:25432 / guidant_ai)、以及兩座「已退役但還連得進去」的舊庫(188:25432/guidant_ai_stg、189:25432/guidant_ai_poc)。換密碼與關掉舊庫屬環境異動,要決策者明示,不在本卡。
      • 功能不能壞:改 generate_db_schema_docx.py 後要實跑一次產生 DOCX 的流程,確認只是密碼來源換了、產出內容不變。
  • 這張卡的手測總清單(批次驗):
    • 清完後 git grep -c -I -F -- '<該值>' 零命中(含 docs/features-site/site/search/search_index.json)
    • docs/system-design/scripts/generate_db_schema_docx.py 實跑一次(有設環境變數的正常路徑),確認 DOCX 產得出來且內容不變;再跑一次「沒設環境變數」確認會明確報錯而不是靜默用預設
    • 重跑 doc-site-build 後再 grep 一次(驗 features-site 與 HTML 都乾淨)
    • 抽查 2 個被清過的 md,確認憑證以外的內容沒被誤刪
  • 需決策者先裁的:
    • D-b4-2(原問題已解,改成新的):盤點已由本棒做完(250 檔清單與唯一寫入來源都在上面),原「要不要先盤點」的問題取消。新的問題是:這組密碼現在到底換過沒有? M23 報告把它列在「已換發」那 8 件裡,但 .env 現值與版控殘留值完全一致——若沒換,清版控只是打掃、洞還在(而且這個帳號是 BYPASSRLS、四處通行)。建議:先確認換沒換再決定這張卡的性質(打掃 vs 堵漏);換密碼本身屬環境異動(要動出貨基線庫與兩座舊庫),必須決策者明示放行,不在本卡。
§6

卡 4-3:移除掃描派工資料表裡的明文帳密殘留(程式修正)

  • 範圍:套件 jedi-detection(M03);worktree 名建議 wt-fix-b4-detection-plaintext;涵蓋 #70
  • 每件的修法:
    • #70 按下「開始掃描」時,解密後的客戶主機帳密會多存一份明文進派工資料表,從頭到尾沒有程式讀過,且無限累積
      • 修法:刪掉寫入那一行(已確認代理程式讀的是另一份當場重新解密的,刪掉不影響代理程式),再補一支清掉既有存量殘留資料的腳本/migration。
      • 🔴 入口清單(已查證,file:line):
        • 要刪的那一行:jedi-detection/jedi_detection/app/service/detection_orchestration_service.py:480
          params["_credentials"] = creds
          在 _dispatch_one()(:438-517)內,緊接在 :478-479 的 source_file 處理之後、:485-493 的 create_task() 之前。params 整包被塞進 agent_tasks.params(JSONB),所以明文帳密就落在那張表裡。
        • creds 是怎麼來的::215-217 呼叫 _resolve_credentials()(定義在 :1397-1430,:1429 是 json.loads(self._crypto.decrypt(config.credentials_encrypted)))。另一條路徑 :942-947(start_assignment_now 那支重跑流程)也拿了 creds 並在 :980 傳進同一個 _dispatch_one → 同樣會經過 :480。🔴 所以是「一行寫入、兩條路徑都會走到」,刪那一行兩條一起解決;不要以為要改兩處。
        • 零讀取查證:cd ~/Projects/Jedicogy/module/jedi-python-package && grep -rn '_credentials' --include="*.py" . | grep -v /.venv/ | grep -v /.claude/worktrees/ | grep -v credentials_encrypted → 命中只有::215/:218/:480(寫入側)+:603/:605/:799/:2273 四處註解提到它+:1370/:1391/:1395 的 _tool_requires_credentials(不同東西)。沒有任何地方讀 params["_credentials"]。 BE 側 grep -rn '"_credentials"' --include="*.py" . → 0 命中。
        • 🔴 代理程式真正拿到帳密的路徑(證實刪掉不影響 agent):BE infra/remote_agent/adapter/detection_task_payload_provider.py:68
          "credentials": self._resolve_tool_credentials(t.detection_tool_id, tenant_id),
          →_resolve_tool_credentials() 定義在同檔 :109-125,:124 自己做一次 json.loads(self._crypto.decrypt(config.credentials_encrypted))。這是心跳組裝時當場重新解密的第二份,與 agent_tasks.params._credentials 無關。 套件 :799 的註解也寫明「改由心跳組裝時當場注入(與 _credentials 同時機…)」——設計上早就分開了,:480 那行是舊的殘留。既有測試 test/test_agent_enrollment_service.py:376 test_heartbeat_attaches_pending_tasks_with_decrypted_credentials 測的就是這條活路徑。
        • 要清的存量資料:表 agent_tasks 的 params JSONB 欄(model 在 jedi-remote-agent/jedi_remote_agent/infra/agent_task/model/agent_task.py:45)。清法:UPDATE ... SET params = params - '_credentials' WHERE params ? '_credentials'(JSONB 刪 key,不動其他欄位)。先在 DEV 跑;STG/POC 要決策者明示放行(屬寫入類環境異動)。
        • ⚠️ 清理前先數一次(唯讀):SELECT count(*) FROM agent_tasks WHERE params ? '_credentials'; 在 DEV 跑,把數字寫進 Notion 卡當驗收基準。
      • 功能不能壞:刪 :480 前確認 agent 走的是 detection_task_payload_provider.py:68 那條(上面已證實)——手測要含「DEV 正常跑一次掃描,代理程式仍能正確拿到主機帳密並執行完成」。
    • 清理既有殘留資料的腳本:對照「資料庫寫入類異動」規範,這支清理腳本若要在 STG/POC 跑,要走決策者明示放行;DEV 可以先跑。
  • 這張卡的手測總清單(批次驗):
    • DEV 跑一次完整「開始掃描」流程,確認 agent_tasks.params 不再出現 _credentials key:SELECT count(*) FROM agent_tasks WHERE params ? '_credentials'; 應為 0(新單)
    • 同一輪確認代理程式仍拿得到帳密並執行完成(掃描跑到有結果,不是卡在「缺欄位」)
    • 再跑一次「立即開始/重跑」那條路徑(走 start_assignment_now → 同一個 _dispatch_one),確認也乾淨
    • 執行清理腳本後,DEV 該表既有殘留歸零(與開工前數的基準數字對照)
    • 跑一次套件既有測試(jedi-detection repo 內與 orchestration 相關的 test file)+ BE pytest test/test_agent_enrollment_service.py(心跳帶憑證那支,驗活路徑沒被弄壞)
  • 需決策者先裁的:
    • 清理腳本要不要套 STG/POC:DEV 可以先跑;STG/POC 屬寫入類環境異動,要決策者當次明示(CLAUDE.md 環境異動鐵律)。建議:先只清 DEV,STG/POC 等整批修正上版時一起放行。
§7

卡 4-4:Google 雲端硬碟金鑰——清殘留字串(金鑰重設另待決策)

  • 範圍:BE 主專案(已查證:14 檔全在 docs/conversation-history/,套件 monorepo 零命中);worktree 名建議 wt-fix-b4-gdrive-keys;涵蓋 #74
  • 每件的修法:
    • #74 Google 雲端硬碟應用程式密鑰與加密金鑰,散在 15 個版控檔案裡,是 Google 後台唯一一組、所有客戶環境共用
      • 修法:這件跟這批其他件不同——密鑰本身還沒重設(模組頁狀態「未修」,不在「已撤銷換發」的 6 件之列)。這張卡先做「清掉這 15 個檔案裡的殘留字串」這半段(跟其他清字串卡同性質);金鑰本身重設與「先寫好重新加密既有通行證的程式」這半段,材料裁定要先有重新加密程式才能換鑰,範圍較大、且要決策者明確指示才能做(比照 CLAUDE.md 環境異動鐵律「換密碼要決策者裁示」),這張卡不包含這半段。
      • 🔴 入口清單(已查證;落點是 BE 主專案,不是套件):
        • 取值方式(runner 自己做):.env 的三個 key
          • GOOGLE_DRIVE_OAUTH_CLIENT_SECRET(35 字元,前 4 碼 GOCS)→ 12 檔
          • DRIVE_TOKEN_ENCRYPTION_KEY(前 4 碼 0JGh)→ 10 檔
          • GOOGLE_DRIVE_OAUTH_CLIENT_ID(前 4 碼 1051,嚴格說是識別碼不是密鑰,但它與 secret 成對、一起清比較乾淨)→ 14 檔
        • grep 指令:git grep -lIF -- '<各值>'
        • 應用程式密鑰+加密金鑰的聯集 = 14 檔(M23 說 15,本棒實數 14,差 1 屬漂移);再加 client_id 的聯集 = 17 檔。全部落在 docs/conversation-history/,清單: 2026-04-21-google-drive-integration/…-part-03-of-14.md 2026-04-21-google-drive-integration/…-part-05-of-14.md 2026-04-21-google-drive-integration/…-part-08-of-14.md 2026-04-21-google-drive-integration/…-part-09-of-14.md 2026-04-28-to-04-30-survey-answer-arc/part-verbatim-01-of-03.md 2026-05-19/ssp-import-export-phase2-A0.1/e500cf61.md 2026-05-22/ssp-import-export-phase2/7bed5ff9-session-docs-features-ssp-import-ex.md 2026-05-27/participant-picker-framework-drive-fixes/0043-617066cd-session-projectplannin.md 2026-05-29/conversation-history-housekeeping/1035-c5c2e417-5-28.md 2026-05-31/evidence-classify-reports/1029-d0b63dd1-feature-ai-analysis-result-landing.md 2026-06-02/db-erd-tooling-convergence/2024-d2a67903.md 2026-06-14/fr038-oscal-redesign-wave1/2034-3fc2f2b1-oscal-v1-2-2-schema-c.md 2026-06-14/fr038-oscal-redesign-wave1/2034-6219587d-oscal-v1-2-2-schema-c.md (client_id 另外多 3 檔,runner 用 git grep -lIF 自取)
        • 套件側零命中:cd ~/Projects/Jedicogy/module/jedi-python-package && git grep -lIF -- '<各值>' → 0。所以是 BE 主專案獨有,落點確定。
        • 好消息:全部是 docs/conversation-history/ 底下的對話紀錄,沒有一個是現行正式設定檔。程式實際讀值走的是 .env → config/(GOOGLE_DRIVE_OAUTH_CLIENT_ID / _SECRET / DRIVE_TOKEN_ENCRYPTION_KEY 三個環境變數),清對話紀錄完全不會動到執行期路徑,風險極低。
        • ⚠️ 與 .env 現值完全一致(本棒比對)——這把確實還沒換,與 M23 第 3 條「未修」狀態相符。所以這張卡只是「打掃」,洞還在,要等金鑰重設那半(見 D-b4-3)。
      • 功能不能壞:純清對話紀錄字串。不要動 .env、不要動 config/ 下讀取那三個環境變數的程式(金鑰本身沒換,執行期要維持能讀到)。
  • 這張卡的手測總清單(批次驗):
    • 三個值各自 git grep -c -I -F -- '<值>' 零命中
    • 手測一次「連接 Google 雲端硬碟」(授權一次、上傳/讀一個檔),確認清字串沒動到執行期讀取路徑
    • 抽查 2 個被清過的對話紀錄檔,確認憑證以外內容沒被誤刪
  • 需決策者先裁的:
    • D-b4-3:Google 金鑰重設+重新加密既有通行證的程式,要不要現在排進這批(第 4 批)或另開一批/另一張卡處理?我的建議:這件密鑰本身还沒撤銷,性質跟這批其他「已換發只剩清字串」的件不同、範圍也更大(要先寫重新加密程式),建議切成獨立一張卡另外排(可以緊接在這張清字串卡後面做,但不要混在同一張卡裡拖慢驗收)。

§8

決策者要裁的事(彙整本批的 D)

  • D-b4-1:清版控殘留字串範圍要不要含 git 歷史(改寫 history)?建議:只清現存檔案,不改寫歷史。
    • 🔴 本棒補一個新事實影響這條判斷:docs/features-site/site/search/search_index.json 這個 MkDocs 搜尋索引裡有多把憑證原文。它是產出物,只要重跑 doc-site-build 就會乾淨,不需要改寫 git 歷史。同理 docs/features/*/*.html 與 docs/features-site/ 下 100 多檔鏡射副本都是「改來源 md + 重 build」就好。所以「只清現存檔案」的成本比原估低很多。
  • D-b4-2(原問題已解,換成新的):原問題是「249 檔清單要誰盤點」——已由本棒盤完(清單在 4-2 卡內),該問題取消。 新問題:這批標「已撤銷換發」的憑證,有三把與現行 .env 完全一致,到底換過沒有?(資料庫管理員密碼、系統管理員登入密碼、Google AI 服務金鑰) 若沒換,這三件就不是「打掃」而是「洞還在」,清字串只解決一半。建議:開卡前先各實查一次(資料庫那把試連一次即知;Google 那把到 Cloud Console 看;系統管理員那把用 DEV 登入試一次),再決定卡片性質與優先序。換密碼本身屬環境異動(要動出貨基線庫與兩座舊庫),必須決策者明示放行,不在這批。
  • D-b4-3:Google 金鑰重設+重新加密程式要不要現在排進這批?建議:切獨立一張卡另外排,不要混進這張清字串卡。
    • 本棒補查證實:這把確實還沒換(與 .env 現值一致),且 14 檔殘留全在對話紀錄、沒有一個是現行設定檔,所以「清字串」那半風險極低、可以先做;「重設金鑰」那半照原建議另開卡。
  • D-b4-4(新增):#10 那「4 把外部程式碼平台通行證」只找到 1 把真值(另 1 把是 glpat-fake… 範例值,其餘 2 把在 BE 與套件 monorepo 都零命中,GitHub 系列的 token 前綴也全零命中)。要不要請 M15 報告作者或 CM-1607 工單補出另 2 把的實際位置?建議:先照查到的 1 把清掉,另 2 把標「查無、待補位置」,不要為了湊數字擋住這張卡。