卡:CM-2267(母卡 CM-2019)。2026-09-27 在 190(guidant-ai-be:1.21.0b4、agent 1.1.0b2)與本機四個 repo 驗。只驗不修。
| 結果 | 條數 | 條號 |
|---|---|---|
| ✅ 通過 | 12 | #10 #11 #26 #71 #72 #82 #93 #96 #103 #122 #132 #185 |
| ⚠️ 修法在出貨線成立,但主線仍有殘留 | 3 | #12(出貨線乾淨)、#73、#74 |
| ❌ 未通過 | 2 | #95、#125 |
| ⚠️ 打折(只驗到一部分) | 1 | #133 |
最重要的兩件事:
app/service/user_service.py 的 get_users 裡 logger.info(f"Get users: {users}")。這行寫到容器標準輸出,沒進 app.log 檔也沒進資料庫。fix/security-b1,還沒合回 feature/review 與 main。 主 checkout 上 Google 四把金鑰與 JWT 簽章金鑰的殘留都還在,main 上也在。只要 fix/security-b1 如期合回,這幾條就會通過;但在那之前,任何從 main 或 feature/review clone 的人都拿得到。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 計檔數。值只在記憶體比對,本報告只寫檔數。X-Forwarded-For、叫 AI 儀表板查使用者),觸發字串帶一個本卡專用標記,再到 docker logs、/srv/guidant-ai/log/app.log、api_logs、system_logs 找標記與機密。DB 全部唯讀查詢。12dee3157、FE 55ca905、agent 1b71a0c、套件 d94a472d(皆 fix/security-b1)。| 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,偽造標頭沒被當真。 |
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 |
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 行)。sqlalchemy.orm 那處改成 INFO 仍不夠,是否算「還剩」的那兩處之一,要對原報告 M04-9 確認。驗證清單把 #125 對到 CM-2178、#133 對到 CM-2180。兩張卡的標題分別是「合規範本七個寫入入口補權限」與「刪框架版本前改查資源庫」,都跟本條無關,應該是「總表第 N 項」與「SUMMARY 第 N 條」兩套編號撞號。本卡這兩條照白話自擬測法。
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 測試帳號不同。不是殘留憑證,記下避免之後掃描誤判。
出貨線全部是 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 相同。
| 檔:行 | 形狀 | 判定 |
|---|---|---|
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 已判定不清 |