|
26 | 26 | OS_PLATFORM = sys.platform |
27 | 27 |
|
28 | 28 |
|
| 29 | +def is_safe_workspace_name(name: str) -> bool: |
| 30 | + """ |
| 31 | + Check if a workspace name is safe (no path traversal characters). |
| 32 | +
|
| 33 | + Args: |
| 34 | + name: The workspace name to validate. |
| 35 | +
|
| 36 | + Returns: |
| 37 | + bool: True if safe, False if contains path separators or parent references. |
| 38 | + """ |
| 39 | + if not name: |
| 40 | + return False |
| 41 | + # Reject path separators and parent directory references |
| 42 | + return "/" not in name and "\\" not in name and name not in ("..", ".") |
| 43 | + |
| 44 | + |
29 | 45 | def get_demo_source_dirs() -> list[Path]: |
30 | 46 | """ |
31 | 47 | Get list of demo workspace source directories from settings. |
@@ -87,8 +103,12 @@ def find_demo_workspace_path(demo_name: str) -> Path | None: |
87 | 103 | demo_name: Name of the demo workspace to find. |
88 | 104 |
|
89 | 105 | Returns: |
90 | | - Path to the demo workspace, or None if not found. |
| 106 | + Path to the demo workspace, or None if not found or name is unsafe. |
91 | 107 | """ |
| 108 | + # Validate against path traversal attacks |
| 109 | + if not is_safe_workspace_name(demo_name): |
| 110 | + return None |
| 111 | + |
92 | 112 | for source_dir in get_demo_source_dirs(): |
93 | 113 | demo_path = source_dir / demo_name |
94 | 114 | if demo_path.exists() and demo_path.is_dir(): |
@@ -384,8 +404,14 @@ def page_setup(page: str = "") -> dict[str, Any]: |
384 | 404 | if "workspace" in st.query_params: |
385 | 405 | requested_workspace = st.query_params.workspace |
386 | 406 |
|
| 407 | + # Validate workspace name against path traversal |
| 408 | + if not is_safe_workspace_name(requested_workspace): |
| 409 | + # Invalid workspace name - fall back to new UUID workspace |
| 410 | + workspace_id = str(uuid.uuid1()) |
| 411 | + st.session_state.workspace = Path(workspaces_dir, workspace_id) |
| 412 | + st.query_params.workspace = workspace_id |
387 | 413 | # Check if the requested workspace is a demo workspace (online mode) |
388 | | - if st.session_state.location == "online" and requested_workspace in available_demos: |
| 414 | + elif st.session_state.location == "online" and requested_workspace in available_demos: |
389 | 415 | # Create a new UUID workspace and copy demo contents |
390 | 416 | workspace_id = str(uuid.uuid1()) |
391 | 417 | st.session_state.workspace = Path(workspaces_dir, workspace_id) |
|
0 commit comments