jdx/mise · error

no age recipients to encrypt for

Error message

no age recipients to encrypt for

What it means

encrypt_bytes compresses plaintext and encrypts it for a supplied list of age recipients. An empty recipient list would produce an envelope nobody can decrypt, so the function rejects it up front with this error.

Source

Thrown at src/agecrypt.rs:154

pub(crate) fn read_bounded(reader: impl Read, limit: u64) -> std::io::Result<Vec<u8>> {
    let mut bytes = Vec::new();
    reader.take(limit + 1).read_to_end(&mut bytes)?;
    if bytes.len() as u64 > limit {
        return Err(std::io::Error::other(
            "encrypted content exceeds the size limit",
        ));
    }
    Ok(bytes)
}

/// zstd-compressed, then age-encrypted for `recipients`.
pub(crate) fn encrypt_bytes(
    plaintext: &[u8],
    recipients: &[Box<dyn Recipient + Send>],
) -> Result<Vec<u8>> {
    if recipients.is_empty() {
        bail!("no age recipients to encrypt for");
    }
    if plaintext.len() as u64 > MAX_PLAINTEXT_BYTES {
        bail!("plaintext exceeds the size limit");
    }
    let compressed = zstd::encode_all(plaintext, ZSTD_COMPRESSION_LEVEL)?;
    if compressed.len() as u64 > MAX_ENCRYPTED_BYTES {
        bail!("compressed payload exceeds the size limit");
    }
    let encryptor =
        Encryptor::with_recipients(recipients.iter().map(|r| r.as_ref() as &dyn Recipient))
            .map_err(|e| eyre!("creating the age encryptor: {e}"))?;
    let mut out = Vec::new();
    let mut writer = encryptor.wrap_output(&mut out)?;
    writer.write_all(&compressed)?;
    writer.finish()?;
    if out.len() as u64 > MAX_ENCRYPTED_BYTES {
        bail!("encrypted payload exceeds the size limit");
    }

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Populate the recipients list with at least one age recipient before calling encrypt_bytes.
  2. Check that age identity/recipient configuration is present and parses (e.g. ssh-ed25519 or age1... recipients).
  3. Verify the plugin registration/recovery path actually supplies its software-recovery recipient.
  4. In tests, add a generated recipient (as the passing tests do) before encrypting.

Example fix

// before
let recipients: Vec<Box<dyn Recipient + Send>> = vec![];
encrypt_bytes(&plaintext, &recipients)?;
// after
let recipients: Vec<Box<dyn Recipient + Send>> = vec![Box::new(recipient)];
assert!(!recipients.is_empty());
encrypt_bytes(&plaintext, &recipients)?;
Defensive patterns

Strategy: validation

Validate before calling

if recipients.is_empty() {
    return Err(anyhow!("no age recipients configured; check MISE age recipient settings"));
}

Type guard

fn has_recipients(recipients: &[Box<dyn Recipient + Send>]) -> bool { !recipients.is_empty() }

Try / catch

match encrypt_bytes(&plaintext, &recipients) { Err(e) if e.to_string().contains("no age recipients") => { load_recipients_from_config()?; }, Err(e) => return Err(e), Ok(ciphertext) => ciphertext }

Prevention

When it happens

Trigger: Calling encrypt_bytes (directly or via plugin protocol encryption / software recovery paths) with an empty recipients slice — e.g. no identities loaded from config, key files missing, or filtering removed all recipients.

Common situations: Missing or unreadable age key files (MISE_AGE_RECIPIENTS / key config); plugin recovery paths where the recipient was never registered; tests constructing encryption without adding a recipient.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/48abebd22c10cf8c. Report an issue: GitHub.