Skip to content

Commit e0495eb

Browse files
Copilotmnriem
andauthored
Fix arc_tmp_path UnboundLocalError in workflow install; add preset symlink rejection test
Agent-Logs-Url: https://github.com/github/spec-kit/sessions/0469bac5-296a-46b6-b84e-eb33b0dc0fce Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
1 parent cb87a41 commit e0495eb

2 files changed

Lines changed: 25 additions & 6 deletions

File tree

src/specify_cli/__init__.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5079,16 +5079,18 @@ def _validate_and_install_local(yaml_path: Path, source_label: str) -> None:
50795079
if archive_fmt in ("tar.gz", "zip"):
50805080
# Extract workflow.yml from the archive.
50815081
suffix = ".tar.gz" if archive_fmt == "tar.gz" else ".zip"
5082+
arc_tmp_path = None
50825083
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as arc_tmp:
5083-
arc_tmp.write(raw_data)
50845084
arc_tmp_path = Path(arc_tmp.name)
5085+
arc_tmp.write(raw_data)
50855086
try:
50865087
wf_yaml = _extract_workflow_yml(arc_tmp_path, archive_fmt)
50875088
with tempfile.NamedTemporaryFile(suffix=".yml", delete=False) as tmp:
5088-
tmp.write(wf_yaml)
50895089
tmp_path = Path(tmp.name)
5090+
tmp.write(wf_yaml)
50905091
finally:
5091-
arc_tmp_path.unlink(missing_ok=True)
5092+
if arc_tmp_path is not None:
5093+
arc_tmp_path.unlink(missing_ok=True)
50925094
else:
50935095
# Treat as a plain YAML file (existing behaviour).
50945096
with tempfile.NamedTemporaryFile(suffix=".yml", delete=False) as tmp:
@@ -5125,9 +5127,10 @@ def _validate_and_install_local(yaml_path: Path, source_label: str) -> None:
51255127
console.print(f"[red]Error:[/red] Failed to extract workflow from archive: {exc}")
51265128
raise typer.Exit(1)
51275129
import tempfile
5130+
tmp_local = None
51285131
with tempfile.NamedTemporaryFile(suffix=".yml", delete=False) as tmp:
5129-
tmp.write(wf_yaml)
51305132
tmp_local = Path(tmp.name)
5133+
tmp.write(wf_yaml)
51315134
try:
51325135
_validate_and_install_local(tmp_local, str(source_path))
51335136
finally:
@@ -5231,13 +5234,15 @@ def _validate_and_install_local(yaml_path: Path, source_label: str) -> None:
52315234
if cat_archive_fmt in ("tar.gz", "zip"):
52325235
# Download URL points to an archive — extract workflow.yml from it.
52335236
suffix = ".tar.gz" if cat_archive_fmt == "tar.gz" else ".zip"
5237+
arc_tmp = None
52345238
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as arc_f:
5235-
arc_f.write(raw_response)
52365239
arc_tmp = Path(arc_f.name)
5240+
arc_f.write(raw_response)
52375241
try:
52385242
wf_yaml_bytes = _extract_workflow_yml(arc_tmp, cat_archive_fmt)
52395243
finally:
5240-
arc_tmp.unlink(missing_ok=True)
5244+
if arc_tmp is not None:
5245+
arc_tmp.unlink(missing_ok=True)
52415246
workflow_file.write_bytes(wf_yaml_bytes)
52425247
else:
52435248
workflow_file.write_bytes(raw_response)

tests/test_presets.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,20 @@ def test_install_from_tar_gz_rejects_path_traversal(self, project_dir, temp_dir)
719719
with pytest.raises(PresetValidationError, match="Unsafe path"):
720720
manager.install_from_zip(archive, "0.1.5")
721721

722+
def test_install_from_tar_gz_rejects_symlinks(self, project_dir, temp_dir):
723+
"""install_from_zip must reject tarballs containing symlinks."""
724+
import tarfile
725+
archive = temp_dir / "symlink.tar.gz"
726+
with tarfile.open(archive, "w:gz") as tf:
727+
info = tarfile.TarInfo(name="link")
728+
info.type = tarfile.SYMTYPE
729+
info.linkname = "/etc/passwd"
730+
tf.addfile(info)
731+
732+
manager = PresetManager(project_dir)
733+
with pytest.raises(PresetValidationError, match="Symlinks"):
734+
manager.install_from_zip(archive, "0.1.5")
735+
722736
def test_remove(self, project_dir, pack_dir):
723737
"""Test removing a preset."""
724738
manager = PresetManager(project_dir)

0 commit comments

Comments
 (0)