astrid-runtime/astrid · critical
OS CSPRNG unavailable while generating gateway signing key
Error message
OS CSPRNG unavailable while generating gateway signing key
What it means
`State::fresh()` generates a 32-byte gateway signing key using the OS CSPRNG (`SysRng.try_fill_bytes`). It panics with this message if the OS random source cannot fill the buffer, because an unpredictable key is mandatory for signing — proceeding would be insecure. This is a fail-fast panic, not a recoverable error.
Solutions
- Fix the host so the OS CSPRNG works (allow the `getrandom` syscall in the container/sandbox profile).
- Retry process startup once entropy is available (the condition is usually transient at boot).
- If the platform genuinely lacks a CSPRNG, switch the RNG provider to one backed by a hardware RNG and re-audit.
- Never replace this with a deterministic/fallback RNG — the panic is intentional.
Example fix
// before (blocks startup under seccomp)
let state = SignState::fresh();
// after: surface the entropy failure instead of panicking deep in construction
let secret = SysRng
.try_fill_bytes(&mut buf)
.map_err(|e| StartupError::EntropyUnavailable(e.to_string()))?;
let state = SignState::from_secret(secret); Defensive patterns
Strategy: fallback
Validate before calling
// Rust: probe the OS RNG before constructing signing state
fn entropy_available() -> bool {
let mut b = [0u8; 1];
SysRng.try_fill_bytes(&mut b).is_ok()
} Try / catch
// Rust: catch at startup boundary, not inside fresh()
match SignState::try_fresh() {
Ok(state) => state,
Err(e) => { log::error("CSPRNG unavailable: {e}"); std::process::exit(1); }
} Prevention
- Add a startup health check that verifies getrandom works before spawning services.
- In containers, explicitly allow the getrandom syscall.
- Never substitute a non-CSPRNG fallback for signing keys.
- Monitor entropy-related kernel logs on embedded/VM hosts.
When it happens
Trigger: Calling `SignState::fresh()` (at process startup or key generation) when `getrandom`/OS entropy fails: e.g. exhausted `/dev/urandom`, restricted seccomp/sandbox blocking `getrandom(2)`, or a broken RDRAND on some VMs.
Common situations: Running the gateway inside a container/sandbox that blocks the `getrandom` syscall, heavily contended entropy at early boot on embedded/VM hosts, or unusual hardened kernels restricting randomness syscalls.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- alice
- device revocation map poisoned during startup hydration
- empty page deserializes
- failed to spawn hook epoch ticker
- OS CSPRNG unavailable while generating default keypair name
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/f3dcebda227d9656.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-gateway/src/state.rs:56
pub signer: SigningKey,
/// Verifies incoming bearer tokens. Same key — kept separately
/// so middleware can hold a cheap `Copy` of the public half.
pub verifier: VerifyingKey,
}
impl SigningMaterial {
/// Generate a fresh signing keypair from the OS CSPRNG. Used by
/// tests and by the load path when the on-disk key is missing.
///
/// # Panics
///
/// Panics if the OS CSPRNG is unavailable.
#[must_use]
pub fn fresh() -> Self {
let mut secret = [0u8; 32];
SysRng
.try_fill_bytes(&mut secret)
.expect("OS CSPRNG unavailable while generating gateway signing key");
let signer = SigningKey::from_bytes(&secret);
let verifier = signer.verifying_key();
Self { signer, verifier }
}
/// Load the persisted gateway signing key, generating it on
/// first boot. Matches the kernel's `runtime.ed25519` load
/// pattern: 0600 perms, atomic write-then-rename. Same path
/// layout convention (`keys/` under `$ASTRID_HOME`).
///
/// # Errors
/// Returns an error if the keys directory can't be created,
/// the on-disk key is corrupt (wrong length), or the file
/// write fails.
pub fn load_or_generate() -> anyhow::Result<Self> {
use anyhow::Context as _;
let home = astrid_core::dirs::AstridHome::resolve()
.context("resolve $ASTRID_HOME for gateway signing key")?;View on GitHub (pinned to affd8760f4)