diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 8d992c0..0920053 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -9,6 +9,10 @@ the package version; publishing uses PyPI trusted publishing. - Made the safe runtime the default while retaining the legacy runtime only behind an explicit migration flag. +- Changed the safe runtime's `Py7zip.get_binary_url()` to raise + `NotImplementedError` directing callers to the public `download_binary()` + entry point instead of returning a fabricated cache-relative path + (OVH-05). ## 0.7.3 diff --git a/py7zip/py7zip.py b/py7zip/py7zip.py index dda88d9..00a6056 100644 --- a/py7zip/py7zip.py +++ b/py7zip/py7zip.py @@ -8,7 +8,6 @@ import requests -from .platforms import ArtifactCatalog from .safe import SafePy7zip @@ -145,10 +144,27 @@ def download_binary(self): os.chmod(self.binary_path, 0o755) def get_binary_url(self): - """Dynamically gets the URL of the required system binary.""" + """Return the source location for the platform's 7-Zip binary. + + On the safe runtime (default), binary acquisition is explicit: + callers must invoke :meth:`download_binary` (or supply a + pre-verified ``binary_path``) before using the wrapper. This + method raises :class:`NotImplementedError` rather than returning + a fabricated cache-relative path, because the safe runtime does + not construct download URLs at construction time. + + On the legacy runtime (``legacy=True``), returns the URL where + the historical in-package binary would have been served from, + or raises :class:`NotImplementedError` for unsupported host + platforms. + """ if not self.legacy: - spec = ArtifactCatalog.resolve(self.platform_info) - return f"{self._safe.cache_dir}/{spec.relative_path}" + raise NotImplementedError( + "get_binary_url() is not supported on the safe runtime; " + "call download_binary() to acquire a verified binary into " + "the cache directory, or supply a pre-verified " + "binary_path=... to the constructor." + ) if self.platform not in self.supported: raise NotImplementedError(f"Platform '{self.platform}' is not supported.") return self.url diff --git a/tests/test_default_api.py b/tests/test_default_api.py index 81a911d..e1b1ccc 100644 --- a/tests/test_default_api.py +++ b/tests/test_default_api.py @@ -30,6 +30,26 @@ def test_default_aliases_require_individual_option_arguments(tmp_path): wrapper.compress("source", "destination", options="-y") +def test_get_binary_url_raises_on_safe_runtime(tmp_path): + """The safe runtime exposes acquisition, not download URLs. + + Per the F2 finding in the platform-architecture audit, the safe + runtime previously returned a fabricated cache-relative path that + did not correspond to any real download. Acquisition is now + explicit via the public ``download_binary()`` entry point; this + method raises a typed error pointing callers at that API rather + than the internal ``SafePy7zip.ensure_binary()``. + """ + wrapper = Py7zip(cache_dir=tmp_path) + + with pytest.raises(NotImplementedError) as excinfo: + wrapper.get_binary_url() + + message = str(excinfo.value) + assert "download_binary()" in message + assert "ensure_binary" not in message + + def test_legacy_mode_is_explicit(): assert "legacy" in Py7zip.__init__.__code__.co_varnames assert ArchiveRunner is not None