@@ -109,11 +109,40 @@ def _ensure_safe_shared_directory(project_path: Path, directory: Path, *, create
109109 raise ValueError (f"Shared infrastructure directory escapes project root: { label } " ) from None
110110
111111
112- def _ensure_safe_shared_destination (project_path : Path , dest : Path ) -> None :
112+ def _validate_safe_shared_directory (project_path : Path , directory : Path ) -> None :
113+ """Validate existing directory parents while allowing missing directories."""
114+ root = project_path .resolve ()
115+ rel = _shared_relative_path (project_path , directory )
116+ current = project_path
117+
118+ for part in rel .parts :
119+ current = current / part
120+ label = _shared_destination_label (project_path , current )
121+ if current .is_symlink ():
122+ raise ValueError (f"Refusing to use symlinked shared infrastructure directory: { label } " )
123+ if not current .exists ():
124+ continue
125+ if not current .is_dir ():
126+ raise ValueError (f"Shared infrastructure directory path is not a directory: { label } " )
127+ try :
128+ current .resolve ().relative_to (root )
129+ except (OSError , ValueError ):
130+ raise ValueError (f"Shared infrastructure directory escapes project root: { label } " ) from None
131+
132+
133+ def _ensure_safe_shared_destination (
134+ project_path : Path ,
135+ dest : Path ,
136+ * ,
137+ parent_must_exist : bool = True ,
138+ ) -> None :
113139 """Refuse shared infra writes that would escape or follow symlinks."""
114140 root = project_path .resolve ()
115141 _shared_relative_path (project_path , dest )
116- _ensure_safe_shared_directory (project_path , dest .parent , create = False )
142+ if parent_must_exist :
143+ _ensure_safe_shared_directory (project_path , dest .parent , create = False )
144+ else :
145+ _validate_safe_shared_directory (project_path , dest .parent )
117146 label = _shared_destination_label (project_path , dest )
118147 if dest .is_symlink ():
119148 raise ValueError (f"Refusing to overwrite symlinked shared infrastructure path: { label } " )
@@ -235,7 +264,7 @@ def install_shared_infra(
235264
236265 rel_path = src_path .relative_to (variant_src )
237266 dst_path = dest_variant / rel_path
238- _ensure_safe_shared_destination (project_path , dst_path )
267+ _ensure_safe_shared_destination (project_path , dst_path , parent_must_exist = False )
239268 if dst_path .exists () and not force :
240269 skipped_files .append (str (dst_path .relative_to (project_path )))
241270 continue
@@ -264,6 +293,7 @@ def install_shared_infra(
264293 planned_templates .append ((dst , rel , content ))
265294
266295 for dst_path , rel , content , mode in planned_copies :
296+ _ensure_safe_shared_directory (project_path , dst_path .parent )
267297 _write_shared_bytes (project_path , dst_path , content , mode = mode )
268298 manifest .record_existing (rel )
269299
0 commit comments