Skip to content

Commit cf2305f

Browse files
Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent f6e86c2 commit cf2305f

1 file changed

Lines changed: 25 additions & 9 deletions

File tree

dev_environment/demo_api_main.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,20 +102,36 @@ async def upload_arc(request: Request) -> dict[str, str | dict[str, str]]:
102102
_chown_tree(output_path) # Ensure the root output dir belongs to the host user
103103

104104
# Derive a safe ARC identifier from the payload. The ARC identifier is used
105-
# as a directory name below, so ensure it cannot escape the output_path.
105+
# as a directory name below, so ensure it cannot escape the output_path and
106+
# does not contain any path traversal or directory separators.
106107
raw_arc_id = arc_payload.get("identifier")
108+
109+
def _generate_random_arc_id() -> str:
110+
return f"arc_{os.urandom(4).hex()}"
111+
107112
if isinstance(raw_arc_id, str) and raw_arc_id.strip():
108113
candidate_id = raw_arc_id.strip()
109-
# Prevent absolute paths and directory components; keep only the final name.
110-
if Path(candidate_id).is_absolute():
111-
candidate_id = f"arc_{os.urandom(4).hex()}"
112-
safe_name = Path(candidate_id).name
113-
if not safe_name:
114-
arc_id = f"arc_{os.urandom(4).hex()}"
114+
# Reduce to a single path component and normalize it.
115+
safe_name = os.path.normpath(Path(candidate_id).name)
116+
# Reject empty names, current/parent directory markers, or anything that
117+
# would reintroduce directory components on this platform.
118+
if not safe_name or safe_name in {".", ".."} or "/" in safe_name or "\\" in safe_name:
119+
arc_id = _generate_random_arc_id()
115120
else:
116-
arc_id = safe_name
121+
# Build the full path and ensure it stays within the output_path root.
122+
full_path = (output_path / safe_name).resolve()
123+
try:
124+
common_root = os.path.commonpath([str(output_path.resolve()), str(full_path)])
125+
except ValueError:
126+
# On error (e.g., different drives), fall back to a random ID.
127+
arc_id = _generate_random_arc_id()
128+
else:
129+
if common_root != str(output_path.resolve()):
130+
arc_id = _generate_random_arc_id()
131+
else:
132+
arc_id = safe_name
117133
else:
118-
arc_id = f"arc_{os.urandom(4).hex()}"
134+
arc_id = _generate_random_arc_id()
119135

120136
now = datetime.now(UTC).isoformat()
121137
arc_dir = output_path / arc_id

0 commit comments

Comments
 (0)