neondatabase/neon · error
Configured for JWT auth with zero decoding keys. All JWT gat
Error message
Configured for JWT auth with zero decoding keys. All JWT gated requests would be rejected.
What it means
from_key_path accepts a directory to support key rotation, but if that directory contains no loadable regular files the constructor bails. With zero decoding keys every JWT-gated request would be rejected, so the library fails fast at startup instead of running in an always-deny state.
Source
Thrown at libs/utils/src/auth.rs:171
let mut keys = Vec::new();
for entry in fs::read_dir(key_path)? {
let path = entry?.path();
if !path.is_file() {
// Ignore directories (don't recurse)
continue;
}
let public_key = fs::read(path)?;
keys.push(DecodingKey::from_ed_pem(&public_key)?);
}
keys
} else if metadata.is_file() {
let public_key = fs::read(key_path)?;
vec![DecodingKey::from_ed_pem(&public_key)?]
} else {
anyhow::bail!("path is neither a directory or a file")
};
if decoding_keys.is_empty() {
anyhow::bail!(
"Configured for JWT auth with zero decoding keys. All JWT gated requests would be rejected."
);
}
Ok(Self::new(decoding_keys))
}
pub fn from_key(key: String) -> Result<Self> {
Ok(Self::new(vec![DecodingKey::from_ed_pem(key.as_bytes())?]))
}
/// Attempt to decode the token with the internal decoding keys.
///
/// The function tries the stored decoding keys in succession,
/// and returns the first yielding a successful result.
/// If there is no working decoding key, it returns the last error.
pub fn decode<D: DeserializeOwned>(
&self,
token: &str,View on GitHub (pinned to 8f60b04da4)
Solutions
- Ensure at least one Ed25519 public key PEM exists in the directory
- Check the deployment/mount: kubectl describe pod, verify secret name and mountPath
- Add a provisioning step that writes keys before the service starts
Example fix
# before: secret mounted but empty
volumes:
- name: jwt-keys
secret:
secretName: jwt-keys-wrong-name
# after
kubectl create secret generic jwt-keys --from-file=public.pem=./public.pem
# files land at /etc/neon/keys/public.pem Defensive patterns
Strategy: validation
Validate before calling
fn key_dir_has_pem(dir: &camino::Utf8Path) -> bool {
std::fs::read_dir(dir)
.map(|entries| entries.filter_map(Result::ok).any(|e| e.path().is_file()))
.unwrap_or(false)
}
// run before JwtAuth::from_key_path
anyhow::ensure!(key_dir_has_pem(&key_path), "JWT key directory is empty"); Type guard
fn is_zero_keys_error(err: &anyhow::Error) -> bool {
err.to_string().contains("zero decoding keys")
} Try / catch
match JwtAuth::from_key_path(&key_path) {
Err(e) if e.to_string().contains("zero decoding keys") => {
eprintln!("key directory {key_path} contains no PEM files; provision keys first");
std::process::exit(1);
}
other => other?,
} Prevention
- Verify secret mounts are non-empty before service start (initContainer or entrypoint check)
- Make key provisioning an explicit deployment step
- Alert on empty key directories during rotation windows
When it happens
Trigger: The configured JWT key directory exists but is empty: a Kubernetes secret volume that was never populated, a failed/renamed secret mount, or an automation step that skipped key provisioning.
Common situations: k8s secret mounted empty due to name mismatch or missing items; key rotation removed old files before new ones landed; fresh environments missing the provisioning step.
Related errors
- Safekeeper set up for auth but no private key specified
- path is neither a directory or a file
- could not parse config file: {}
- could not open config file at path: {}
- failed to verify authorization token
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/b25f442b11b03f02.
Report an issue: GitHub.