Skip to content

Commit c24d480

Browse files
authored
fix: properly authenticate terminal route (#9098)
1 parent 78489d4 commit c24d480

2 files changed

Lines changed: 31 additions & 4 deletions

File tree

marimo/_server/api/endpoints/terminal.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState
1515

1616
from marimo import _loggers
17+
from marimo._server.api.auth import validate_auth
1718
from marimo._server.api.deps import AppState
19+
from marimo._server.codes import WebSocketCodes
1820
from marimo._server.router import APIRouter
1921
from marimo._session.model import SessionMode
2022
from marimo._utils.platform import is_pyodide, is_windows
@@ -353,6 +355,13 @@ def supports_terminal() -> bool:
353355
@router.websocket("/ws")
354356
async def websocket_endpoint(websocket: WebSocket) -> None:
355357
app_state = AppState(websocket)
358+
359+
if app_state.enable_auth and not validate_auth(websocket):
360+
await websocket.close(
361+
WebSocketCodes.UNAUTHORIZED, "MARIMO_UNAUTHORIZED"
362+
)
363+
return
364+
356365
if app_state.mode != SessionMode.EDIT:
357366
await websocket.close(
358367
code=1008, reason="Terminal only available in edit mode"

tests/_server/api/endpoints/test_terminal.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
from marimo._session.model import SessionMode
2525
from tests._server.mocks import get_session_manager
2626

27+
TERMINAL_WS_URL = "/terminal/ws?access_token=fake-token"
28+
2729
if TYPE_CHECKING:
2830
from starlette.testclient import TestClient
2931

@@ -33,7 +35,7 @@
3335

3436
@pytest.mark.skipif(is_windows, reason="Skip on Windows")
3537
def test_terminal_ws(client: TestClient) -> None:
36-
with client.websocket_connect("/terminal/ws") as websocket:
38+
with client.websocket_connect(TERMINAL_WS_URL) as websocket:
3739
# Send echo message
3840
websocket.send_text("echo hello")
3941
data = websocket.receive_text()
@@ -44,11 +46,27 @@ def test_terminal_ws_not_allowed_in_run(client: TestClient) -> None:
4446
session_manager: SessionManager = get_session_manager(client)
4547
session_manager.mode = SessionMode.RUN
4648
with pytest.raises(WebSocketDisconnect):
47-
with client.websocket_connect("/terminal/ws") as websocket:
49+
with client.websocket_connect(TERMINAL_WS_URL) as websocket:
4850
websocket.send_text("echo hello")
4951
session_manager.mode = SessionMode.EDIT
5052

5153

54+
def test_terminal_ws_unauthorized(client: TestClient) -> None:
55+
"""Test terminal websocket rejects unauthenticated connections."""
56+
with pytest.raises(WebSocketDisconnect) as exc_info:
57+
with client.websocket_connect("/terminal/ws"):
58+
pass
59+
assert exc_info.value.code == 3000
60+
61+
62+
def test_terminal_ws_wrong_token(client: TestClient) -> None:
63+
"""Test terminal websocket rejects wrong token."""
64+
with pytest.raises(WebSocketDisconnect) as exc_info:
65+
with client.websocket_connect("/terminal/ws?access_token=wrong-token"):
66+
pass
67+
assert exc_info.value.code == 3000
68+
69+
5270
# Unit tests for terminal utility functions
5371

5472

@@ -404,7 +422,7 @@ def test_should_close_on_command_case_variations(self) -> None:
404422
@pytest.mark.skipif(is_windows, reason="Skip on Windows")
405423
def test_terminal_ws_unicode_input(client: TestClient) -> None:
406424
"""Test terminal websocket with unicode input."""
407-
with client.websocket_connect("/terminal/ws") as websocket:
425+
with client.websocket_connect(TERMINAL_WS_URL) as websocket:
408426
# Send unicode command
409427
websocket.send_text("echo 'Hello 🌍'")
410428
websocket.send_text("\r")
@@ -423,7 +441,7 @@ def test_terminal_ws_invalid_session_mode(client: TestClient) -> None:
423441
# Test with RUN mode
424442
session_manager.mode = SessionMode.RUN
425443
with pytest.raises(WebSocketDisconnect):
426-
with client.websocket_connect("/terminal/ws"):
444+
with client.websocket_connect(TERMINAL_WS_URL):
427445
pass # Should fail immediately
428446

429447
finally:

0 commit comments

Comments
 (0)