FR-086 · 需求索引 · 本頁由 build 掃資料夾生成
✅ 三棒全部掃完並經首腦驗收(2026-09-12 arc 收口);發現已處理——高風險七條全數已修隨 1.21.0 出貨,M02-8 借用別家儲存設定、M02-11 雲端儲存未加密連線裁定不修,合計 14 條發現(HIGH 6/MEDIUM 4/LOW 4)。這條路四層全空已完整證實——網址入口只驗登入、業務邏輯零檢查、資料查詢無範圍條件、資料表無租戶欄位且隔離關閉(B1b 已用 DEV 資料庫實查坐實:relrowsecurity = f、policy 0 條、17 欄位無 tenant_id)。另有「任何登入者可取得本租戶物件儲存帳密明文」(B2-A,HIGH)。開卡五個疑點三成立兩推翻;路徑穿越兩側各證偽一次,但是執行順序意外擋住的、不是刻意防的。B1b 已驗收:首腦複核資料庫實查四個數字全對、三後端不一致與系統指令安全性逐項開檔核對屬實。
B1 掃完了。結論:只要有一個能登入的帳號,再知道某個檔案的編號,就能把別家客戶的檔案下載走、線上預覽、永久刪除——系統從頭到尾只檢查「你有沒有登入」,從來沒檢查過「這個檔是不是你的」。
而且還能替別人的檔案換發一張免登入的下載通行證,直接把網址傳給公司外面的任何人。另有一條:上傳一個網頁檔,別人一點預覽就會在我們自己的網域裡執行。
四條全是同一個病:認得出你是誰,但不管你能碰什麼。四條共用同一道修法。
好消息:卡片列為「本棒最重要」的路徑穿越經查不成立——但它是靠執行順序意外擋住的,不是有人刻意防的,重構就會長回來。
B2 回答了 B1 留下的問題:宿主那半邊有沒有補上檢查?沒有。 合起來是一條四層全空的路徑——網址入口只驗登入、業務邏輯零檢查、資料查詢無範圍條件、資料表連租戶欄位都沒有(隔離也是關的)。沒有任何一層會問「這個檔是不是你的」。
B2 另外人工追出一條同級的:任何登入者打一支查設定的 GET,就拿到自己租戶物件儲存的位址、帳號、密碼明文——拿到後可完全繞過系統直接連上倉庫,應用層再怎麼補都管不到。三處小改可修。
B1b 補上最後一塊拼圖,而且證據等級最高:不是讀建表腳本推論,而是實際連上 DEV 資料庫查出來的——存檔案那張表的隔離開關是關的、隔離規則零條、17 個欄位裡連「這是哪家客戶的」都沒有。另查出主機硬碟後端刪檔時不清轉檔快取(物件儲存那邊會清),等於刪了沒真的刪掉。
產品裡所有「上傳檔案、下載檔案、預覽檔案」都經過這支套件。它決定三件事:
這個 arc 要回答:有沒有辦法把檔案寫到不該寫的地方、有沒有辦法下載到別人的檔案、儲存憑證會不會外洩。
| # | 看到什麼 | 為什麼可疑 |
|---|---|---|
| 1 | 上傳時用使用者可控的字串拼出存檔路徑(api/routes/upload_file_route.py:101) |
那兩個值來自請求內容,沒看到 ../ 過濾 |
| 2 | 任何登入者可替任意檔案編號換一張下載憑證(:150-156,只掛 @auth_required) |
中間沒有「這個檔是不是你的」檢查 |
| 3 | 查檔案的資料層完全沒有範圍條件(infra/repository/upload_file_repo_impl.py:41-45) |
加上那張表沒有租戶欄位,猜到編號就取得檔案 |
| 4 | 檔名處理很薄(common/utils/file_utils.py:16) |
無副檔名白名單、無檔名正規化、無單檔大小上限 |
| 5 | 物件儲存憑證存在資料庫 JSONB,加密預設為關(app/upload_file/service/managed_file_upload_service.py:99-119) |
secure=v.get('secure', False) |
第 1、2、3 條若都成立,合起來就是一條完整的跨租戶取檔路徑——這正是三棒要分別驗證的。
| 棒 | 範圍 | 檔數 | scanRoot | 為什麼獨立一棒 |
|---|---|---|---|---|
| B1 HTTP 邊界與契約 | api/+app/+ports/+common/+plugin.py |
35 | jedi monorepo | 上傳/下載/預覽/發憑證四條對外路徑同一條呼叫鏈,三個最直接的疑點都在這 |
| B2 宿主接線 | app/upload_file/+infra/upload_file/+core/upload_file_wiring.py+di_containers/upload_file/ |
10 | BE repo | 檔數少但密度最高:用哪個後端、拿誰的憑證、算不算你的檔全在這 |
| B1b 儲存後端與持久層 | infra/+domain/+migrations/ |
26 | jedi monorepo | 檔案真正被讀寫的地方;路徑穿越真正寫出界的動作在這裡 |
三棒是一條路徑的三段:B1 查「請求進來時路徑是不是使用者可控」→ B2 查「挑後端時有沒有驗歸屬」→ B1b 查「落地時有沒有兜底」。驗收時要互相對照。
偵察建議過一棒「remote_agent 檔案通道」,首腦查證後砍掉——它與 FR-077 的 CM-1594(R3 宿主接線,17 檔)範圍完全重疊。要掃那條路請派 CM-1594,不要在本 arc 重開。
跨 arc 總表的排名表寫「61 檔/2,510 行」,實際是 68 檔/2,877 行(排除測試與報告目錄後),低估約 11%。已據實際數字切棒。
| 棒 | 卡 | 檔 | 面板 | 發現 | 報告 |
|---|---|---|---|---|---|
| B1 HTTP 邊界與契約 | CM-1652 | 35 | ✅ 18 票全投出,verified |
4 條(HIGH 3/MEDIUM 1)+ 人工另記 1 條 LOW | scan-B1-http-boundary.md |
| B2 宿主接線 | CM-1653 | 10 | ✅ 36 票全投、verified |
範圍內 1H +人工 4 條(1H 1M 1L 1 記錄) ⚠️ 另 9 條範圍外全是舊案重複 |
報告 |
| B1b 儲存後端與持久層 | CM-1654 | 26 | ✅ 6 票全投、verified⚠️ 工具達標 0 條 |
人工 5 條(HIGH 1/MEDIUM 2/LOW 2) 🔴 資料庫實查坐實無隔離 |
報告 |
三棒合計 14 條發現:B1 五條(HIGH 3/MEDIUM 1/LOW 1)、B2 五條(HIGH 2/MEDIUM 1/LOW 1/記錄 1)、B1b 五條(HIGH 1/MEDIUM 2/LOW 2)。範圍外的舊案重複九條不計入。
最值得修的前三名:
relrowsecurity = f、policy 0 條、17 個欄位**無 tenant_id`。必須套件側與宿主側同一張卡一起修,拆開等於沒修。upload_files 加租戶欄位+開啟資料庫隔離(HIGH) — 第④層防線。注意不可只做這條:建表腳本已警告「開了隔離卻沒有 policy 會讓所有下載當場 404」,且系統共享資產會被一起擋掉。需 migration + 資料回填,獨立排。開卡時的疑點,五條裡三條成立、兩條被推翻:
| 開卡疑點 | 結果 |
|---|---|
| 1. 上傳時用使用者可控字串拼存檔路徑 | ❌ 推翻(B1 從檔名側、B1b 從落地側各證偽一次;但是執行順序意外擋住的,不是刻意防的,重構會長回來) |
| 2. 任何登入者可替任意檔案編號換下載憑證 | ✅ 成立(HIGH,B1-1) |
| 3. 查檔案的資料層完全沒有範圍條件 | ✅ 成立(HIGH,B1b-1,已用 DEV 實查坐實) |
| 4. 檔名處理很薄 | 🟡 一半一半(穿越推翻;無副檔名白名單成立,是「上傳網頁檔被當網頁執行」的燃料;無大小/檔數上限成立) |
| 5. 物件儲存憑證加密預設為關 | ✅ 成立(LOW,B2-C)——而且追下去發現更前面就漏了(B2-A,HIGH) |
另外三棒都印證同一件事:工具不會照卡片清單走——B1b 卡列八個重點,工具只碰到一個半,其餘全是人工追出來的,包含本 arc 證據等級最高的那一條。
現況:已處理。 對應 docs/security-report/M02-file-upload.md:高風險七條全數已修,隨 1.21.0 出貨。裁定不修兩條——M02-8(讀不到自己的儲存設定時借用別家客戶的;決策者 10-01:屬舊資料相容問題,新建客戶會自動繼承總部儲存設定)、M02-11(連雲端儲存不加密;內建容器間通訊,CM-2063)。
~/Projects/Jedicogy/module/jedi-python-package/jedi-file-upload/app/upload_file/、infra/upload_file/、core/upload_file_wiring.py.claude/skills/security-scan-lead/SKILL.mdsecurity-scan-consolidated/FR-075/handoff/security-scan-STATE.md以下全部由 build 掃資料夾產生,新增檔案重 build 即自動出現。標題連結指向渲染後的 HTML,md 連向源檔。
| 文件 | 類型 | 標題 | 最後更新 |
|---|---|---|---|
| scan-B1-http-boundary / md | 盤點證據 | FR-086.B1 掃描報告:檔案上傳下載的 HTTP 邊界與契約(jedi-file-upload) | 2026-09-11 |
| scan-B1b-storage-backends / md | 盤點證據 | FR-086.B1b 掃描報告:儲存後端實作與持久層(jedi-file-upload) | 2026-09-11 |
| scan-B2-host-wiring / md | 盤點證據 | FR-086.B2 掃描報告:宿主接線——儲存解析與租戶憑證 | 2026-09-11 |