From e0132b6d6c414cece2bced7eaf164eeebe088dd1 Mon Sep 17 00:00:00 2001 From: Lee Jackson <130007945+Imagineer99@users.noreply.github.com> Date: Mon, 20 Jul 2026 05:04:28 +0100 Subject: [PATCH] Pin the Hermes remote installer and harden consent (#7179) Pin the fetched Hermes install.sh/install.ps1 and the checkout they perform to an immutable upstream commit, and distinguish pinned from unpinned sources in the consent warning. --- unsloth_cli/commands/start.py | 54 +++++++++++++++++++++++++++------ unsloth_cli/tests/test_start.py | 38 ++++++++++++++++++----- 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/unsloth_cli/commands/start.py b/unsloth_cli/commands/start.py index 8128447a02..6da31229f8 100644 --- a/unsloth_cli/commands/start.py +++ b/unsloth_cli/commands/start.py @@ -49,13 +49,22 @@ _HERMES_PROVIDER = "unsloth" # the wizard's global API-key/model prompts would block the launch and point the # user at a different (global) provider than the one Unsloth just configured. # Both installers expose a skip flag: `-SkipSetup` (PowerShell) and -# `--skip-setup` (POSIX; passed to the piped script via `bash -s --`). +# `--skip-setup` (POSIX; passed to the piped script via `bash -s --`). Pin both +# the fetched script and the repository checkout it performs to the same full +# commit so a later change to either upstream branch cannot silently replace +# code that Unsloth executes with the user's privileges. +_HERMES_INSTALL_COMMIT = "f1af945f6c576eccb126fa955edc9be258b33020" +_HERMES_INSTALL_BASE = ( + "https://raw.githubusercontent.com/NousResearch/hermes-agent/" + f"{_HERMES_INSTALL_COMMIT}/scripts" +) _HERMES_WINDOWS_INSTALL_HINT = ( - "& ([scriptblock]::Create((irm https://hermes-agent.nousresearch.com/install.ps1))) -SkipSetup" + f"& ([scriptblock]::Create((irm {_HERMES_INSTALL_BASE}/install.ps1)))" + f" -SkipSetup -Commit {_HERMES_INSTALL_COMMIT}" ) _HERMES_POSIX_INSTALL_HINT = ( - "curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent" - "/main/scripts/install.sh | bash -s -- --skip-setup" + f"curl -fsSL {_HERMES_INSTALL_BASE}/install.sh | bash -s --" + f" --skip-setup --commit {_HERMES_INSTALL_COMMIT}" ) # Hermes refuses to initialize when the model window is under 64,000 tokens; its # error message points at the model.context_length / auxiliary.compression @@ -1199,6 +1208,16 @@ def _install_source(install_hint: str) -> Optional[str]: return match.group(0) if match else None +def _pinned_raw_github_commit(source: str) -> Optional[str]: + """Return the immutable full commit in a raw GitHub URL, if present.""" + match = re.match( + r"^https://raw\.githubusercontent\.com/[^/]+/[^/]+/([0-9a-f]{40})/", + source, + flags = re.IGNORECASE, + ) + return match.group(1).lower() if match else None + + def _install_agent(name: str, install_hint: str) -> Optional[str]: # Missing agent under --launch: offer to run its documented install command, then # re-resolve it on PATH. Consent-based (we never auto-run a remote install script @@ -1212,12 +1231,27 @@ def _install_agent(name: str, install_hint: str) -> Optional[str]: # and nothing checks a signature or hash on the fetched content. Naming the source # turns a blind "yes" into informed consent. source = _install_source(install_hint) - warning = ( - f"This will download and RUN a script from {source} with your privileges" - if source - else f"This will RUN `{install_hint}` with your privileges" - ) - typer.secho(f"{warning}; there is no signature or hash check.", fg = "yellow", err = True) + if source: + pinned_commit = _pinned_raw_github_commit(source) + if pinned_commit: + warning = ( + "Security warning: This will download and execute a third-party script " + f"from {source} with your privileges. Unsloth pins this content to " + f"immutable upstream commit {pinned_commit}, but does not independently " + "verify or sandbox it. Continue only if you trust this source and commit." + ) + else: + warning = ( + "Security warning: This will download and execute an unverified third-party " + f"script from {source} with your privileges. Unsloth does not pin or verify " + "the downloaded content. Continue only if you trust this source." + ) + else: + warning = ( + f"This will RUN `{install_hint}` with your privileges; " + "there is no signature or hash check." + ) + typer.secho(warning, fg = "yellow", err = True) if not typer.confirm(f"Install `{name}` now with `{install_hint}`?", default = False): return None # Run each hint through the shell it is written for: PowerShell (irm | iex, or npm) diff --git a/unsloth_cli/tests/test_start.py b/unsloth_cli/tests/test_start.py index 5b2806be12..98bd9f8157 100644 --- a/unsloth_cli/tests/test_start.py +++ b/unsloth_cli/tests/test_start.py @@ -7,6 +7,7 @@ from __future__ import annotations import json import os +import re import shlex import sys import urllib.error @@ -128,7 +129,7 @@ def test_install_agent_uses_powershell_on_windows(monkeypatch): assert ran == [["powershell", "-NoProfile", "-Command", install_hint]] -def test_install_agent_warns_and_names_remote_source(monkeypatch, capsys): +def test_install_agent_warns_remote_installer_is_unverified_third_party(monkeypatch, capsys): # Before the confirm, a remote installer must name the URL it fetches so the # user consents to a specific source rather than blindly accepting. monkeypatch.setattr(start.os, "name", "nt") @@ -137,9 +138,23 @@ def test_install_agent_warns_and_names_remote_source(monkeypatch, capsys): hint = "& ([scriptblock]::Create((irm https://hermes-agent.nousresearch.com/install.ps1))) -SkipSetup" assert start._install_agent("hermes", hint) is None err = capsys.readouterr().err + assert "Security warning" in err + assert "unverified third-party script" in err assert "https://hermes-agent.nousresearch.com/install.ps1" in err - assert "download and RUN" in err - assert "signature or hash" in err + assert "Unsloth does not pin or verify the downloaded content" in err + assert "Continue only if you trust this source" in err + + +def test_install_agent_reports_immutable_remote_installer_pin(monkeypatch, capsys): + monkeypatch.setattr(start.os, "name", "posix") + monkeypatch.setattr(start.sys, "stdin", SimpleNamespace(isatty = lambda: True)) + monkeypatch.setattr(start.typer, "confirm", lambda *a, **k: False) + assert start._install_agent("hermes", start._HERMES_POSIX_INSTALL_HINT) is None + err = capsys.readouterr().err + assert start._HERMES_INSTALL_COMMIT in err + assert "immutable upstream commit" in err + assert "does not independently verify or sandbox it" in err + assert "does not pin or verify" not in err def test_install_agent_warns_for_package_installer(monkeypatch, capsys): @@ -160,8 +175,8 @@ def test_hermes_install_hint_is_windows_native_on_windows(monkeypatch): # Scriptblock form so `-SkipSetup` reaches the installer and the interactive # setup wizard is skipped during the unattended `unsloth start hermes` run. assert start._hermes_install_hint() == ( - "& ([scriptblock]::Create((irm https://hermes-agent.nousresearch.com/install.ps1)))" - " -SkipSetup" + f"& ([scriptblock]::Create((irm {start._HERMES_INSTALL_BASE}/install.ps1)))" + f" -SkipSetup -Commit {start._HERMES_INSTALL_COMMIT}" ) @@ -170,11 +185,20 @@ def test_hermes_install_hint_is_bash_on_posix(monkeypatch): # `bash -s -- --skip-setup` forwards the skip flag to the piped installer. assert start._hermes_install_hint() == ( - "curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent" - "/main/scripts/install.sh | bash -s -- --skip-setup" + f"curl -fsSL {start._HERMES_INSTALL_BASE}/install.sh | bash -s --" + f" --skip-setup --commit {start._HERMES_INSTALL_COMMIT}" ) +def test_hermes_install_hints_pin_script_and_checkout_to_full_commit(): + commit = start._HERMES_INSTALL_COMMIT + assert re.fullmatch(r"[0-9a-f]{40}", commit) + for hint in (start._HERMES_WINDOWS_INSTALL_HINT, start._HERMES_POSIX_INSTALL_HINT): + assert hint.count(commit) == 2 + assert "/main/" not in hint + assert "hermes-agent.nousresearch.com" not in hint + + def test_refresh_windows_path_noop_off_windows(monkeypatch): monkeypatch.setattr(start.os, "name", "posix") before = os.environ.get("PATH", "")