Affected versions
Regression introduced in 4.0.0. Confirmed still present in 4.1.0.
Working in 3.2.0.
Description
SecretLeaseCreatedEvent copies the secret data with Map.copyOf(secrets),
which rejects null values with a NullPointerException:
https://github.com/spring-projects/spring-vault/blob/main/spring-vault-core/src/main/java/org/springframework/vault/core/lease/event/SecretLeaseCreatedEvent.java
public SecretLeaseCreatedEvent(RequestedSecret requestedSecret, Lease lease, Map<String, Object> secrets) {
super(requestedSecret, lease);
this.secrets = Map.copyOf(secrets); // NPE if any value is null
}
3.2.0 used a null-tolerant copy:
this.secrets = Collections.unmodifiableMap(new LinkedHashMap<>(secrets));
SecretLeaseRotatedEvent extends SecretLeaseCreatedEvent and delegates to
this constructor, so the rotation path fails identically.
Reproducer
No Vault instance required — the constructor alone reproduces it:
Map<String, Object> secrets = new LinkedHashMap<>();
secrets.put("access_key", "SOME_KEY");
secrets.put("secret_key", "SOME_VALUE);
secrets.put("session_token", null);
// throws NullPointerException on 4.0.0+, succeeds on 3.2.0
new SecretLeaseCreatedEvent(RequestedSecret.renewable("aws/creds/my-role"), Lease.none(), secrets);
Real-world trigger
Vault's AWS secrets engine returns a null session token for the iam_user
credential type, because no STS call is involved:
{
"data": {
"access_key": "AKIA...",
"secret_key": "...",
"session_token": null
}
}
How it surfaces
Via Spring Cloud Vault (spring-cloud-vault-config 5.0.2), with
spring.cloud.vault.aws.enabled=true:
SecretLeaseContainer.doGetSecrets // reads aws/creds/<role>
SecretLeaseContainer.start // -> onSecretsObtained(...)
SecretLeaseEventPublisher.onSecretsObtained // -> dispatch(new SecretLeaseCreatedEvent(...))
SecretLeaseCreatedEvent.<init> // NullPointerException
doGetSecrets has a try/catch (RuntimeException) that routes failures to
onError, but it wraps only the HTTP read — onSecretsObtained is invoked
afterwards, outside it. The NPE therefore escapes unwrapped rather than
reaching any LeaseErrorListener, so the application fails to start with a
bare NullPointerException and no indication of which secret or key caused it.
Expected behaviour
Either:
- Restore the null-tolerant copy from 3.2.0, or
- Drop null-valued entries before copying, or
- At minimum, route the failure through
onError so it surfaces as a
VaultException naming the offending path.
Affected versions
Regression introduced in 4.0.0. Confirmed still present in 4.1.0.
Working in 3.2.0.
Description
SecretLeaseCreatedEventcopies the secret data withMap.copyOf(secrets),which rejects null values with a
NullPointerException:https://github.com/spring-projects/spring-vault/blob/main/spring-vault-core/src/main/java/org/springframework/vault/core/lease/event/SecretLeaseCreatedEvent.java
3.2.0 used a null-tolerant copy:
SecretLeaseRotatedEventextendsSecretLeaseCreatedEventand delegates tothis constructor, so the rotation path fails identically.
Reproducer
No Vault instance required — the constructor alone reproduces it:
Real-world trigger
Vault's AWS secrets engine returns a null session token for the
iam_usercredential type, because no STS call is involved:
{ "data": { "access_key": "AKIA...", "secret_key": "...", "session_token": null } }How it surfaces
Via Spring Cloud Vault (
spring-cloud-vault-config5.0.2), withspring.cloud.vault.aws.enabled=true:doGetSecretshas atry/catch (RuntimeException)that routes failures toonError, but it wraps only the HTTP read —onSecretsObtainedis invokedafterwards, outside it. The NPE therefore escapes unwrapped rather than
reaching any
LeaseErrorListener, so the application fails to start with abare
NullPointerExceptionand no indication of which secret or key caused it.Expected behaviour
Either:
onErrorso it surfaces as aVaultExceptionnaming the offending path.