本卡屬 FR-080(母卡 CM-1612),第 3 棒:附件上傳下載(jedi monorepo)。只掃不修。
掃問題單的附件功能(14 檔):使用者上傳的檔案存到哪、怎麼取回、怎麼刪除。支援三種儲存後端(存本機/傳到 GitLab/傳到 GitHub)。要找的是檔案會不會被寫到不該寫的地方、能不能讀到別人的檔。
檔案上傳下載是路徑穿越與任意檔案讀寫的天然溫床,而這裡有三種後端各自實作——同一組介面三份程式碼,清洗與授權只要有一份漏做就是洞。範圍天然乾淨(一個目錄 14 檔),適合獨立一棒。
首腦讀過程式碼後的起點,未經驗證。
jedi_issue/api/__init__.py:11-13 自稱「GitLab/GitHub 整合目前產品沒有在用,約佔套件 58%」——首腦已從宿主端證偽(app/feedback/service/feedback_service.py 156/171/210/222/246/252 六處實際呼叫,IssueProviderCode 統計 LOCAL 9/GITLAB 4/GITHUB 3)。這是工具已知盲點二(會被程式碼註解說服)的教科書形狀。本套件 docstring 普遍詳盡且自我辯護,一律當待查證的宣稱。local/local_issue_attachment.py:52:save_dir = os.path.join("issue", issue_uid)——issue_uid 若可被呼叫端指定且未驗格式,../ 就能跳出目錄。要追:uid 從哪來、有沒有驗證、file_upload_service.upload_file(..., save_dir=save_dir) 拿到後怎麼用(base dir 由主專案傳入,同一支檔的註解自己寫了這件事)。gitlab/gitlab_issue_attachment.py:69:self.gl.projects.get(int(project)).upload(filename=file.filename, filedata=file_bytes)——file.filename 是使用者控制的。要追三個後端各自對檔名做了什麼。gitlab_issue_attachment.py:17-19 與 github/github_issue_attachment.py:72-73 都用 UPLOAD_UID_FILENAME_RE.findall(note.body) 從留言內文解出附件清單。留言內文是使用者可控的——要追:偽造一段符合格式的文字能不能讓系統去取/刪別人的檔。這是本棒最值得追的一條。delete_attachment(issue_uid, file_uids, ...)。要追:有沒有驗證這些 file_uids 真的屬於這張 issue、屬於這個租戶——FR-079 的 F16 就是「只驗你能不能刪,不驗這是不是你的」,同款要查。repository/issue_upload_files_repo_impl.py 的 add_issue_file_mapping/get_issue_file_mapping。要追這張對應表有沒有租戶隔離、models/issue_upload_file.py 有沒有掛 tenant scope。local_issue_attachment.py:63 在迴圈裡 IssueUploadFilesRepoImpl() 就地新建(不是注入的那個)。CLAUDE.md 有 repo session 必須 lazy 的鐵則——要確認不會踩到 session 問題,以及是否因此跳過某層處理。第一步:確認主 session 是 Opus 5 (1M context)(不是 Sonnet),並讀過首腦手冊第二節(.claude/skills/security-scan-lead/SKILL.md)。
第二步:驗 scope 檔數,對上 14 才啟動:
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-issue && \
git ls-files -- jedi_issue/infra/issue_upload_files/ | wc -l
(14 檔中有 6 個是空的 __init__.py,實質 8 檔;數字對上 14 即可。這是本 arc 最小的一棒,面板最有把握完整跑完。)