NousResearch/hermes-agent · critical · RuntimeError

iron-proxy checksums.txt failed GPG signature verification —

Error message

iron-proxy checksums.txt failed GPG signature verification — refusing to install (possible release-channel tampering). gpg: {verify.stderr.decode('utf-8', 'replace')[:300]}

What it means

The installer downloads checksums.txt.asc and the pinned public key, imports the key into an ephemeral GPG keyring, and verifies the detached signature. A present signature that FAILS verification is treated as a tamper signal and hard-fails the install (unlike a missing gpg/signature, which only warns and falls back to SHA-256). This is stricter than the checksum check alone because it catches a rewritten binary+checksum pair.

Source

Thrown at agent/proxy_sources/iron_proxy.py:623

    imp = subprocess.run(  # noqa: S603 — gpg path from trusted PATH lookup
        [*base_cmd, "--import", str(pubkey_path)],
        capture_output=True, timeout=60,
    )
    if imp.returncode != 0:
        logger.warning(
            "Could not import iron-proxy signing key — skipping GPG "
            "verification (SHA-256 still enforced): %s",
            imp.stderr.decode("utf-8", "replace")[:200],
        )
        return False

    verify = subprocess.run(  # noqa: S603
        [*base_cmd, "--verify", str(sig_path), str(checksum_path)],
        capture_output=True, timeout=60,
    )
    if verify.returncode != 0:
        # A present signature that does NOT verify is a tamper signal — fail hard.
        raise RuntimeError(
            "iron-proxy checksums.txt failed GPG signature verification — "
            "refusing to install (possible release-channel tampering). "
            f"gpg: {verify.stderr.decode('utf-8', 'replace')[:300]}"
        )
    logger.info("Verified iron-proxy checksums.txt GPG signature.")
    return True


def _expected_sha256(checksum_file: Path, asset_name: str) -> str:
    """Parse the standard ``sha256sum`` output: ``<hex>  <filename>``."""

    text = checksum_file.read_text(encoding="utf-8", errors="replace")
    for line in text.splitlines():
        parts = line.strip().split()
        if len(parts) >= 2 and parts[-1] == asset_name:
            return parts[0]
    raise RuntimeError(
        f"No checksum entry for {asset_name} in {checksum_file.name}"

View on GitHub (pinned to c896c09c42)

Solutions

  1. Do not retry-bypass: verify the pinned key is still the project's current signing key (check the project's KEYLESS/changelog); if the key legitimately rotated, update the pinned key in iron_proxy.py.
  2. Re-download checksums.txt and checksums.txt.asc manually from the official release page and confirm `gpg --verify` against the published key — if it verifies there but not through Hermes, your download channel is being modified; fix the proxy/network.
  3. If the official release signature is genuinely broken, report it upstream and pin/rollback to the previous known-good _IRON_PROXY_VERSION.
Defensive patterns

Strategy: try-catch

Validate before calling

import shutil

def gpg_available() -> bool:
    return shutil.which("gpg") is not None

Try / catch

try:
    find_iron_proxy(install_if_missing=True)
except RuntimeError as e:
    if "GPG signature verification" in str(e):
        # hard stop: never bypass; escalate for manual verification of the release channel
        raise

Prevention

When it happens

Trigger: find_iron_proxy(install_if_missing=True) / `hermes egress install` where gpg --verify of checksums.txt.asc exits non-zero: the served checksums.txt or .asc was modified in transit (MITM proxy), the release key rotated and the pinned key no longer matches, or the signature file itself is corrupted/truncated by the same flaky channel.

Common situations: Corporate TLS-intercepting proxies that also rewrite downloaded files; upstream re-signing releases with a new key after the pin in this file was set; a partially-downloaded .asc from a dropped connection.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/455c25bed48302b7. Report an issue: GitHub.