From fec287fce380a19361e7f0be4ba9d2f7f97b82eb Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Wed, 15 Apr 2026 19:47:23 -0700 Subject: [PATCH] fix(security): add bearer token auth to /transcript endpoint (#287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #287 Any container on molecule-monorepo-net could previously read the full Claude session log without authentication. Guard uses get_token() from platform_auth — skipped only before workspace registration (dev-mode). --- workspace-template/main.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/workspace-template/main.py b/workspace-template/main.py index 2fb3adac..60766e71 100644 --- a/workspace-template/main.py +++ b/workspace-template/main.py @@ -290,12 +290,15 @@ async def main(): # pragma: no cover from starlette.routing import Route async def _transcript_handler(request): - # No bearer check here — same model as POST / (A2A): the workspace's - # HTTP server only listens on the internal Docker network, and the - # platform's TranscriptHandler is the only intended caller. Phase 30 - # remote workspaces will need a proper auth story (TODO #N) — likely - # the existing wsauth bearer, but with a callback to the platform to - # validate (since the workspace doesn't see all live tokens). + # Require workspace bearer token — the same token issued at registration + # and stored in /configs/.auth_token. Any container on molecule-monorepo-net + # could otherwise read the full session log. Closes #287. + from platform_auth import get_token + expected = get_token() + if expected: + auth_header = request.headers.get("Authorization", "") + if auth_header != f"Bearer {expected}": + return JSONResponse({"error": "unauthorized"}, status_code=401) try: since = int(request.query_params.get("since", "0")) limit = int(request.query_params.get("limit", "100"))