fix(windows): silence tirith-unavailable banner + skip install/spawn attempts on unsupported platforms (#26718)
Some checks are pending
Deploy Site / deploy-vercel (push) Waiting to run
Deploy Site / deploy-docs (push) Waiting to run
Docker Build and Publish / build-amd64 (push) Waiting to run
Docker Build and Publish / build-arm64 (push) Waiting to run
Docker Build and Publish / merge (push) Blocked by required conditions
Docker Build and Publish / move-main (push) Blocked by required conditions
Docker Build and Publish / move-latest (push) Blocked by required conditions
Lint (ruff + ty) / ruff + ty diff (push) Waiting to run
Lint (ruff + ty) / ruff enforcement (blocking) (push) Waiting to run
Lint (ruff + ty) / Windows footguns (blocking) (push) Waiting to run
Nix / nix (macos-latest) (push) Waiting to run
Nix / nix (ubuntu-latest) (push) Waiting to run
OSV-Scanner / Scan lockfiles (push) Waiting to run
Tests / test (push) Waiting to run
Tests / e2e (push) Waiting to run
uv.lock check / uv lock --check (push) Waiting to run

Tirith ships no Windows binary, so on every Windows CLI startup users
saw a scary 'tirith security scanner enabled but not available' banner
they could not act on. The banner suggested degraded security; in
reality pattern-matching guards still run and the message was pure noise.

Fix:
- New public is_platform_supported() helper in tools/tirith_security.py
  that returns False when _detect_target() doesn't resolve (Windows, any
  non-x86_64/aarch64 arch).
- ensure_installed(), _resolve_tirith_path(), and check_command_security()
  short-circuit on unsupported platforms: cache _resolved_path =
  _INSTALL_FAILED with reason 'unsupported_platform', skip PATH probes,
  skip the background download thread, skip the disk failure marker, and
  return allow with an empty summary from check_command_security so the
  spawn loop never fires.
- Explicit user-configured tirith_path is still honored everywhere (a
  user who built tirith themselves under WSL keeps that path).
- CLI banner in cli.py gated on is_platform_supported() — fires only on
  platforms where tirith *should* work but isn't installed.
- Docs note tirith's supported-platform list and point Windows users at
  WSL.

Tests: tests/tools/test_tirith_security.py +8 tests covering Linux
x86_64, Darwin arm64, Windows, and unknown-arch verdicts plus the
silent ensure_installed / check_command_security / _resolve_tirith_path
fast-paths and the explicit-path override.

  test_tirith_security.py     75 passed (8 new + 67 pre-existing)
  test_command_guards.py      19 passed
This commit is contained in:
Teknium 2026-05-15 20:29:28 -07:00 committed by GitHub
parent a31191c3f5
commit c5dc9700eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 143 additions and 4 deletions

View file

@ -214,7 +214,12 @@ def _hermes_bin_dir() -> str:
def _detect_target() -> str | None:
"""Return the Rust target triple for the current platform, or None."""
"""Return the Rust target triple for the current platform, or None.
Windows is intentionally unsupported tirith does not ship a Windows
build. Callers should treat `None` as "this platform will never have
tirith" and silently fall back to pattern-matching guards.
"""
system = platform.system()
machine = platform.machine().lower()
@ -236,6 +241,16 @@ def _detect_target() -> str | None:
return f"{arch}-{plat}"
def is_platform_supported() -> bool:
"""True when tirith ships a prebuilt binary for this OS+arch.
Used by callers (CLI banner, etc.) to distinguish "tirith failed to
install" from "tirith was never going to install here" — the latter
is silent because there is nothing the user can do about it.
"""
return _detect_target() is not None
def _download_file(url: str, dest: str, timeout: int = 10):
"""Download a URL to a local file."""
req = urllib.request.Request(url)
@ -448,6 +463,15 @@ def _resolve_tirith_path(configured_path: str) -> str:
explicit = _is_explicit_path(configured_path)
install_failed = _resolved_path is _INSTALL_FAILED
# Platform has no tirith build (Windows etc.). Cache the verdict and
# return the unexpanded configured path — the spawn loop will fail-open
# via the dedupe'd OSError handler, but only after the first call; on
# subsequent calls the fast-path above short-circuits before spawning.
if not explicit and not is_platform_supported():
_resolved_path = _INSTALL_FAILED
_install_failure_reason = "unsupported_platform"
return expanded
# Explicit path: check it and stop. Never auto-download a replacement.
if explicit:
if os.path.isfile(expanded) and os.access(expanded, os.X_OK):
@ -574,6 +598,14 @@ def ensure_installed(*, log_failures: bool = True):
return path
return None
# Platform has no tirith build (e.g. Windows) — don't probe PATH,
# don't start a download thread, don't write a disk failure marker.
# Pattern-matching guards still run; this path stays silent.
if not is_platform_supported():
_resolved_path = _INSTALL_FAILED
_install_failure_reason = "unsupported_platform"
return None
configured_path = cfg["tirith_path"]
explicit = _is_explicit_path(configured_path)
expanded = os.path.expanduser(configured_path)
@ -659,6 +691,12 @@ def check_command_security(command: str) -> dict:
if not cfg["tirith_enabled"]:
return {"action": "allow", "findings": [], "summary": ""}
# Unsupported platform (Windows etc.) — tirith has no binary here and
# never will. Skip the resolver entirely so we don't even try to spawn.
# Pattern-matching guards still run via the rest of approval.py.
if not is_platform_supported():
return {"action": "allow", "findings": [], "summary": ""}
tirith_path = _resolve_tirith_path(cfg["tirith_path"])
timeout = cfg["tirith_timeout"]
fail_open = cfg["tirith_fail_open"]