Ship ShadowBroker v0.9.83 with live Infonet gate messaging and DM protocols.

Gate hashchain replication, Tor/SOCKS transport hardening, terminal session teardown, v0.9.83 UI/changelog, and release digest pins for seamless updater verification.
This commit is contained in:
BigBodyCobain
2026-06-15 15:37:29 -06:00
parent 8fcb01276c
commit 5ede669a12
35 changed files with 589 additions and 263 deletions
+84 -25
View File
@@ -33,6 +33,52 @@ TOR_INSTALL_DIR = TOR_DIR / "tor_bin"
_STARTUP_TIMEOUT_S = 90
_POLL_INTERVAL_S = 1.0
def _arti_socks_port() -> int:
from services.config import get_settings
return int(get_settings().MESH_ARTI_SOCKS_PORT or 9050)
def _torrc_socks_line(socks_port: int) -> str:
return f"SocksPort {socks_port}\n"
def _torrc_has_socks_port(socks_port: int) -> bool:
if not TORRC_PATH.exists():
return False
return _torrc_socks_line(socks_port) in TORRC_PATH.read_text(encoding="utf-8")
def _local_socks_listening(socks_port: int) -> bool:
return _local_socks_handshake_ready(socks_port, timeout=0.75)
def _local_socks_handshake_ready(socks_port: int, *, timeout: float = 5.0) -> bool:
import socket
try:
with socket.create_connection(("127.0.0.1", socks_port), timeout=timeout) as sock:
sock.settimeout(timeout)
sock.sendall(b"\x05\x01\x00")
return sock.recv(2) == b"\x05\x00"
except OSError:
return False
def _write_torrc(*, target_port: int, socks_port: int) -> None:
TOR_DIR.mkdir(parents=True, exist_ok=True)
hidden_service_dir = TOR_DIR / "hidden_service"
hidden_service_dir.mkdir(parents=True, exist_ok=True)
torrc_content = (
f"DataDirectory {TOR_DATA_DIR.as_posix()}\n"
f"HiddenServiceDir {hidden_service_dir.as_posix()}\n"
f"HiddenServicePort {target_port} 127.0.0.1:{target_port}\n"
f"{_torrc_socks_line(socks_port)}"
"Log notice stderr\n"
)
TORRC_PATH.write_text(torrc_content, encoding="utf-8")
# Windows x86_64 Tor Expert Bundle URLs. Keep a fallback so first-run
# onboarding does not break when Tor rotates point releases.
_TOR_EXPERT_BUNDLE_URLS = [
@@ -357,12 +403,28 @@ class TorHiddenService:
def start(self, target_port: int = 8000) -> dict:
"""Start Tor hidden service pointing to target_port on localhost."""
with self._lock:
socks_port = _arti_socks_port()
if self._running and self._process and self._process.poll() is None:
return {
"ok": True,
"onion_address": self._onion_address,
"detail": "already running",
}
if _torrc_has_socks_port(socks_port) and _local_socks_handshake_ready(socks_port, timeout=1.5):
return {
"ok": True,
"onion_address": self._onion_address,
"detail": "already running",
}
logger.info(
"Tor is running without a ready SOCKS proxy on port %s — restarting",
socks_port,
)
try:
self._process.terminate()
self._process.wait(timeout=10)
except Exception:
try:
self._process.kill()
except Exception:
pass
self._process = None
self._running = False
self._error = ""
tor_bin = _find_tor_binary()
@@ -388,20 +450,9 @@ class TorHiddenService:
except OSError:
pass
from services.config import get_settings
settings = get_settings()
socks_port_line = ""
if not bool(getattr(settings, "MESH_ARTI_ENABLED", False)):
socks_port_line = "SocksPort 9050\n"
torrc_content = (
f"DataDirectory {TOR_DATA_DIR.as_posix()}\n"
f"HiddenServiceDir {hidden_service_dir.as_posix()}\n"
f"HiddenServicePort {target_port} 127.0.0.1:{target_port}\n"
f"{socks_port_line}"
"Log notice stderr\n"
)
TORRC_PATH.write_text(torrc_content, encoding="utf-8")
# Mesh "Arti" transport uses Tor's local SOCKS proxy for .onion peers.
# Always publish SocksPort — MESH_ARTI_ENABLED only gates callers, not Tor.
_write_torrc(target_port=target_port, socks_port=socks_port)
try:
self._process = subprocess.Popen(
@@ -434,15 +485,23 @@ class TorHiddenService:
hostname = HOSTNAME_PATH.read_text().strip()
if hostname.endswith(".onion"):
self._onion_address = f"http://{hostname}:8000"
logger.info("Tor hidden service ready: %s", self._onion_address)
return {
"ok": True,
"onion_address": self._onion_address,
}
if _local_socks_handshake_ready(socks_port, timeout=3.0):
logger.info(
"Tor hidden service ready: %s (SOCKS %s)",
self._onion_address,
socks_port,
)
return {
"ok": True,
"onion_address": self._onion_address,
}
time.sleep(_POLL_INTERVAL_S)
self._error = f"Tor did not generate hostname within {_STARTUP_TIMEOUT_S}s"
self._error = (
f"Tor did not publish a ready hidden service and SOCKS proxy "
f"on port {socks_port} within {_STARTUP_TIMEOUT_S}s"
)
self.stop()
return {"ok": False, "detail": self._error}
+93 -11
View File
@@ -27,6 +27,13 @@ _STATE_CACHE_TS = 0.0
_STATE_CACHE_TTL_S = 2.0
_ARTI_PROOF_CACHE: dict[str, Any] = {"port": 0, "ok": False, "ts": 0.0}
_ARTI_PROOF_CACHE_TTL_S = 30.0
_ARTI_STATUS_CACHE: dict[str, Any] = {"port": 0, "ready": False, "ts": 0.0}
_ARTI_STATUS_FAIL_TTL_S = 4.0
_ARTI_PROBE_LOCK = threading.Lock()
_ARTI_SOCKS_FAILURES = 0
_ARTI_LAST_TOR_RECOVERY_TS = 0.0
_ARTI_TOR_RECOVERY_COOLDOWN_S = 45.0
_ARTI_SOCKS_CONNECT_TIMEOUT_S = 5.0
_PRIVATE_CLEARNET_FALLBACK_WINDOW_S = 300.0
BACKEND_DIR = Path(__file__).resolve().parent.parent
@@ -70,16 +77,43 @@ _WORMHOLE_ENV_EXPLICIT = {
"PRIVACY_CORE_MIN_VERSION",
}
def _check_arti_ready() -> bool:
from services.config import get_settings
def invalidate_arti_ready_cache() -> None:
_ARTI_PROOF_CACHE.update({"port": 0, "ok": False, "ts": 0.0})
_ARTI_STATUS_CACHE.update({"port": 0, "ready": False, "ts": 0.0})
settings = get_settings()
if not bool(settings.MESH_ARTI_ENABLED):
return False
socks_port = int(settings.MESH_ARTI_SOCKS_PORT or 9050)
def _maybe_recover_tor_socks_transport(socks_port: int) -> None:
global _ARTI_SOCKS_FAILURES, _ARTI_LAST_TOR_RECOVERY_TS
_ARTI_SOCKS_FAILURES += 1
if _ARTI_SOCKS_FAILURES < 3:
return
now = time.time()
if (now - _ARTI_LAST_TOR_RECOVERY_TS) < _ARTI_TOR_RECOVERY_COOLDOWN_S:
return
_ARTI_LAST_TOR_RECOVERY_TS = now
_ARTI_SOCKS_FAILURES = 0
try:
with socket.create_connection((WORMHOLE_HOST, socks_port), timeout=2.0) as sock:
# SOCKS5 greeting: version 5, 1 auth method, no-auth.
from services.tor_hidden_service import tor_service
logger.warning(
"Tor SOCKS on port %s is wedged — recycling Tor hidden service",
socks_port,
)
tor_service.stop()
tor_service.start(target_port=8000)
invalidate_arti_ready_cache()
except Exception as exc:
logger.warning("Tor SOCKS recovery failed: %s", exc)
def _probe_arti_socks_ready(socks_port: int) -> bool:
try:
with socket.create_connection(
(WORMHOLE_HOST, socks_port),
timeout=_ARTI_SOCKS_CONNECT_TIMEOUT_S,
) as sock:
sock.settimeout(_ARTI_SOCKS_CONNECT_TIMEOUT_S)
sock.sendall(b"\x05\x01\x00")
response = sock.recv(2)
if response != b"\x05\x00":
@@ -88,6 +122,53 @@ def _check_arti_ready() -> bool:
except Exception as exc:
logger.warning("Arti SOCKS check failed on port %s: %s", socks_port, exc)
return False
return True
def _check_arti_ready(*, force: bool = False) -> bool:
from services.config import get_settings
settings = get_settings()
if not bool(settings.MESH_ARTI_ENABLED):
return False
socks_port = int(settings.MESH_ARTI_SOCKS_PORT or 9050)
now = time.time()
if not force:
if (
int(_ARTI_STATUS_CACHE.get("port", 0) or 0) == socks_port
and (now - float(_ARTI_STATUS_CACHE.get("ts", 0.0) or 0.0)) < _ARTI_STATUS_FAIL_TTL_S
):
return bool(_ARTI_STATUS_CACHE.get("ready"))
if (
int(_ARTI_PROOF_CACHE.get("port", 0) or 0) == socks_port
and bool(_ARTI_PROOF_CACHE.get("ok"))
and (now - float(_ARTI_PROOF_CACHE.get("ts", 0.0) or 0.0)) < _ARTI_PROOF_CACHE_TTL_S
):
return True
with _ARTI_PROBE_LOCK:
now = time.time()
if not force:
if (
int(_ARTI_STATUS_CACHE.get("port", 0) or 0) == socks_port
and (now - float(_ARTI_STATUS_CACHE.get("ts", 0.0) or 0.0)) < _ARTI_STATUS_FAIL_TTL_S
):
return bool(_ARTI_STATUS_CACHE.get("ready"))
if (
int(_ARTI_PROOF_CACHE.get("port", 0) or 0) == socks_port
and bool(_ARTI_PROOF_CACHE.get("ok"))
and (now - float(_ARTI_PROOF_CACHE.get("ts", 0.0) or 0.0)) < _ARTI_PROOF_CACHE_TTL_S
):
return True
if not _probe_arti_socks_ready(socks_port):
_ARTI_STATUS_CACHE.update({"port": socks_port, "ready": False, "ts": now})
_maybe_recover_tor_socks_transport(socks_port)
return False
global _ARTI_SOCKS_FAILURES
_ARTI_SOCKS_FAILURES = 0
_ARTI_STATUS_CACHE.update({"port": socks_port, "ready": True, "ts": now})
now = time.time()
if (
@@ -110,12 +191,13 @@ def _check_arti_ready() -> bool:
is_tor = bool(payload.get("IsTor")) or bool(payload.get("is_tor"))
if not (response.ok and is_tor):
logger.warning(
"Arti Tor proof failed (status=%s is_tor=%s) — SOCKS is up, using Arti anyway",
"Arti Tor proof failed (status=%s is_tor=%s)",
getattr(response, "status_code", "unknown"),
payload.get("IsTor", payload.get("is_tor")),
)
_ARTI_PROOF_CACHE.update({"port": socks_port, "ok": True, "ts": now})
return True
_ARTI_PROOF_CACHE.update({"port": socks_port, "ok": False, "ts": now})
_ARTI_STATUS_CACHE.update({"port": socks_port, "ready": False, "ts": now})
return False
_ARTI_PROOF_CACHE.update({"port": socks_port, "ok": True, "ts": now})
return True
except Exception as exc: