本卡屬 FR-077(母卡 CM-PARENT),第 2 棒:任務下發與檔案取用(jedi monorepo,跨
jedi-remote-agent與jedi-detection兩包)。只掃不修。
R1 掃的是「agent 怎麼證明自己是誰」,這一棒掃的是證明完之後它能做什麼:雲端怎麼派工作給它、它怎麼回報做完了、以及它怎麼把工作要用到的檔案(源碼壓縮包、檢測設定檔)下載回去。
用 claude-security plugin 掃 29 個檔並產出報告。只找問題、不修問題。
「任務」與「任務引用的檔案」放同一棒,是因為它們是同一個授權窗口——檔案下載的授權判定(agent_file_access_service.py 的 resolver 清單)判的正是「這個檔案是不是該 agent 名下未完成任務引用的」。拆開來兩邊都看不出接縫:只看任務不知道任務狀態會影響檔案授權,只看檔案不知道任務狀態機允許哪些轉移。
跨兩個套件是因為 repo 邊界不等於業務邊界——派工單信封在 jedi-remote-agent,派工內容與檔案授權在 jedi-detection,這條路徑是連著的。兩包同在 jedi monorepo,同一個 scanRoot 掃得到。
出問題會怎樣:能替別人的任務回報結果,就能污染檢測證據(產品的核心產出);能繞過檔案授權,就能拿到別的客戶的源碼壓縮包。
這些是首腦讀過全部程式碼後認為最容易出事的地方。是思考起點不是檢查清單——掃完後照這個自己追(FR-076 的 L2 那條 HIGH 就是 runner 這樣追出來的,工具當時只找到 LOW)。
app/service/agent_task_service.py 的 ack_task(uid) 與 receive_result(uid, payload) ——參數只有路徑上的 task uid,沒有 agent_uid、沒有 tenant 檢查、沒有「這張單是不是派給你的」判斷。這兩支由不掛任何 user 認證的控制面端點呼叫(/agents/tasks/<uid>/ack、/agents/tasks/<uid>/result)。jedi-detection 的 agent_file_access_service.py docstring 自己寫了「tasks ack / result 則根本不識別身分」——這是已被文件化但從沒有人判斷過可不可接受的事。要追完整攻擊路徑:任一能打到控制面的 agent(含被入侵的客戶端 agent),拿到別人的 task uid 後能做什麼?task uid 是 uuid4 隨機的——它會出現在哪些地方(log?回應?心跳 payload?跨租戶可見嗎)?receive_result 的 result_ref 與 summary 直接進 listener 做「轉證據」編排——塞任意內容會怎樣?jedi-detection/jedi_detection/app/service/agent_file_access_service.py)。這支是首腦標的「最危險」:它的授權是「逐一問 resolver,任一認可即放行」,而 resolver 判的是 file_uid ∈ 該 agent 名下未完成任務引用的檔案 uid 集合。agent 身分來自 X-Agent-Uid header 自報。docstring 的辯護是「自報成別台也只能拿到那台名下任務正在用的檔案」——這個論證要驗。要追:_active_task_file_uids() 用 get_one_remote_agent(uid=agent_uid) 查 agent,有沒有 tenant 檢查?它跑在 @transaction 但這條路徑無 user context(super admin 繞 RLS)——那麼「別台 agent」包不包括別的租戶的 agent?若能列舉或猜到別租戶的 agent_uid,就能拿到那個租戶正在跑的任務所引用的檔案。另外:1559 盤點標這支「讀回空 → 404,看起來像權限正常實際是全部拒絕」——要分清「授權判定正確地拒絕」與「查詢因 context 問題回空而全部拒絕」,後者是掩蓋真實授權缺陷的偽陽性。domain/agent_task/service/agent_task_domain_service.py)。_ALLOWED_TRANSITIONS 允許 dispatched → succeeded/failed 直接跳(因為 agent 端不呼叫 mark_running)。終態鎖死。要問:update_status() 先 verify_task_is_exist 再 _assert_valid_transition 再 repo.update ——這三步之間沒有鎖,兩個併發的 result 回報會怎樣?receive_result 的 cancelled 短路是先 get_one 再判斷再寫——同款競態。狀態機能不能被用來把已完成的任務打回可領取狀態?list_pending_for_agent(agent_id, tenant_id) → repo.list_dispatchable_for_agent)。這支由 R1 的心跳呼叫,agent.id 與 agent.tenant_id 都來自心跳解析出的那一列。追進 repo 實作(infra/agent_task/repository/agent_task_repo_impl.py):那個 query 真的同時用了 agent_id 與 tenant_id 嗎?scheduled_at IS NULL OR <= now() 那條的時區處理對不對(utc_now() 是 UTC naive)?domain/agent_task/repository/payload_provider.py 的 port 定義 + create_task 的 params)。params 是 dict 直接落 JSONB、原樣進 payload provider。port 的 docstring 說產品側會在裡面放「工具代碼、解密後參數、憑證、限額」——也就是解密後的憑證會經由心跳回應送到 agent。要追:這些內容會不會落 log?payload provider 的介面有沒有讓「A 租戶的 agent 拿到 B 租戶的參數」的可能(build_pending_payloads(pending, tenant_id) 的 tenant_id 從哪來、有沒有被信任)?params 從建單到執行有沒有任何 sanitize(它最終會變成 agent 端執行的掃描參數)?jedi-detection/jedi_detection/common/agent_auth.py)。_DisabledSettings ——provider 沒接線時回 enabled=False,等同資料面完全不加認證。2026-09-01 arc-review 補了一次性 warning,但降級行為本身還在,且 warning 只發一次(_warned_not_configured)。要追:這支的三個 caller(detection_result_handler 取報告檔、agent_probe_client 測連線、agent_cancel_client 取消掃描)在 enabled=False 時各自會退化成什麼?裸 HTTP 嗎?打的是 agent 自報的 base_url 嗎(SSRF)?infra/detection_tools/connector/agent_probe_client.py、agent_cancel_client.py)。這兩支是「雲端主動打 agent」,照 FR-039 的規矩必須走 build_cloud_mtls_context() + jwt_util.mint()(memory feedback_fr039_all_agent_callers_reuse_mtls_auth.md:check_health 與 reconcile 曾各漏一次)。逐支確認它們有沒有照做——第三處裸 httpx 是新發現。同時看:URL 怎麼組(base_url 是 agent 自報的)、逾時設定、例外有沒有被靜默吞掉。job_execution_detection_tool_agent 這條線(domain/infra 五檔)。它記錄「哪次執行派給哪台 agent」。要看:查詢有沒有帶 tenant、agent_id 是 soft-ref(不是 FK)到 remote_agents.id ——soft-ref 意味著沒有 DB 層約束,跨租戶的 id 塞進來會怎樣?本範圍從未被掃過。R1(agent 身分與註冊)與本棒相鄰但範圍不重疊——R1 掃「怎麼證明自己是誰」,本棒掃「證明完能做什麼」。