@@ -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