fix(security): don't warn while tirith's first download runs
The first CLI launch after a PM install starts tirith's download in the background, so ensure_installed() returns None and the CLI printed "tirith security scanner enabled but not available". The scanner was on its way, not missing; the same warning also fired when lazy installs are disabled by the operator's own policy. missing_is_expected() tells those by-design states apart. The CLI logs them and keeps the visible warning for a missing explicit tirith_path or a finished download that failed.
This commit is contained in:
19
cli.py
19
cli.py
@@ -972,18 +972,23 @@ class HermesCLI(CLIInitMixin, CLITuiRuntimeMixin, CLIProcessNotificationsMixin,
|
|||||||
return
|
return
|
||||||
self._tirith_security_checked = True
|
self._tirith_security_checked = True
|
||||||
try:
|
try:
|
||||||
from tools.tirith_security import ensure_installed, is_platform_supported
|
from tools.tirith_security import ensure_installed, is_platform_supported, missing_is_expected
|
||||||
|
|
||||||
if (
|
if (
|
||||||
ensure_installed(log_failures=False) is None and is_platform_supported()
|
ensure_installed(log_failures=False) is None and is_platform_supported()
|
||||||
and (self.config.get("security", {}) or {}).get("tirith_enabled", True)
|
and (self.config.get("security", {}) or {}).get("tirith_enabled", True)
|
||||||
):
|
):
|
||||||
_cprint(
|
# First launch after install downloads tirith in the background;
|
||||||
f" {_DIM}⚠ tirith security scanner enabled but not available "
|
# warning then would report a fault that resolves itself.
|
||||||
f"— command scanning will use pattern matching only{_RST}"
|
if missing_is_expected():
|
||||||
)
|
logger.info("tirith not ready (downloading or lazy installs off); pattern matching only")
|
||||||
except Exception:
|
else:
|
||||||
pass
|
_cprint(
|
||||||
|
f" {_DIM}⚠ tirith security scanner enabled but not available "
|
||||||
|
f"— command scanning will use pattern matching only{_RST}"
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug("tirith availability check failed: %s", exc)
|
||||||
|
|
||||||
def _show_security_advisories(self):
|
def _show_security_advisories(self):
|
||||||
"""Startup banner for unacked security advisories, on stderr (piped stdout stays clean); 24h rate-limited."""
|
"""Startup banner for unacked security advisories, on stderr (piped stdout stays clean); 24h rate-limited."""
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ def test_tirith_opt_in_background_and_explicit_override(consumer_store, tmp_path
|
|||||||
monkeypatch.setenv("TIRITH_BIN", str(tmp_path / "missing"))
|
monkeypatch.setenv("TIRITH_BIN", str(tmp_path / "missing"))
|
||||||
assert tirith.ensure_installed(explicit=True) is None
|
assert tirith.ensure_installed(explicit=True) is None
|
||||||
assert not RangeHandler.ranges_seen
|
assert not RangeHandler.ranges_seen
|
||||||
|
assert not tirith.missing_is_expected(), "a missing explicit binary must be reported"
|
||||||
external = tmp_path / "external-tirith"
|
external = tmp_path / "external-tirith"
|
||||||
external.write_text(f"#!{sys.executable}\nimport json,sys\nprint(json.dumps({{'summary':'external'}}))\nsys.exit(1)\n")
|
external.write_text(f"#!{sys.executable}\nimport json,sys\nprint(json.dumps({{'summary':'external'}}))\nsys.exit(1)\n")
|
||||||
external.chmod(0o755)
|
external.chmod(0o755)
|
||||||
@@ -201,6 +202,7 @@ def test_tirith_opt_in_background_and_explicit_override(consumer_store, tmp_path
|
|||||||
assert tirith.ensure_installed() is None
|
assert tirith.ensure_installed() is None
|
||||||
assert entered.wait(10)
|
assert entered.wait(10)
|
||||||
assert pm.installed_package("tirith") is None
|
assert pm.installed_package("tirith") is None
|
||||||
|
assert tirith.missing_is_expected(), "an in-flight first download is not a fault"
|
||||||
finally:
|
finally:
|
||||||
release.set()
|
release.set()
|
||||||
for thread in tirith._install_threads.values():
|
for thread in tirith._install_threads.values():
|
||||||
|
|||||||
@@ -229,6 +229,24 @@ def ensure_installed(*, log_failures: bool = True, explicit: bool = False):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def missing_is_expected() -> bool:
|
||||||
|
"""Whether an unresolved default tirith is by design rather than a fault.
|
||||||
|
|
||||||
|
The first launch after a PM install starts the download in the background,
|
||||||
|
and a lazy-install policy refusal is the operator's choice; neither is
|
||||||
|
actionable. A missing explicit ``tirith_path`` always is.
|
||||||
|
"""
|
||||||
|
import pm
|
||||||
|
|
||||||
|
configured = _load_security_config()["tirith_path"]
|
||||||
|
if configured != "tirith":
|
||||||
|
return False
|
||||||
|
thread = _install_threads.get(hermes_home_key())
|
||||||
|
if thread is not None and thread.is_alive():
|
||||||
|
return True
|
||||||
|
return _local_tirith(configured) is not None or not pm.lazy_installs_allowed()
|
||||||
|
|
||||||
|
|
||||||
# --- Main API ---
|
# --- Main API ---
|
||||||
_MAX_FINDINGS = 50
|
_MAX_FINDINGS = 50
|
||||||
_MAX_SUMMARY_LEN = 500
|
_MAX_SUMMARY_LEN = 500
|
||||||
|
|||||||
Reference in New Issue
Block a user