getsops/sops · error

failed to copy age decrypted data into bytes.Buffer

Error message

failed to copy age decrypted data into bytes.Buffer: %w

What it means

sops failed to read the plaintext output produced by the age decryption stream into memory. After a successful age.Decrypt call, the resulting reader r is copied into a bytes.Buffer; an error here means the underlying reader (e.g. the armored/file stream wrapping the data key) returned an I/O error mid-read, so decryption is aborted. This indicates the decrypted stream was corrupt, truncated, or unreadable, not that key decryption itself failed.

Solutions

  1. Verify the encrypted file is complete and unmodified (git checkout the original file, re-download, or re-encrypt from source).
  2. Re-run sops with SOPS_AGE_DEBUG or general logging to see which key/reader errored.
  3. If using an age plugin recipient, confirm the plugin binary is installed, executable, and works with a direct age round-trip.
  4. Check disk space and file permissions on the input file; retry the decrypt.
  5. Regenerate the data key by re-encrypting the file with a known-good age identity.

Example fix

// before: decrypting a truncated file pulled from a partial sync
sops -d secrets.enc.yaml
// failed to copy age decrypted data into bytes.Buffer: unexpected EOF

// after: restore the complete file, then decrypt
git checkout -- secrets.enc.yaml
sops -d secrets.enc.yaml
Defensive patterns

Strategy: try-catch

Validate before calling

// Go caller
if info, err := os.Stat(encFile); err != nil || info.Size() == 0 {
    return fmt.Errorf("encrypted file missing or empty: %s", encFile)
}
// verify round-trip once at deploy time:
// sops -d encFile > /dev/null && echo ok

Try / catch

plaintext, err := key.Decrypt()
if err != nil {
    var agg *sopskey.ErrDecrypt  // or use errors.Is/As on the wrapped cause
    if errors.As(err, &agg) {
        log.Printf("age decrypt failed (corrupt/truncated stream?): %v", err)
        return fmt.Errorf("re-encrypt from source or restore file: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: MasterKey.Decrypt (age/keysource.go) calls io.Copy into a bytes.Buffer and the underlying reader returns an error before EOF: corrupted or truncated sops file, an age plugin binary that exits early, or a reader that errors after partial output.

Common situations: Sops-encrypted files damaged by partial writes or bad git merges; age plugin binaries (age-plugin-yubikey etc.) crashing or emitting garbage; piping truncated input into sops decrypt; filesystem/permission problems surfacing as read errors mid-stream.

Related errors


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

Appendix: source

Thrown at age/keysource.go:267

		if len(ids) == 0 {
			log.Info("Decryption failed")
			return nil, formatError("failed to load age identities", nil, errs, unusedLocations)
		}
		ids.ApplyToMasterKey(key)
	}

	src := bytes.NewReader([]byte(key.EncryptedKey))
	ar := armor.NewReader(src)
	r, err := age.Decrypt(ar, key.parsedIdentities...)
	if err != nil {
		log.Info("Decryption failed")
		return nil, formatError("failed to create reader for decrypting sops data key with age", err, errs, unusedLocations)
	}

	var b bytes.Buffer
	if _, err := io.Copy(&b, r); err != nil {
		log.Info("Decryption failed")
		return nil, fmt.Errorf("failed to copy age decrypted data into bytes.Buffer: %w", err)
	}

	log.Info("Decryption succeeded")
	return b.Bytes(), nil
}

// NeedsRotation returns whether the data key needs to be rotated or not.
func (key *MasterKey) NeedsRotation() bool {
	return false
}

// ToString converts the key to a string representation.
func (key *MasterKey) ToString() string {
	return key.Recipient
}

// ToMap converts the MasterKey to a map for serialization purposes.
func (key *MasterKey) ToMap() map[string]interface{} {

View on GitHub (pinned to 13442bb981)