{"record":{"id":"455c25bed48302b7","repo":"NousResearch/hermes-agent","slug":"iron-proxy-checksums-txt-failed-gpg-signature-veri","errorCode":null,"errorMessage":"iron-proxy checksums.txt failed GPG signature verification — refusing to install (possible release-channel tampering). gpg: {verify.stderr.decode('utf-8', 'replace')[:300]}","messagePattern":"iron-proxy checksums\\.txt failed GPG signature verification — refusing to install \\(possible release-channel tampering\\)\\. gpg: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"critical","filePath":"agent/proxy_sources/iron_proxy.py","lineNumber":623,"sourceCode":"    imp = subprocess.run(  # noqa: S603 — gpg path from trusted PATH lookup\n        [*base_cmd, \"--import\", str(pubkey_path)],\n        capture_output=True, timeout=60,\n    )\n    if imp.returncode != 0:\n        logger.warning(\n            \"Could not import iron-proxy signing key — skipping GPG \"\n            \"verification (SHA-256 still enforced): %s\",\n            imp.stderr.decode(\"utf-8\", \"replace\")[:200],\n        )\n        return False\n\n    verify = subprocess.run(  # noqa: S603\n        [*base_cmd, \"--verify\", str(sig_path), str(checksum_path)],\n        capture_output=True, timeout=60,\n    )\n    if verify.returncode != 0:\n        # A present signature that does NOT verify is a tamper signal — fail hard.\n        raise RuntimeError(\n            \"iron-proxy checksums.txt failed GPG signature verification — \"\n            \"refusing to install (possible release-channel tampering). \"\n            f\"gpg: {verify.stderr.decode('utf-8', 'replace')[:300]}\"\n        )\n    logger.info(\"Verified iron-proxy checksums.txt GPG signature.\")\n    return True\n\n\ndef _expected_sha256(checksum_file: Path, asset_name: str) -> str:\n    \"\"\"Parse the standard ``sha256sum`` output: ``<hex>  <filename>``.\"\"\"\n\n    text = checksum_file.read_text(encoding=\"utf-8\", errors=\"replace\")\n    for line in text.splitlines():\n        parts = line.strip().split()\n        if len(parts) >= 2 and parts[-1] == asset_name:\n            return parts[0]\n    raise RuntimeError(\n        f\"No checksum entry for {asset_name} in {checksum_file.name}\"","sourceCodeStart":605,"sourceCodeEnd":641,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/agent/proxy_sources/iron_proxy.py#L605-L641","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","If the official release signature is genuinely broken, report it upstream and pin/rollback to the previous known-good _IRON_PROXY_VERSION."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import shutil\n\ndef gpg_available() -> bool:\n    return shutil.which(\"gpg\") is not None","typeGuard":null,"tryCatchPattern":"try:\n    find_iron_proxy(install_if_missing=True)\nexcept RuntimeError as e:\n    if \"GPG signature verification\" in str(e):\n        # hard stop: never bypass; escalate for manual verification of the release channel\n        raise","preventionTips":["Keep the pinned signing key in iron_proxy.py in sync with upstream key rotations.","Install from networks without content-rewriting middleboxes.","Never catch-and-continue on this error; a verified-bad signature means the channel is untrusted."],"tags":["security","gpg","tampering","install","iron-proxy"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}