本卡屬 FR-084(母卡 CM-XXXX,建完補號),唯一一棒:jedi-integrity 套件本體(jedi monorepo)。只掃不修。
掃 jedi-integrity 套件(21 檔、約 3,250 行)。它是落地版產品的防竄改機制:開機時逐檔核對原廠簽過名的檔案清單,不符就拒啟並鎖定;服務跑起來後每 4 小時左右抽查一次;被鎖的機器只有原廠簽的一次性解鎖檔能解。要找的是:這套保護能不能被繞過、被關掉、或被拿來搗亂(讓正常客戶的機器被鎖、或鎖了解不開)。只找問題、不修問題。
這支的風險形狀跟前面掃的七支都不同。前面是「誰能看到不該看的資料」,這支被攻破的後果是保護失效:產品被改了卻沒人知道、鎖定紀錄被洗掉、或反過來被人拿來把正常客戶鎖死。FR-076 曾順手撞到它一條 HIGH(CM-1572 解壓炸彈,已修),但從沒專門掃過。宿主那半邊只有一支 357 行的接線檔加 5 個呼叫點,不值得一棒,首腦已讀完寫在下方「已知背景」。
首腦讀過全部 21 檔與宿主接線後的起點,未經驗證。工具不會照這份清單走(連續三個 arc 如此),工具沒答的你要自己開檔查,並在報告標明「(工具未報,runner 人工查證)」。
manifest.py:70-85 enforcement_enabled():is_packaged() 回 False 就跳過全部檔案核對,只剩「有沒有鎖定標記檔」一道。宿主的判定在 common/util/resource_path.py:26-28(看 __compiled__ 全域變數或 sys.frozen)。要追:在已編譯的產物上,有沒有辦法讓這個判定回 False?force_enforcement 只能開不能關(config.py:78,理由在 manifest.py:70-85 docstring),這個主張成立嗎?startup_gate.py:206-214 只讀 FS 標記(tamper_marker.read_marker);TamperEventRepo.exists(infra/tamper_event_repo.py:96)全套件與宿主零呼叫者。README 與 tamper_marker.py 檔頭都宣稱「DB 被還原時 FS 仍在,FS 被刪時 DB 仍在」,但 DB 那一半從不參與啟動判定。要追:拿到主機 shell 的人刪掉 /opt/guidant/pki/.integrity-tamper 一個檔,服務是不是就正常起來?tamper_sync_service.py 檔頭第 1 點自己也承認了這個洞,只說「窗口縮到最小」。這條若成立,判嚴重度時要看:竄改者本來就有主機 shell(他改檔案就是靠這個),所以「刪標記」的成本是不是等於零?unlock.token、已用 nonce 帳 used-nonces.json(unlock.py:47-56,全在 config.marker_dir)。_read_used_nonces()(unlock.py:83-100)毀損時刻意回空清單(docstring 自陳「取寬鬆側」)。要追:把 used-nonces.json 改成亂碼,一張用過的解鎖檔能不能再用一次?它還受效期、指紋、事件編號三關擋,那三關擋得住「同一台機器、同一次事件、效期內」的重放嗎?另外 _MAX_USED_NONCES = 1000 滿了丟最舊的(:60、:114)。jedi_license_runtime.common.engine,它 import cryptography(Ed25519)。啟動閘門只核對 core+resources 兩層(config.py:49 DEFAULT_BOOT_LAYERS),thirdparty 層只由 4 小時一次的抽查隨機抽 200 檔(config.py:38)。build 腳本 scripts/build/build_release.sh:365 的註解列出 pdfminer.six → charset_normalizer, cryptography 在「被排除編譯的相依封閉集合」內,代表 cryptography 很可能落在 thirdparty 層。要追(對照 BE repo 的 build 腳本查證,不要猜):如果 cryptography 在 thirdparty,換掉它的 .so 讓 Ed25519PublicKey.verify 永遠通過,啟動閘門是不是完全失效、且平均要等兩小時、抽中機率還取決於 thirdparty 檔數?0.0.0.0、任何來源都放行。 lockdown.py 的殼是 stdlib ThreadingHTTPServer,config.py:93 預設 lockdown_bind_host = "0.0.0.0",回應帶 Access-Control-Allow-Origin: *(lockdown.py:182)。它把機器指紋與事件編號吐給任何連得到的人。要追:這兩個值是解鎖流程的憑證材料之一(unlock.py 第二、三關比對的就是它們),對外公開有沒有問題?殼本身有沒有可以塞爆的地方(ThreadingHTTPServer 每連線一個 thread、無上限)?lc_report.py:69-79 用 urllib.request 打 config.lc_base_url,header X-API-Token 帶 lc_api_token;宿主接線把 LICENSE_ACTIVATION_SERVER_URL 的預設值寫成 http://127.0.0.1:5062(common/integrity/adapters.py:55),是明文 http。要追:正式部署時這個 URL 是 https 嗎?token 從哪來、客戶機器上會不會有這把 token(若有,等於每套安裝都拿著能打 LC 內部 API 的鑰匙)?回報的 detail 內含 trigger_context,宿主端會塞登入帳號、來源 IP、活躍 session 清單、log 尾段 50 行(adapters.py:212-330、forensic.py:108-129)——log 尾段裡有沒有可能夾帶密碼或 token?termination.py:76-99:worker 收到 tamper 判定後對登記的 master pid 送 SIGQUIT,判準是 os.getppid() == master_pid;非 gunicorn 時若 pgid == pid 就 killpg 整個 group。要追:register_gunicorn_master 是模組全域、無保護(:59-70),有沒有辦法讓它登記到錯的 pid?失敗路徑 os._exit(1) 前所有例外都吞(:100-115),有沒有「訊號送不到、自己也沒退出」的組合?unlock.py:172-183 用本機 datetime.now(timezone.utc) 比 expires_at,naive 時間一律當 UTC。要追:改本機時鐘就能讓過期的解鎖檔復活嗎?expires_at 缺欄位或是字串 "9999-12-31" 時各走哪條?(原廠簽發端在 license_center repo,不在本棒範圍,但 payload 形狀要對得上。)runtime_check.py:220-241 _init_hotpath_cache():業務埋點初始化時 manifest 讀不到或驗不過,不判竄改、退化成空集合只 log WARNING(docstring 自陳「誤殺代價高於漏掉」)。要追:這條「先刪 manifest 再走一次匯出流程」會不會讓埋點永久失效(cache 一旦是空清單就不再重讀)?抽查 job 是 4 小時一次,中間這段時間埋點等於沒有。harness/dev_app.py(410 行)。 它自帶一套 Ed25519 簽章驗證器(:107-140),與正式產品的驗章實作是兩份程式碼。密鑰專項最常撈到東西的地方:寫死的金鑰、tmp 目錄權限、--keep 保留的目錄。注意:FR-076 L1 提到此檔第 103 行曾有同款無界解壓,CM-1572 已改成 _bounded_unpack(:95-105),驗證那一版是否真的補齊。common/integrity/adapters.py(357 行)+ __init__.py(33 行),5 個呼叫點:main.py:108-113(建 context、跑啟動閘門,在 create_app() 之前)、core/app_factory.py:349-357(register() 接 DB writer)、core/scheduler.py:30-60(4h 抽查用 date trigger 自排)與 :295-330(啟動後 FS→DB 補同步一次)、api/license/__init__.py:50(授權匯入的埋點)、app/oscal/service/export/ssp_export_app_service.py:92(SSP 匯出的埋點)。jedi_license_runtime.common.engine.verify_payload(Ed25519+硬編公鑰,三環境公鑰全編進同一份,見總表 §3.1 第 6 項);指紋 = /etc/machine-id 的 sha256(容器內要掛宿主的 machine-id,docker/production/docker-compose.yml:155-168);打包判定 = common/util/resource_path.py:26。