getsops/sops · error

GnuPG binary error: %w

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.

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)

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.