xpipe-io/xpipe · error · IllegalArgumentException
No secret available to encrypt
Error message
No secret available to encrypt
What it means
In with(), when a principal already has an existing entry but the secret or principal changed (keep == false), a new ciphertext must be produced. If the caller passed a null secret in that situation, there is nothing to encrypt and an IllegalArgumentException is thrown. Passing the same secret and same principal (keep == true) avoids the throw.
Source
Thrown at app/src/main/java/io/xpipe/app/secret/MultiPrincipalSecret.java:209
for (EncryptionPrincipal principal : scope.getPrincipals()) {
var existingEntry = entries.stream()
.filter(entry -> entry.getPrincipal().equals(principal))
.findFirst();
// Keep existing entry if possible if not accessible
if (!principal.isAccessible()) {
if (existingEntry.isPresent()) {
l.add(existingEntry.get());
}
continue;
}
if (existingEntry.isPresent()) {
var principalUnchanged = existingEntry.get().getToken().matches(principal);
var keep = secretUnchanged && principalUnchanged;
if (!keep && secret == null) {
throw new IllegalArgumentException("No secret available to encrypt");
}
l.add(new Entry(
principal,
keep
? existingEntry.get().getEncrypted()
: AesSecretValue.encrypt(secret.getSecret(), principal.getSecretKey())
.getEncryptedValue(),
keep ? iteration : iteration + 1,
keep ? existingEntry.get().getToken() : EncryptionToken.of(principal)));
} else {
if (secret == null) {
throw new IllegalArgumentException("No secret available to encrypt");
}
var enc = AesSecretValue.encrypt(secret.getSecret(), principal.getSecretKey());
l.add(new Entry(principal, enc.getEncryptedValue(), iteration + 1, EncryptionToken.of(principal)));
}View on GitHub (pinned to d85ca821ba)
Solutions
- Pass the actual secret value when principals change
- If you intend to clear the secret, also remove the corresponding principal from the set
- Reuse the existing MultiPrincipalSecret instance when secret is unchanged (it returns this)
Example fix
// before secret.with(null, scope); // principals changed -> throw // after secret.with(currentSecretValue, scope);
Defensive patterns
Strategy: validation
Validate before calling
if (secret == null && principalsChanged) {
throw new IllegalArgumentException("Provide a secret when principals change");
} Try / catch
try { return secret.with(s, scope); } catch (IllegalArgumentException e) { if (e.getMessage().equals("No secret available to encrypt")) { /* supply a secret value or skip update */ } else throw e; } Prevention
- Never pass null secrets into principal-update flows
- Model 'clear secret' as removing principals instead
- Keep secret and principal updates in one consistent call
When it happens
Trigger: Calling with(null, scope) / withUpdatedPrincipals with a null secret while the new principal differs from the existing entry's token, so keep == false and encryption is required.
Common situations: Clearing a secret by passing null but also changing the principal set; building update flows that pass null for 'unchanged' while principals were rotated.
Related errors
- Principal ${name} is not accessible
- Scope ${scope} is not supported
- Secret is not accessible
- Unable to parse or decrypt secret
- Unknown principal " + pr
AI-assisted analysis of xpipe-io/xpipe@d85ca821ba (2026-09-06).
Data as JSON: /api/errors/a171ec79dda5484d.
Report an issue: GitHub.