Skip to content

Commit 6919c23

Browse files
authored
fix: skew potection token in run-mode when auth is enabled (#5168)
Bug fix where we were using the random skew potection token instead of one based on the code, in run mode.
1 parent 48b5a90 commit 6919c23

File tree

4 files changed

+161
-15
lines changed

4 files changed

+161
-15
lines changed

marimo/_server/sessions.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -782,26 +782,34 @@ def __init__(
782782
# since this will contain config-level overrides
783783
self._config_manager = config_manager
784784

785+
def _get_code() -> str:
786+
app = file_router.get_single_app_file_manager(
787+
default_width=self._config_manager.default_width,
788+
default_sql_output=self._config_manager.default_sql_output,
789+
).app
790+
return "".join(code for code in app.cell_manager.codes())
791+
785792
# Auth token and Skew-protection token
786-
if auth_token is not None:
787-
self.auth_token = auth_token
788-
self.skew_protection_token = SkewProtectionToken.random()
789-
elif mode == SessionMode.EDIT:
793+
if mode == SessionMode.EDIT:
790794
# In edit mode, if no auth token is provided,
791795
# generate a random token
792-
self.auth_token = AuthToken.random()
796+
self.auth_token = (
797+
AuthToken.random() if auth_token is None else auth_token
798+
)
793799
self.skew_protection_token = SkewProtectionToken.random()
794800
else:
795-
app = file_router.get_single_app_file_manager(
796-
default_width=self._config_manager.default_width,
797-
default_sql_output=self._config_manager.default_sql_output,
798-
).app
799-
codes = "".join(code for code in app.cell_manager.codes())
801+
source_code = _get_code()
800802
# Because run-mode is read-only and we could have multiple
801803
# servers for the same app (going to sleep or autoscaling),
802804
# we default to a token based on the app's code
803-
self.auth_token = AuthToken.from_code(codes)
804-
self.skew_protection_token = SkewProtectionToken.from_code(codes)
805+
self.auth_token = (
806+
AuthToken.from_code(source_code)
807+
if auth_token is None
808+
else auth_token
809+
)
810+
self.skew_protection_token = SkewProtectionToken.from_code(
811+
source_code
812+
)
805813

806814
def app_manager(self, key: MarimoFileKey) -> AppFileManager:
807815
"""

marimo/_server/tokens.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def random() -> AuthToken:
3030

3131
@staticmethod
3232
def from_code(code: str) -> AuthToken:
33-
return AuthToken(str(hash(code)))
33+
return AuthToken(str(hash("auth:" + code)))
3434

3535
@staticmethod
3636
def is_empty(token: AuthToken) -> bool:
@@ -51,7 +51,7 @@ def __init__(self, token: str) -> None:
5151

5252
@staticmethod
5353
def from_code(code: str) -> SkewProtectionToken:
54-
return SkewProtectionToken(str(hash(code)))
54+
return SkewProtectionToken(str(hash("skew:" + code)))
5555

5656
@staticmethod
5757
def random() -> SkewProtectionToken:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ extra-dependencies = [
280280
"ipython~=8.12.3",
281281
# testing gen ai
282282
"openai>=1.55.3",
283-
"anthropic==0.34.1",
283+
"anthropic==0.52.2",
284284
"google-generativeai==0.8.5",
285285
"boto3>=1.38.25",
286286
"litellm>=1.70.0",

tests/_server/test_session_manager.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
Session,
1717
SessionManager,
1818
)
19+
from marimo._server.tokens import AuthToken, SkewProtectionToken
1920
from marimo._types.ids import SessionId
2021

2122
if TYPE_CHECKING:
@@ -254,3 +255,140 @@ async def test_create_session_with_script_config_overrides(
254255
)
255256

256257
session.close()
258+
259+
260+
def test_session_manager_auth_token_edit_mode_with_provided_token():
261+
"""Test that provided auth token is used in EDIT mode"""
262+
provided_token = AuthToken("custom-edit-token")
263+
session_manager = SessionManager(
264+
file_router=AppFileRouter.new_file(),
265+
mode=SessionMode.EDIT,
266+
development_mode=False,
267+
quiet=False,
268+
include_code=True,
269+
lsp_server=MagicMock(spec=LspServer),
270+
config_manager=get_default_config_manager(current_path=None),
271+
cli_args={},
272+
argv=None,
273+
auth_token=provided_token,
274+
redirect_console_to_browser=False,
275+
ttl_seconds=None,
276+
)
277+
278+
assert session_manager.auth_token is provided_token
279+
assert str(session_manager.auth_token) == "custom-edit-token"
280+
assert session_manager.skew_protection_token is not None
281+
282+
283+
def test_session_manager_auth_token_edit_mode_without_provided_token():
284+
"""Test that random auth token is generated in EDIT mode when none provided"""
285+
session_manager = SessionManager(
286+
file_router=AppFileRouter.new_file(),
287+
mode=SessionMode.EDIT,
288+
development_mode=False,
289+
quiet=False,
290+
include_code=True,
291+
lsp_server=MagicMock(spec=LspServer),
292+
config_manager=get_default_config_manager(current_path=None),
293+
cli_args={},
294+
argv=None,
295+
auth_token=None,
296+
redirect_console_to_browser=False,
297+
ttl_seconds=None,
298+
)
299+
300+
# Should generate a random token (we can't predict the value, but it should exist)
301+
assert session_manager.auth_token is not None
302+
assert str(session_manager.auth_token) != ""
303+
# Verify it's a random token by checking length (AuthToken.random() uses token_urlsafe(16))
304+
assert len(str(session_manager.auth_token)) > 10
305+
assert session_manager.skew_protection_token is not None
306+
307+
308+
def test_session_manager_auth_token_run_mode_with_provided_token():
309+
"""Test that provided auth token is used in RUN mode"""
310+
provided_token = AuthToken("custom-run-token")
311+
session_manager = SessionManager(
312+
file_router=AppFileRouter.new_file(),
313+
mode=SessionMode.RUN,
314+
development_mode=False,
315+
quiet=False,
316+
include_code=True,
317+
lsp_server=MagicMock(spec=LspServer),
318+
config_manager=get_default_config_manager(current_path=None),
319+
cli_args={},
320+
argv=None,
321+
auth_token=provided_token,
322+
redirect_console_to_browser=False,
323+
ttl_seconds=None,
324+
)
325+
326+
assert session_manager.auth_token is provided_token
327+
assert str(session_manager.auth_token) == "custom-run-token"
328+
assert str(session_manager.skew_protection_token) == str(
329+
SkewProtectionToken.from_code("")
330+
)
331+
332+
333+
def test_session_manager_auth_token_run_mode_without_provided_token(
334+
tmp_path: Path,
335+
):
336+
"""Test that code-based auth token is generated in RUN mode when none provided"""
337+
# Create a simple marimo file
338+
notebook_content = dedent(
339+
"""\
340+
import marimo
341+
342+
app = marimo.App()
343+
344+
@app.cell
345+
def test_cell():
346+
"hello"
347+
return
348+
"""
349+
)
350+
351+
file_path = tmp_path / "test_notebook.py"
352+
file_path.write_text(notebook_content)
353+
354+
session_manager = SessionManager(
355+
file_router=AppFileRouter.infer(str(file_path)),
356+
mode=SessionMode.RUN,
357+
development_mode=False,
358+
quiet=False,
359+
include_code=True,
360+
lsp_server=MagicMock(spec=LspServer),
361+
config_manager=get_default_config_manager(current_path=None),
362+
cli_args={},
363+
argv=None,
364+
auth_token=None,
365+
redirect_console_to_browser=False,
366+
ttl_seconds=None,
367+
)
368+
369+
# Should generate a deterministic token based on code
370+
assert session_manager.auth_token is not None
371+
assert str(session_manager.auth_token) != ""
372+
assert str(session_manager.skew_protection_token) != ""
373+
374+
# Create another session manager with the same code - should have same token
375+
session_manager2 = SessionManager(
376+
file_router=AppFileRouter.infer(str(file_path)),
377+
mode=SessionMode.RUN,
378+
development_mode=False,
379+
quiet=False,
380+
include_code=True,
381+
lsp_server=MagicMock(spec=LspServer),
382+
config_manager=get_default_config_manager(current_path=None),
383+
cli_args={},
384+
argv=None,
385+
auth_token=None,
386+
redirect_console_to_browser=False,
387+
ttl_seconds=None,
388+
)
389+
390+
# Should have the same deterministic token
391+
assert str(session_manager.auth_token) == str(session_manager2.auth_token)
392+
assert str(session_manager.skew_protection_token) == str(
393+
session_manager2.skew_protection_token
394+
)

0 commit comments

Comments
 (0)