zeroclaw-labs/zeroclaw · error · anyhow::Error

auth_secret '{secret_name}' is empty after decryption

Error message

auth_secret '{secret_name}' is empty after decryption

What it means

Thrown by HttpRequestTool::reload_auth_secret (crates/zeroclaw-tools/src/http_request.rs:320) when a secret entry in [http_request.secrets] is recognized as encrypted (zeroclaw_config::secrets::SecretStore format), decrypts successfully, but yields an empty string. Decryption worked; the stored plaintext itself is empty. The tool refuses to send an empty Authorization header, because that would silently break auth.

Source

Thrown at crates/zeroclaw-tools/src/http_request.rs:320

                "Failed to parse config file {} for auth_secret '{secret_name}': {e}",
                config_path.display()
            ))
        })?;

        let raw_secret = config
            .http_request
            .secrets
            .get(secret_name)
            .filter(|secret| !secret.is_empty())
            .ok_or_else(|| anyhow::Error::msg(format!("auth_secret '{secret_name}' not found")))?;

        let secret = if zeroclaw_config::secrets::SecretStore::is_encrypted(raw_secret) {
            let zeroclaw_dir = config_path.parent().unwrap_or_else(|| Path::new("."));
            let store =
                zeroclaw_config::secrets::SecretStore::new(zeroclaw_dir, self.secrets_encrypt);
            let plaintext = store.decrypt(raw_secret)?;
            if plaintext.is_empty() {
                anyhow::bail!("auth_secret '{secret_name}' is empty after decryption");
            }
            plaintext
        } else {
            raw_secret.clone()
        };

        if let Some(env_secret) = resolve_env_backed_auth_secret(secret_name, &secret)? {
            Ok(env_secret)
        } else {
            Ok(secret)
        }
    }

    fn apply_auth_secret(
        &self,
        headers: &mut HeaderMap,
        auth_secret: Option<&str>,
    ) -> anyhow::Result<()> {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Re-encrypt the secret with a non-empty plaintext using zeroclaw's SecretStore and update the config.toml entry.
  2. Verify end-to-end: decrypt the stored value once after encryption and assert it is non-empty.
  3. If the secret is intentionally env-backed, encrypt "${VAR_NAME}" (the reference string) rather than the current env contents.

Example fix

# before
api_token = "<encrypted empty string>"  # decrypts to ""

# after
# re-run: zeroclaw secret encrypt with the real value
api_token = "<encrypted 'Bearer real-token'>"
Defensive patterns

Strategy: validation

Validate before calling

// at startup, verify every configured secret decrypts to a non-empty value
for (name, raw) in &config.http_request.secrets {
    if SecretStore::is_encrypted(raw) {
        let plain = store.decrypt(raw)?;
        anyhow::ensure!(!plain.is_empty(), "secret '{name}' decrypts to empty");
    }
}

Try / catch

let result = tool.execute(args).await?;
if let Some(err) = &result.error {
    if err.contains("is empty after decryption") {
        // re-encrypt the secret with its real value, then retry
    }
}

Prevention

When it happens

Trigger: Running the secret-encrypt workflow while the source value was empty (e.g. exported an unset env var before encrypting); encrypting "" deliberately as a placeholder; a secret rotation script that encrypted an empty replacement; secrets_encrypt flag mismatches usually fail decryption with a different error, so this specifically means empty plaintext.

Common situations: Bootstrap scripts that encrypt ${VAR} when VAR was unset in that shell; CI pipelines creating placeholder secrets for new environments; copy-pasted encrypt commands run before the value was pasted in.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/e8932b943b4d865b. Report an issue: GitHub.