xpipe-io/xpipe · error · BeaconClientException

Unable to parse or decrypt secret

Error message

Unable to parse or decrypt secret

What it means

SecretDecryptExchange deserializes the client-supplied encrypted secret via MultiPrincipalSecret.deserialize(). If deserialization yields null or the payload lacks an internal secret, the daemon cannot parse or decrypt the value and rejects the request. Typically the ciphertext was truncated, corrupted, encrypted for another daemon instance, or is not in the expected container format.

Source

Thrown at app/src/main/java/io/xpipe/app/beacon/api/SecretDecryptExchange.java:25

import com.sun.net.httpserver.HttpExchange;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import tools.jackson.databind.JsonNode;

public class SecretDecryptExchange extends BeaconInterface<SecretDecryptExchange.Request> {

    @Override
    public String getPath() {
        return "/secret/decrypt";
    }

    @Override
    public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException {
        var secret = MultiPrincipalSecret.deserialize(msg.getEncrypted());
        if (secret == null || secret.getInternalSecret() == null) {
            throw new BeaconClientException("Unable to parse or decrypt secret");
        }

        return Response.builder()
                .decrypted(new String(secret.getInternalSecret().getSecret()))
                .build();
    }

    @Jacksonized
    @Builder
    @Value
    public static class Request {
        @NonNull
        JsonNode encrypted;
    }

    @Jacksonized
    @Builder
    @Value

View on GitHub (pinned to d85ca821ba)

Solutions

  1. Re-obtain the encrypted secret from the original XPipe secret store rather than a copy
  2. Verify the daemon instance you call is the one that encrypted the secret (same key/vault)
  3. Confirm the value is sent as the full, unmodified base64 container (no truncation/whitespace)
  4. Upgrade client and daemon together if the serialization format changed

Example fix

// before
client.decryptSecret(userSuppliedString);
// after
if (userSuppliedString == null || userSuppliedString.isBlank()) {
    throw new IllegalArgumentException("encrypted secret required");
}
client.decryptSecret(userSuppliedString.trim());
Defensive patterns

Strategy: validation

Validate before calling

if (encrypted == null || encrypted.isBlank()) {
    throw new IllegalArgumentException("Encrypted secret payload is empty");
}

Try / catch

try {
    return client.decryptSecret(encrypted);
} catch (BeaconClientException e) {
    if (e.getMessage().contains("Unable to parse or decrypt")) {
        throw new IllegalStateException("Secret corrupt or encrypted by another daemon; re-encrypt it", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Posting an encrypted secret blob that is null, empty, base64-corrupted, produced by a different XPipe installation's key, or wrapped in an unexpected multi-principal structure so getInternalSecret() returns null.

Common situations: Copying the XPipe secrets vault/key between machines; hand-editing or truncating stored secret strings; passing a plaintext value where an encrypted container is expected; version mismatch between client serialization format and daemon parser.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of xpipe-io/xpipe@d85ca821ba (2026-09-06). Data as JSON: /api/errors/7ed707596be6ef7e. Report an issue: GitHub.