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.
This commit is contained in:
parent
8fab1c5310
commit
e0132b6d6c
2 changed files with 75 additions and 17 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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", "")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue