Skip to content

Commit 8de5612

Browse files
committed
Add path traversal validation for workspace names
- Add is_safe_workspace_name() to reject names with path separators or parent directory references (/, \, .., .) - Apply validation in find_demo_workspace_path() to prevent demo name path traversal attacks - Apply validation in page_setup() for workspace query parameter, falling back to new UUID workspace if invalid Addresses Copilot security review comments #1 and #2.
1 parent 1699472 commit 8de5612

1 file changed

Lines changed: 28 additions & 2 deletions

File tree

src/common/common.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,22 @@
2626
OS_PLATFORM = sys.platform
2727

2828

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+
2945
def get_demo_source_dirs() -> list[Path]:
3046
"""
3147
Get list of demo workspace source directories from settings.
@@ -87,8 +103,12 @@ def find_demo_workspace_path(demo_name: str) -> Path | None:
87103
demo_name: Name of the demo workspace to find.
88104
89105
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.
91107
"""
108+
# Validate against path traversal attacks
109+
if not is_safe_workspace_name(demo_name):
110+
return None
111+
92112
for source_dir in get_demo_source_dirs():
93113
demo_path = source_dir / demo_name
94114
if demo_path.exists() and demo_path.is_dir():
@@ -384,8 +404,14 @@ def page_setup(page: str = "") -> dict[str, Any]:
384404
if "workspace" in st.query_params:
385405
requested_workspace = st.query_params.workspace
386406

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
387413
# 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:
389415
# Create a new UUID workspace and copy demo contents
390416
workspace_id = str(uuid.uuid1())
391417
st.session_state.workspace = Path(workspaces_dir, workspace_id)

0 commit comments

Comments
 (0)