FR-086 · 需求索引 · 本頁由 build 掃資料夾生成

FR-086 檔案上傳下載(jedi-file-upload)資安檢查

🔄 三棒掃完兩棒(2026-09-11)。B1/B2 已掃完並經首腦驗收——B2 證實這條路四層全空,另人工追出「任何登入者可取得本租戶物件儲存帳密明文」。B1b 待派。B1 找到 4 條(HIGH 3/MEDIUM 1),全是「只驗身分不驗歸屬」——任何登入者憑檔案編號即可下載/預覽/刪除/換發免登入通行證;卡片列為最重要的①路徑穿越經查不成立。B2(CM-1653)/B1b(CM-1654)待派

狀態:🔄 進行中 文件 2 份

🔴 一頁看完

B1 掃完了。結論:只要有一個能登入的帳號,再知道某個檔案的編號,就能把別家客戶的檔案下載走、線上預覽、永久刪除——系統從頭到尾只檢查「你有沒有登入」,從來沒檢查過「這個檔是不是你的」。

而且還能替別人的檔案換發一張免登入的下載通行證,直接把網址傳給公司外面的任何人。另有一條:上傳一個網頁檔,別人一點預覽就會在我們自己的網域裡執行

四條全是同一個病:認得出你是誰,但不管你能碰什麼。四條共用同一道修法。

好消息:卡片列為「本棒最重要」的路徑穿越經查不成立——但它是靠執行順序意外擋住的,不是有人刻意防的,重構就會長回來。

B2 回答了 B1 留下的問題:宿主那半邊有沒有補上檢查?沒有。 合起來是一條四層全空的路徑——網址入口只驗登入、業務邏輯零檢查、資料查詢無範圍條件、資料表連租戶欄位都沒有(隔離也是關的)。沒有任何一層會問「這個檔是不是你的」。

B2 另外人工追出一條同級的任何登入者打一支查設定的 GET,就拿到自己租戶物件儲存的位址、帳號、密碼明文——拿到後可完全繞過系統直接連上倉庫,應用層再怎麼補都管不到。三處小改可修。

📄 B1 報告B2 報告|B1b 待派。

§1

這在做什麼(白話)

產品裡所有「上傳檔案、下載檔案、預覽檔案」都經過這支套件。它決定三件事:

  • 使用者傳上來的檔案存到哪(主機硬碟、物件儲存、或客戶自己的設備)
  • 存成什麼檔名、放在哪個目錄
  • 下載時怎麼判斷**「這個檔是不是你的」**

這個 arc 要回答:有沒有辦法把檔案寫到不該寫的地方、有沒有辦法下載到別人的檔案、儲存憑證會不會外洩。

§2

為什麼掃它

  • 從未掃過。 跨 arc 總表把它列在建議順序第三,理由是「路徑穿越與任意檔案讀取的天然溫床」。
  • 存檔案的那張表沒有多租戶欄位(建表腳本檔頭自陳),意思是資料庫層對它沒有任何隔離兜底——應用層的歸屬檢查是唯一防線。
  • 首腦盤點看到的疑點密度高於前幾支(見下)。
§3

🔴 首腦盤點的五個現成疑點(未驗證,行號已開檔核對)

# 看到什麼 為什麼可疑
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 條若都成立,合起來就是一條完整的跨租戶取檔路徑——這正是三棒要分別驗證的。

§4

怎麼切三棒(按業務功能垂直切)

範圍 檔數 scanRoot 為什麼獨立一棒
B1 HTTP 邊界與契約 api/app/ports/common/plugin.py 35 jedi monorepo 上傳/下載/預覽/發憑證四條對外路徑同一條呼叫鏈,三個最直接的疑點都在這
B2 宿主接線 app/upload_file/infra/upload_file/core/upload_file_wiring.pydi_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%。已據實際數字切棒。

§5

進度

面板 發現 報告
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

修正卡

(掃完驗收後才會有。依決策者裁定,修正卡先不開,發現登記進跨 arc 總表 §3。)

§7

座標

  • 套件:~/Projects/Jedicogy/module/jedi-python-package/jedi-file-upload/
  • 宿主接線:app/upload_file/infra/upload_file/core/upload_file_wiring.py
  • 相關前案:CM-1594(FR-077 R3,遠端 agent 宿主接線,與本 arc 砍掉的那棒重疊)、CM-1633(FR-081 I3,另一支套件的附件功能)、CM-1559(FR-085 C1,無身分時隔離關掉)
  • 首腦手冊:.claude/skills/security-scan-lead/SKILL.md
  • 跨 arc 總表:security-scan-consolidated/
  • 現況:FR-075/handoff/security-scan-STATE.md
§8

文件

以下全部由 build 掃資料夾產生,新增檔案重 build 即自動出現。標題連結指向渲染後的 HTML,md 連向源檔。

其他文件

文件 類型 標題 最後更新
scan-B1-http-boundarymd 文件 FR-086.B1 掃描報告:檔案上傳下載的 HTTP 邊界與契約(jedi-file-upload) 2026-09-11
scan-B2-host-wiringmd 文件 FR-086.B2 掃描報告:宿主接線——儲存解析與租戶憑證 未提交
§9

Notion 卡

卡片內容(決策紀錄、驗收條件)以 Notion 為準,本頁只記座標。

關係 卡號 標題 狀態
母案 CM-1651 FR-086 檔案上傳下載(jedi-file-upload)資安掃描(三棒,只掃不修)
子卡 CM-1652 B1 HTTP 邊界與契約(35 檔) 修正待驗證
子卡 CM-1653 B2 宿主接線:儲存解析與租戶憑證(10 檔,BE repo) 修正待驗證
子卡 CM-1654 B1b 儲存後端實作與持久層(26 檔) Not started