From 0ff03c1d643830fbbdd488d5cbb77c4e81960868 Mon Sep 17 00:00:00 2001 From: Pine Date: Tue, 1 Sep 2026 20:36:24 +0800 Subject: [PATCH] =?UTF-8?q?fix(frontend=5Fplugin):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=90=AB=E6=96=9C=E6=9D=A0=E7=9A=84=20scoped=20=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=20id=20=E7=9A=84=E9=9D=99=E6=80=81=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 市场安装的 app/插件 id 可为 @scope/name 形式(如 @kmu/incubator)。 原路由 /{plugin_id}/files/{file_path:path} 会把 @kmu/incubator 按 '/' 拆段, 导致 get_loaded_plugin('@kmu') 未命中 → bundle 404 → 前端路由未注册 → 应用中心提示该应用尚未加载,请先从侧边栏打开一次后重试。 改为 /{rest:path} 捕获整个剩余路径,按 '/files/' 标记拆分 plugin_id 与 file_path, 兼容简单 id(qwenpaw-creator)与 scoped id(@kmu/incubator); 路径穿越防护仍由 serve_plugin_ui_file 的 is_relative_to 承担。 --- src/pineagents/app/routers/frontend_plugin.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/pineagents/app/routers/frontend_plugin.py b/src/pineagents/app/routers/frontend_plugin.py index 078c756..59289c2 100644 --- a/src/pineagents/app/routers/frontend_plugin.py +++ b/src/pineagents/app/routers/frontend_plugin.py @@ -10,7 +10,7 @@ Only read operations are exposed here. All plugin management operations require a valid Bearer token. """ -from fastapi import APIRouter, Request +from fastapi import APIRouter, HTTPException, Request from .plugins import ( _list_plugins_from_disk, @@ -61,21 +61,37 @@ async def list_frontend_plugins(request: Request): @router.get( - "/{plugin_id}/files/{file_path:path}", + "/{rest:path}", summary="Serve plugin static file (public)", description=( "Serve a static asset (JS, CSS, images) from a plugin's directory. " "Public so plugin bundles can be loaded on the unauthenticated " - "login page." + "login page. Supports both simple ids (qwenpaw-creator) and " + "scoped ids containing '/' (e.g. @kmu/incubator) — the plugin id " + "and file path are split on the '/files/' marker." ), ) async def serve_frontend_plugin_file( - plugin_id: str, - file_path: str, + rest: str, request: Request, ): """Delegate to the authenticated static-file handler in plugins.py. + URL shape: ``/frontend_plugin//files/`` + e.g. ``/frontend_plugin/qwenpaw-creator/files/ui/dist/index.js`` + ``/frontend_plugin/@kmu/incubator/files/dist/index.js`` + + The previous route ``/{plugin_id}/files/{file_path:path}`` could not + capture scoped plugin ids that contain a '/' (e.g. ``@kmu/incubator``), + because FastAPI splits the path on '/'. This route captures the whole + remainder and splits on the ``/files/`` marker instead. + Path-traversal protection is handled inside serve_plugin_ui_file. """ + marker = "/files/" + if marker not in rest: + raise HTTPException(status_code=404, detail="Plugin file path malformed") + plugin_id, file_path = rest.split(marker, 1) + if not plugin_id or not file_path: + raise HTTPException(status_code=404, detail="Plugin file path malformed") return await serve_plugin_ui_file(plugin_id, file_path, request)