b4 驗證 V4——主機/版控殘留與日誌內容(18 條)

b4 驗證 V4:主機/版控殘留與日誌內容

卡:CM-2267(母卡 CM-2019)。2026-09-27 在 190(guidant-ai-be:1.21.0b4、agent 1.1.0b2)與本機四個 repo 驗。只驗不修。

§1

結論

結果 條數 條號
✅ 通過 12 #10 #11 #26 #71 #72 #82 #93 #96 #103 #122 #132 #185
⚠️ 修法在出貨線成立,但主線仍有殘留 3 #12(出貨線乾淨)、#73、#74
❌ 未通過 2 #95、#125
⚠️ 打折(只驗到一部分) 1 #133

最重要的兩件事:

  1. #95 還沒修好。 AI 儀表板在問「使用者清單」時,後端一行紀錄把六個帳號的完整資料原文寫進容器紀錄,含密碼雜湊、加密鹽值、超級管理員旗標。回給畫面的內容乾淨,但紀錄那一端沒有遮。出處是 jedi-iam app/service/user_service.py 的 get_users 裡 logger.info(f"Get users: {users}")。這行寫到容器標準輸出,沒進 app.log 檔也沒進資料庫。
  2. 版控殘留的清理全在 fix/security-b1,還沒合回 feature/review 與 main。 主 checkout 上 Google 四把金鑰與 JWT 簽章金鑰的殘留都還在,main 上也在。只要 fix/security-b1 如期合回,這幾條就會通過;但在那之前,任何從 main 或 feature/review clone 的人都拿得到。
§2

驗法

  • 版控:四個 repo(BE、FE、evidence-agent、套件 monorepo)用 git grep -I 對兩個 ref 各掃一次:fix/security-b1(出貨線,190 的 b4 就是從這條出的)與當下 HEAD(feature/review)。兩種掃法:
    • 字串形狀:AKIA…、AIza…、glpat-…、sk-…、lsv2_…、BEGIN … PRIVATE KEY、postgres://帳號:密碼@、redis://:密碼@、GOCSPX-…、ghp_…、ntn_…、JWT 三段式、password="…" 這類賦值。
    • 真值比對:從本機 .env 取 19 把現值,以及從修正卡 commit 的刪除行取出被清掉的舊值,逐一 git grep -F 計檔數。值只在記憶體比對,本報告只寫檔數。
  • 190:用 API 實打觸發(登入、帳號欄帶換行、密碼帶雙引號、寄測試信到打不通的主機、偽造 X-Forwarded-For、叫 AI 儀表板查使用者),觸發字串帶一個本卡專用標記,再到 docker logs、/srv/guidant-ai/log/app.log、api_logs、system_logs 找標記與機密。DB 全部唯讀查詢。
  • 分支版本:BE 12dee3157、FE 55ca905、agent 1b71a0c、套件 d94a472d(皆 fix/security-b1)。
§3

結果表

n CM 一句 結果 證據
10 CM-2048 GitLab 通行證殘留 ✅(主線另計) 四 repo 的 glpat- 形狀:出貨線 0 檔。HEAD 剩 2 檔,就是 CM-2048 清掉的那把真值(已撤銷),等合回。
11 CM-2048 四把 AI 服務金鑰殘留 ✅(主線另計) .env 現行 OpenAI、Anthropic 兩把:四 repo 兩 ref 全 0。sk-/lsv2_ 形狀在出貨線只剩套件測試檔的假值(見附錄 B)。現行 Google AI 金鑰算進 #74 那一格一起講。
12 CM-2049 資料庫管理員密碼 249 檔殘留 ⚠️ 出貨線乾淨 從 CM-2049 的刪除行取出被清的那組密碼:四 repo 兩 ref 全 0,含 HEAD。postgres://帳號:密碼@ 形狀在出貨線剩 30 行,全部是 {…} 變數組字串或文件裡的 ** 遮罩範例,沒有字面密碼。之所以標 ⚠️,是 CM-2049 本身寫明「密碼未換發」,所以這組密碼仍有效。
71 — 自擬 產生文件腳本寫死展示環境 DB 密碼 ✅ docs/system-design/scripts/generate_db_schema_docx.py 在出貨線改讀 DB_SCHEMA_DOCX_DB_PASSWORD,沒設就中止。被清的舊值在四 repo 兩 ref 全 0。
72 CM-2048 管理員密碼寫死三支腳本、環境變數沒設就默默用 ✅ 五支腳本在出貨線都改成「沒設就 raise SystemExit 並提示變數名」。預設測試帳號密碼字串在四 repo 兩 ref 全 0。
73 — 自擬 套件倉庫與檔案儲存帳密寫在交接文件 ⚠️ 出貨線乾淨、主線 13 檔 被清的舊值在出貨線 0 檔。HEAD 仍有 13 檔,含交接文件原檔、它的 HTML、文件站鏡像、search_index.json,另有 8 份對話紀錄。
74 CM-2051 Google 雲端硬碟密鑰與加密金鑰散在 15 檔 ⚠️ 出貨線乾淨、主線仍在 .env 現值比對,出貨線四把全 0。HEAD(以及 main)還有:雲端硬碟 client secret 12 檔、token 加密金鑰 10 檔、client id 14 檔、Google AI 金鑰 9 檔,全在 docs/conversation-history/。
122 — 自擬 JWT 簽章金鑰、DB 密碼、儲存金鑰在同一測試設定檔 ✅(主線另計) 出貨線 .env 現行 JWT 簽章金鑰 0 檔。190 的簽章金鑰與本機 DEV 的不同(比對雜湊,沒比值),符合「每套各自產生」。HEAD 仍有 30 檔 DEV 金鑰殘留(docs/conversation-history/),等合回。
125 卡號對不上 兩種紀錄詳細程度寫死最詳細 ❌ 見下方「#125 細節」。190 開機時 ORM 一次倒出約 6,000 行 INFO 對映紀錄,佔一小時紀錄量九成。穩態每 5 秒一行背景工作 INFO。表列卡號 CM-2178 講的是範本權限,跟本條無關。
132 CM-2226 分類器六個套件沒鎖版本沒校驗碼 ✅ guidant-classifier 映像的建置步驟是 pip install --require-hashes --no-deps -r requirements.txt。容器內 requirements.txt 24 個套件全帶 ==,共 542 個 --hash。pip freeze 25 個套件版本與鎖檔一致。
26 — 自擬 登入時帳密與通行證原文寫進紀錄檔與 DB ✅ 兩輪。第一輪實打 4 次登入(成功、失敗、需 MFA、帶雙引號密碼),api_logs 四筆密碼都是 ***。補驗(2026-09-27):子租戶 4 的 blsfrank 完整走一次「帳密 → email OTP → 拿到通行證 → 帶通行證打 API」。用拿到的 access/refresh token 各取尾段 40 字元、加上密碼,到 login_logs、api_logs、system_logs、docker logs、app.log 五處找:全部 0。api_logs 的 OTP 驗證那筆回應記成 "access_token":"***","refresh_token":"***";全表 JWT 三段式 0 筆。login_logs 登入完仍是 0 筆,原因是出貨線 jedi-iam 整條 login_logs 寫入鏈已經沒有呼叫端(程式註解寫明「零呼叫端,留著只為維持 metadata」),這張表不再被寫,不是驗不到。login_tokens 只存 jti,沒有 token 本體。
82 — 自擬 密碼含雙引號時只遮前半截 ✅ 送密碼 Q1"tail-<標記>-SECRETPART:api_logs 該筆 request 裡密碼是 ***,找不到 SECRETPART;system_logs、app.log 也找不到。
93 CM-2055 帳號欄塞換行,送到客戶 SIEM 變兩筆 ✅(本機端) 不登入,帳號填 …\nFAKE admin granted super_admin…。app.log 與 docker logs 沒有任何一行以偽造內容開頭。api_logs 該筆 user_name 空白,沒有換行。190 開著 GELF over TCP 轉送,但目標 192.168.50.150:5140 拒絕連線,所以收端沒驗到。轉送前的跳脫在出貨線 jedi_api_log/forwarding/common/forwarder.py 有 _CONTROL_ESCAPES。
95 CM-2064 儀表板回應夾帶鹽值與超級管理員旗標 ❌ 用 admin 叫一次 AI 儀表板「列出所有使用者的全部欄位」:回應 244KB、200,裡面 salt/is_super_admin/password 都是 0 次,回應端修好了。但同一個請求讓 docker logs guidant-api 多了一行 [user_service.get_users:98] Get users: [UserEntity(...)],六個帳號的 password=、salt=、is_super_admin= 都是原值。該行只在容器標準輸出,app.log 與 system_logs 各 0 筆。送給外部 AI 的樣本本卡看不到(服務沒記送出內容),沒驗。
96 — 自擬 寄信失敗把整組郵件設定含密碼寫進紀錄 ✅ admin 對 /mail/test 送一組指向打不通主機的設定,密碼帶標記。等 60 秒逾時後回 500。紀錄有兩行帶標記:smtp_mail_adapter 那行只有主機、埠、帳號;錯誤處理器的 5xx context 那行 body 裡 secret 是 ***。三處(docker logs、app.log、system_logs)找不到密碼。附帶發現:打不通的主機要等 60 秒,最後回 500,不是明確的「寄信失敗」訊息。
103 — 自擬 寫 DB 日誌失敗會連帶弄壞請求 ✅(讀碼) 出貨線 jedi-common logger/db_log/db_handler.py 的 emit 整支包 try/except,失敗走 self.handleError(record)。打折:190 上沒辦法在不改環境的情況下讓 DB 寫入失敗,所以只讀碼。
133 卡號對不上 竄改回報送出主機紀錄最後 50 行 ⚠️ 打折(讀碼) 出貨線 main.py:131 把 infra/support/diag_masking.mask_log_lines 注入給竄改偵測;jedi_integrity 送出前經它遮蔽。190 的 integrity_tamper_events 0 筆,沒發生過竄改,也不能為了驗而竄改。表列卡號 CM-2180 講的是刪框架版本,跟本條無關。
185 CM-2171 操作日誌來源 IP 全記成前端代理 ✅ 從本機經 VPN 打 API:api_logs.source_ip 記 10.8.0.6,就是本機的 VPN 位址,不是 172.x 容器位址。再帶 X-Forwarded-For: 1.2.3.4 打一次:仍記 10.8.0.6,偽造標頭沒被當真。
§4

#125 細節

190 的 api 容器 ENV=PRD、RUN_ENV=prod、DEBUG=false,沒設 LOG_LEVEL,所以走 jedi-common config_prod.py。

量 數字
開機後一小時 docker logs 行數 7,191
其中 sqlalchemy.orm.* INFO 約 6,050
穩態十分鐘行數 367
app.log 今天 5.3MB
昨天整天 app.log 7.0MB
  • 大宗是 ORM 對映紀錄:sqlalchemy.orm.mapper.Mapper、relationships、strategies.LazyLoader 在開機那一分鐘倒出約 6,000 行,全是 INFO。config_prod.py 裡 sqlalchemy.orm 設的是 INFO,註解說「調回 INFO」,但 SQLAlchemy 的對映設定紀錄本身就是 INFO 級,這個級別擋不掉。要擋得設 WARNING。
  • 穩態主要是背景工作:drive_sync_worker 每 5 秒一行「system context entered」。
  • app handler 級別寫死 DEBUG:config_prod.py 的 handlers.app.level 是 DEBUG,目前靠 logger 的 LOG_LEVEL 擋著所以沒冒出 DEBUG 行(app.log 內 DEBUG 0 行)。
  • 本條原文說「三處已改一處,還剩兩處」,SUMMARY 標已修。實況是 pymongo 那處改了,sqlalchemy.orm 那處改成 INFO 仍不夠,是否算「還剩」的那兩處之一,要對原報告 M04-9 確認。
§5

表列卡號對不上的兩條

驗證清單把 #125 對到 CM-2178、#133 對到 CM-2180。兩張卡的標題分別是「合規範本七個寫入入口補權限」與「刪框架版本前改查資源庫」,都跟本條無關,應該是「總表第 N 項」與「SUMMARY 第 N 條」兩套編號撞號。本卡這兩條照白話自擬測法。

§6

範圍外發現(不修,記下)

  • OTP 驗證碼原文留在操作日誌:api_logs 的 /otp-verify 那筆 request 欄記的是完整六位數驗證碼,沒遮。驗證碼一次性、用過即失效,風險低於密碼;但它和帳號 uid 同一筆,在有效期內看得到操作日誌的人理論上可以搶先用。遮罩名單可考慮加 code。

  • /mail/test 打不通時回 500:對方主機不回應時,要等整整 60 秒才回 COMMON_500001,使用者看不到「郵件伺服器連不上」。SMTP 轉接器對 TimeoutError 回 False,但例外還是往上冒到錯誤處理器。

  • jedi-iam 登入成功會印整份 capability 清單:capability_service.get_capabilities:55 每次把所有能力點的完整物件寫成一行 INFO,一次約數 KB。屬 #125 的紀錄量問題。

  • jedi-iam harness 有寫死的 smoke 密碼:jedi-iam/harness/dev_app.py:330。註解寫明「harness 本機專用,非任何環境憑證」,值跟 DEV 測試帳號不同。不是殘留憑證,記下避免之後掃描誤判。

§7

附錄 A:主線(HEAD=feature/review)仍在的殘留

出貨線全部是 0。以下是等 fix/security-b1 合回才會消失的:

項目 條號 HEAD 檔數 位置
Google 雲端硬碟 client secret #74 12 docs/conversation-history/
Google 雲端硬碟 token 加密金鑰 #74 10 docs/conversation-history/
Google 雲端硬碟 client id #74 14 docs/conversation-history/
Google AI 金鑰 #11/#74 9 docs/conversation-history/
GitLab 通行證(已撤銷) #10 2 docs/conversation-history/
DEV JWT 簽章金鑰 #122 30 docs/conversation-history/
套件倉庫/檔案儲存帳密 #73 13 FR-039 交接文件的 md、html、文件站鏡像、search_index.json,另有 8 份對話紀錄

main 上 Google 四項的檔數與 HEAD 相同。

§8

附錄 B:出貨線上被形狀掃描抓到、判定不是憑證的

檔:行 形狀 判定
BE scripts/build/build_image.sh:159、:164 私鑰標頭 註解,說明格式
BE test/test_detection_secret_params.py:194、test/test_diag_log_line_masking.py:45、:56 私鑰標頭 測試假值
BE infra/support/diag_masking.py:137、:292 DB 連線字串 註解,說明遮罩規則
BE docs/conversation-history/ 下 28 行 DB 連線字串 全部是 {creds[...]}/{secret[...]} 變數組字串
BE docs/features/FR-120-*/scan-U10.md、scan-U11.md DB 連線字串 文件用 ** 表示遮罩
agent test/test_*_connector.py 四處 私鑰標頭 內容是 fake/fakekeydata
套件 jedi-*/harness/dev_app.py 十處 DB 連線字串 本機 harness 專用庫,連 localhost:55xx/54xx
套件 jedi-evidence-classification 三個測試檔 sk-ant-… 測試用假金鑰,配 t0ken-for-tests-1234
FE JobExecutionDrawer.vue:1059、agent install.sh:2066、套件多處 _NOT_MET_TOKEN 等 TOKEN = "…" 值是識別字,不是密鑰
BE、FE、套件都有 TURNSTILE_SECRET_KEY 同值 .env 比對命中 Cloudflare 公開測試金鑰,CM-2048 已判定不清