{"record":{"id":"f3dcebda227d9656","repo":"astrid-runtime/astrid","slug":"os-csprng-unavailable-while-generating-gateway-sig","errorCode":null,"errorMessage":"OS CSPRNG unavailable while generating gateway signing key","messagePattern":"OS CSPRNG unavailable while generating gateway signing key","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/astrid-gateway/src/state.rs","lineNumber":56,"sourceCode":"    pub signer: SigningKey,\n    /// Verifies incoming bearer tokens. Same key — kept separately\n    /// so middleware can hold a cheap `Copy` of the public half.\n    pub verifier: VerifyingKey,\n}\n\nimpl SigningMaterial {\n    /// Generate a fresh signing keypair from the OS CSPRNG. Used by\n    /// tests and by the load path when the on-disk key is missing.\n    ///\n    /// # Panics\n    ///\n    /// Panics if the OS CSPRNG is unavailable.\n    #[must_use]\n    pub fn fresh() -> Self {\n        let mut secret = [0u8; 32];\n        SysRng\n            .try_fill_bytes(&mut secret)\n            .expect(\"OS CSPRNG unavailable while generating gateway signing key\");\n        let signer = SigningKey::from_bytes(&secret);\n        let verifier = signer.verifying_key();\n        Self { signer, verifier }\n    }\n\n    /// Load the persisted gateway signing key, generating it on\n    /// first boot. Matches the kernel's `runtime.ed25519` load\n    /// pattern: 0600 perms, atomic write-then-rename. Same path\n    /// layout convention (`keys/` under `$ASTRID_HOME`).\n    ///\n    /// # Errors\n    /// Returns an error if the keys directory can't be created,\n    /// the on-disk key is corrupt (wrong length), or the file\n    /// write fails.\n    pub fn load_or_generate() -> anyhow::Result<Self> {\n        use anyhow::Context as _;\n        let home = astrid_core::dirs::AstridHome::resolve()\n            .context(\"resolve $ASTRID_HOME for gateway signing key\")?;","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/state.rs#L38-L74","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (blocks startup under seccomp)\nlet state = SignState::fresh();\n// after: surface the entropy failure instead of panicking deep in construction\nlet secret = SysRng\n    .try_fill_bytes(&mut buf)\n    .map_err(|e| StartupError::EntropyUnavailable(e.to_string()))?;\nlet state = SignState::from_secret(secret);","handlingStrategy":"fallback","validationCode":"// Rust: probe the OS RNG before constructing signing state\nfn entropy_available() -> bool {\n    let mut b = [0u8; 1];\n    SysRng.try_fill_bytes(&mut b).is_ok()\n}","typeGuard":null,"tryCatchPattern":"// Rust: catch at startup boundary, not inside fresh()\nmatch SignState::try_fresh() {\n    Ok(state) => state,\n    Err(e) => { log::error(\"CSPRNG unavailable: {e}\"); std::process::exit(1); }\n}","preventionTips":["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."],"tags":["rust","cryptography","entropy","panic","startup"],"backgroundTag":"missing-env-var","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}