fix: audit round 3 - fail-closed leak, shared asyncio loop, GOST bundling, close-X UX, persona key lookup, +29 tests
This commit is contained in:
@@ -5,6 +5,7 @@ import io
|
||||
import logging
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -87,6 +88,48 @@ def _write_pinned_exe_hash(exe: Path, digest: str) -> None:
|
||||
log.warning("Could not write gost.exe hash sidecar: %s", exc)
|
||||
|
||||
|
||||
def _bundled_gost_paths() -> tuple[Path | None, Path | None]:
|
||||
"""Return (exe_path, sha_path) inside the PyInstaller bundle, or (None, None)."""
|
||||
base = getattr(sys, "_MEIPASS", None)
|
||||
if not base:
|
||||
# Source checkout — look next to the package
|
||||
base = str(Path(__file__).resolve().parent.parent)
|
||||
bundle_exe = Path(base) / "proxy_chain_manager" / "_bundled" / "gost.exe"
|
||||
bundle_sha = bundle_exe.with_suffix(bundle_exe.suffix + ".sha256")
|
||||
if bundle_exe.is_file():
|
||||
return bundle_exe, (bundle_sha if bundle_sha.is_file() else None)
|
||||
return None, None
|
||||
|
||||
|
||||
def _install_from_bundle(exe: Path) -> bool:
|
||||
"""Copy the bundled gost.exe into *exe* and pin its hash. Returns True
|
||||
on success. Verifies the bundle hash if a sidecar shipped with it."""
|
||||
src, sha_src = _bundled_gost_paths()
|
||||
if not src:
|
||||
return False
|
||||
try:
|
||||
exe.parent.mkdir(parents=True, exist_ok=True)
|
||||
_add_defender_exclusion(exe)
|
||||
shutil.copy2(src, exe)
|
||||
_add_defender_exclusion(exe)
|
||||
digest = _hash_file(exe)
|
||||
if sha_src:
|
||||
expected = sha_src.read_text(encoding="utf-8").strip().lower()
|
||||
if expected and expected != digest:
|
||||
exe.unlink(missing_ok=True)
|
||||
log.warning(
|
||||
"Bundled gost.exe SHA mismatch (bundle=%s actual=%s) — "
|
||||
"falling back to network download.", expected, digest,
|
||||
)
|
||||
return False
|
||||
_write_pinned_exe_hash(exe, digest)
|
||||
log.info("GOST installed from bundle at %s", exe)
|
||||
return True
|
||||
except Exception as exc: # noqa: BLE001
|
||||
log.warning("Bundle install failed: %s", exc)
|
||||
return False
|
||||
|
||||
|
||||
def ensure_gost(target: Path | None = None) -> Path:
|
||||
exe = target or gost_exe_path()
|
||||
if exe.is_file() and exe.stat().st_size > 10_000:
|
||||
@@ -98,7 +141,7 @@ def ensure_gost(target: Path | None = None) -> Path:
|
||||
if actual == pinned:
|
||||
return exe
|
||||
log.warning(
|
||||
"gost.exe SHA256 mismatch on reuse — re-downloading.\n"
|
||||
"gost.exe SHA256 mismatch on reuse — re-installing.\n"
|
||||
" pinned: %s\n actual: %s",
|
||||
pinned, actual,
|
||||
)
|
||||
@@ -112,6 +155,11 @@ def ensure_gost(target: Path | None = None) -> Path:
|
||||
# SHA verification has already happened earlier in the session.
|
||||
_write_pinned_exe_hash(exe, _hash_file(exe))
|
||||
return exe
|
||||
|
||||
# Prefer the bundled binary so the first run works fully offline.
|
||||
if _install_from_bundle(exe):
|
||||
return exe
|
||||
|
||||
exe.parent.mkdir(parents=True, exist_ok=True)
|
||||
# Add exclusion BEFORE downloading so Defender doesn't nuke it on write
|
||||
_add_defender_exclusion(exe)
|
||||
|
||||
Reference in New Issue
Block a user