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)