Fix remote-stack log streaming: URL-encode agent token in WS proxy (0.13.2)
The agent-logs WebSocket proxy injected the agent token raw into the upstream query string (?token=<token>). Tokens containing base64/url-special characters (+, /, =) were then mangled by the query parser on the agent side (e.g. "+" decoded to a space), so the agent rejected the stream with close code 4401 even though the same token works for the HTTP API (where it travels in the Authorization header). URL-encode the token with urllib.parse.quote so it round-trips intact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5ebd615651
commit
4bc0fb8901
@@ -40,7 +40,7 @@ from services import backup_service, compose_service
|
|||||||
|
|
||||||
logger = logging.getLogger("stackpilot.agent")
|
logger = logging.getLogger("stackpilot.agent")
|
||||||
|
|
||||||
AGENT_VERSION = "0.13.1"
|
AGENT_VERSION = "0.13.2"
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ async def lifespan(app: FastAPI):
|
|||||||
schedule_task.cancel()
|
schedule_task.cancel()
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(title="StackPilot", version="0.13.1", lifespan=lifespan)
|
app = FastAPI(title="StackPilot", version="0.13.2", lifespan=lifespan)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
@@ -114,7 +115,9 @@ async def ws_agent_logs(
|
|||||||
ws_url = ("wss://" + base[8:] if base.startswith("https://")
|
ws_url = ("wss://" + base[8:] if base.startswith("https://")
|
||||||
else "ws://" + base[7:] if base.startswith("http://")
|
else "ws://" + base[7:] if base.startswith("http://")
|
||||||
else "ws://" + base)
|
else "ws://" + base)
|
||||||
ws_url += f"/agent/ws/logs/{stack_id}?token={agent.token}"
|
# URL-encode the token: agent tokens may contain base64 chars (+ / =) that
|
||||||
|
# would otherwise be mangled in the query string and rejected as 4401.
|
||||||
|
ws_url += f"/agent/ws/logs/{stack_id}?token={urllib.parse.quote(agent.token, safe='')}"
|
||||||
|
|
||||||
async def _err(detail: str) -> None:
|
async def _err(detail: str) -> None:
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "stackpilot-frontend",
|
"name": "stackpilot-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.13.1",
|
"version": "0.13.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
Reference in New Issue
Block a user