From d51c76a576848c4416f212f86fe841a7c7cb184a Mon Sep 17 00:00:00 2001 From: Adam Shiervani Date: Tue, 28 Apr 2026 11:02:36 +0200 Subject: [PATCH] fix(sync-releases): only treat ERRSIG rc=9 as missing pubkey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GnuPG's ERRSIG line carries an `rc` reason code. rc=9 is the only one that means "we don't have the signer's key" — rc=4 (unsupported algorithm) and other codes are real verification failures. The previous implementation collapsed every ERRSIG into noPubkey, which would have falsely told the operator the OTA root key was missing when the actual problem was e.g. an unsupported pubkey algorithm. Now parses the rc field and surfaces non-9 ERRSIG codes as `invalid` with a human reason (rc=4 → "unsupported algorithm", others → "gpg error code N"). --- scripts/sync-releases.ts | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/scripts/sync-releases.ts b/scripts/sync-releases.ts index f462f17..30e965a 100644 --- a/scripts/sync-releases.ts +++ b/scripts/sync-releases.ts @@ -148,9 +148,22 @@ function runGpgVerify( interface GpgStatus { validSig?: { signingFpr: string; rootFpr: string }; noPubkey?: boolean; + // ERRSIG `rc` field. GnuPG documents rc=4 (unsupported algorithm), + // rc=9 (missing public key); other codes are possible and we leave + // them as raw strings for the caller to format. + errSigRc?: string; badSig?: boolean; } +const ERRSIG_RC_REASONS: Record = { + "4": "unsupported algorithm", + "9": "missing public key", +}; + +function describeErrSigRc(rc: string): string { + return ERRSIG_RC_REASONS[rc] ?? `gpg error code ${rc}`; +} + function parseGpgStatus(statusOutput: string): GpgStatus { const result: GpgStatus = {}; for (const rawLine of statusOutput.split("\n")) { @@ -164,10 +177,17 @@ function parseGpgStatus(statusOutput: string): GpgStatus { if (parts.length >= 11) { result.validSig = { signingFpr: parts[1], rootFpr: parts[10] }; } - } else if (line.startsWith("NO_PUBKEY ") || line.startsWith("ERRSIG ")) { - // ERRSIG with rc=9 means missing pubkey too; treat both as no-pubkey - // unless we already saw an explicit BADSIG below. + } else if (line.startsWith("NO_PUBKEY ")) { result.noPubkey = true; + } else if (line.startsWith("ERRSIG ")) { + // ERRSIG