getsops/sops · error

GnuPG binary error

Error message

GnuPG binary error: %w

What it means

If the OpenPGP attempt fails, EncryptContext falls back to running the gpg binary via encryptWithGnuPG; a failure there is appended as 'GnuPG binary error: %w'. It means the external gpg executable itself failed (non-zero exit) when trying to encrypt the data key. Both errors are later aggregated into the 'could not encrypt data key with PGP key' error.

Solutions

  1. Check the wrapped cause for gpg's stderr (e.g. 'No public key', 'general error')
  2. Verify the binary: gpg --version, and fix SOPS_GPG_EXEC if set to a wrong path
  3. Import the recipient key: gpg --import <pubkey> and confirm with gpg --list-keys <fingerprint>
  4. If the key is expired/revoked, extend or replace it and update .sops.yaml

Example fix

// before
export SOPS_GPG_EXEC=/usr/local/bin/gpg2  # binary does not exist
// after
unset SOPS_GPG_EXEC  # or export SOPS_GPG_EXEC=$(command -v gpg)
Defensive patterns

Strategy: validation

Validate before calling

gpgExec := os.Getenv("SOPS_GPG_EXEC")
if gpgExec == "" { gpgExec = "gpg" }
if _, err := exec.LookPath(gpgExec); err != nil {
    return fmt.Errorf("gpg binary %q not found: %w", gpgExec, err)
}
if out, err := exec.Command(gpgExec, "--version").CombinedOutput(); err != nil {
    return fmt.Errorf("gpg not runnable: %v: %s", err, out)
}

Try / catch

if err := key.EncryptContext(ctx, dataKey); err != nil {
    if strings.Contains(err.Error(), "GnuPG binary error") {
        // surface gpg stderr to the operator, do not retry blindly
        log.Errorf("gpg fallback failed: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling EncryptContext when the gpg binary is missing/unusable (bad SOPS_GPG_EXEC path), the GnuPG home is invalid, the key is absent from the binary keyring, or gpg exits with an error (e.g. 'No public key', unusable pubkey).

Common situations: SOPS_GPG_EXEC pointing to a nonexistent binary; gpg not installed in a container; key not imported for the current user; expired recipient key causing gpg to refuse encryption.

Related errors


AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01). Data as JSON: /api/errors/3921811bc3d6c57f. Report an issue: GitHub.

Appendix: source

Thrown at pgp/keysource.go:292

// fingerprint as the MasterKey.
func (key *MasterKey) EncryptContext(ctx context.Context, dataKey []byte) error {
	var errs errSet

	if !key.disableOpenPGP {
		openpgpErr := key.encryptWithOpenPGP(dataKey)
		if openpgpErr == nil {
			log.WithField("fingerprint", key.Fingerprint).Info("Encryption succeeded")
			return nil
		}
		errs = append(errs, fmt.Errorf("github.com/ProtonMail/go-crypto/openpgp error: %w", openpgpErr))
	}

	binaryErr := key.encryptWithGnuPG(ctx, dataKey)
	if binaryErr == nil {
		log.WithField("fingerprint", key.Fingerprint).Info("Encryption succeeded")
		return nil
	}
	errs = append(errs, fmt.Errorf("GnuPG binary error: %w", binaryErr))

	log.WithField("fingerprint", key.Fingerprint).Info("Encryption failed")
	return fmt.Errorf("could not encrypt data key with PGP key: %w", errs)
}

// encryptWithOpenPGP attempts to encrypt the data key using OpenPGP with the
// PGP key that belongs to Fingerprint. It sets EncryptedDataKey, or returns
// an error.
func (key *MasterKey) encryptWithOpenPGP(dataKey []byte) error {
	entity, err := key.retrievePubKey()
	if err != nil {
		return err
	}

	encBuf := new(bytes.Buffer)
	armorBuf, err := armor.Encode(encBuf, "PGP MESSAGE", nil)
	if err != nil {
		return err

View on GitHub (pinned to 13442bb981)