Skip to content

Commit 9d32234

Browse files
committed
fix: harden GENRELEASES_DIR guard, cache parity tests, safe iterdir
- Reject '..' path segments in GENRELEASES_DIR to prevent traversal - Session-cache both scaffold and release-script results in parity tests — runtime drops from ~74s to ~45s (40% faster) - Guard cmd_dir.iterdir() in assertion message against missing dirs
1 parent 243ef5d commit 9d32234

2 files changed

Lines changed: 45 additions & 21 deletions

File tree

.github/workflows/scripts/create-release-packages.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ case "$GENRELEASES_DIR" in
4040
exit 1
4141
;;
4242
esac
43+
if [[ "$GENRELEASES_DIR" == *".."* ]]; then
44+
echo "Refusing to use GENRELEASES_DIR containing '..' path segments: $GENRELEASES_DIR" >&2
45+
exit 1
46+
fi
4347

4448
mkdir -p "$GENRELEASES_DIR"
4549
rm -rf "${GENRELEASES_DIR%/}/"* || true

tests/test_core_pack_scaffold.py

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,15 @@ def test_scaffold_command_file_count(agent, scaffolded_sh, source_template_stems
263263

264264
cmd_dir = _expected_cmd_dir(project, agent)
265265
generated = _list_command_files(cmd_dir, agent)
266+
267+
if cmd_dir.is_dir():
268+
dir_listing = list(cmd_dir.iterdir())
269+
else:
270+
dir_listing = f"<command dir missing: {cmd_dir}>"
271+
266272
assert len(generated) == len(source_template_stems), (
267273
f"Agent '{agent}': expected {len(source_template_stems)} command files "
268-
f"({_expected_ext(agent)}), found {len(generated)}. Dir: {list(cmd_dir.iterdir())}"
274+
f"({_expected_ext(agent)}), found {len(generated)}. Dir: {dir_listing}"
269275
)
270276

271277

@@ -493,39 +499,53 @@ def test_scaffold_powershell_variant(agent, scaffolded_ps, source_template_stems
493499
# 8. Parity: bundled vs. real create-release-packages.sh ZIP
494500
# ---------------------------------------------------------------------------
495501

502+
@pytest.fixture(scope="session")
503+
def release_script_trees(tmp_path_factory):
504+
"""Session-scoped cache: run release script once per (agent, script_type)."""
505+
cache: dict[tuple[str, str], dict[str, bytes]] = {}
506+
bash = _find_bash()
507+
508+
def _get(agent: str, script_type: str) -> dict[str, bytes] | None:
509+
if bash is None:
510+
return None
511+
key = (agent, script_type)
512+
if key not in cache:
513+
tmp = tmp_path_factory.mktemp(f"release_{agent}_{script_type}")
514+
gen_dir = tmp / "genreleases"
515+
gen_dir.mkdir()
516+
zip_path = _run_release_script(agent, script_type, bash, gen_dir)
517+
extracted = tmp / "extracted"
518+
extracted.mkdir()
519+
with zipfile.ZipFile(zip_path) as zf:
520+
zf.extractall(extracted)
521+
cache[key] = _collect_relative_files(extracted)
522+
return cache[key]
523+
return _get
524+
525+
496526
@pytest.mark.parametrize("script_type", ["sh", "ps"])
497527
@pytest.mark.parametrize("agent", _TESTABLE_AGENTS)
498-
def test_parity_bundled_vs_release_script(tmp_path, agent, script_type):
528+
def test_parity_bundled_vs_release_script(agent, script_type, scaffolded_sh, scaffolded_ps, release_script_trees):
499529
"""scaffold_from_core_pack() file tree is identical to the ZIP produced by
500530
create-release-packages.sh for every agent and script type.
501531
502532
This is the true end-to-end parity check: the Python offline path must
503533
produce exactly the same artifacts as the canonical shell release script.
504534
505-
Each agent is tested independently: generate the release ZIP, generate
506-
the bundled scaffold, compare. This avoids cross-agent interference
507-
from the release script's rm -rf at startup.
535+
Both sides are session-cached: each agent/script_type combination is
536+
scaffolded and release-scripted only once across all tests.
508537
"""
509-
bash = _find_bash()
510-
if bash is None:
538+
script_tree = release_script_trees(agent, script_type)
539+
if script_tree is None:
511540
pytest.skip("bash required to run create-release-packages.sh")
512541

513-
# --- Release script path ---
514-
gen_dir = tmp_path / "genreleases"
515-
gen_dir.mkdir()
516-
zip_path = _run_release_script(agent, script_type, bash, gen_dir)
517-
script_dir = tmp_path / "extracted"
518-
script_dir.mkdir()
519-
with zipfile.ZipFile(zip_path) as zf:
520-
zf.extractall(script_dir)
521-
522-
# --- Bundled path ---
523-
bundled_dir = tmp_path / "bundled"
524-
ok = scaffold_from_core_pack(bundled_dir, agent, script_type)
525-
assert ok
542+
# Reuse session-cached scaffold output
543+
if script_type == "sh":
544+
bundled_dir = scaffolded_sh(agent)
545+
else:
546+
bundled_dir = scaffolded_ps(agent)
526547

527548
bundled_tree = _collect_relative_files(bundled_dir)
528-
script_tree = _collect_relative_files(script_dir)
529549

530550
only_bundled = set(bundled_tree) - set(script_tree)
531551
only_script = set(script_tree) - set(bundled_tree)

0 commit comments

Comments
 (0)