{"record":{"id":"6a5c0e2fcacdbf1a","repo":"astrid-runtime/astrid","slug":"os-csprng-unavailable-while-generating-invite-suff","errorCode":null,"errorMessage":"OS CSPRNG unavailable while generating invite suffix","messagePattern":"OS CSPRNG unavailable while generating invite suffix","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/astrid-kernel/src/kernel_router/admin/invite_handlers.rs","lineNumber":481,"sourceCode":"            out.push(ch.to_ascii_lowercase());\n            last_was_dash = false;\n        } else if !last_was_dash && !out.is_empty() {\n            out.push('-');\n            last_was_dash = true;\n        }\n    }\n    while out.ends_with('-') {\n        out.pop();\n    }\n    out\n}\n\nfn random_suffix() -> String {\n    use rand::{TryRng, rngs::SysRng};\n    let mut bytes = [0u8; 4];\n    SysRng\n        .try_fill_bytes(&mut bytes)\n        .expect(\"OS CSPRNG unavailable while generating invite suffix\");\n    hex::encode(bytes)\n}\n\nfn err_bad_input(msg: String) -> AdminResponseBody {\n    warn!(error = %msg, \"invite request rejected: bad input\");\n    AdminResponseBody::Error(msg)\n}\n\nfn err_internal(msg: String) -> AdminResponseBody {\n    warn!(error = %msg, \"invite request failed: internal error\");\n    AdminResponseBody::Error(msg)\n}\n\nfn err_unauthorized(msg: String) -> AdminResponseBody {\n    warn!(security_event = true, error = %msg, \"invite request denied\");\n    AdminResponseBody::Error(msg)\n}\n","sourceCodeStart":463,"sourceCodeEnd":499,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/kernel_router/admin/invite_handlers.rs#L463-L499","documentation":"Panic from `SysRng.try_fill_bytes(&mut bytes).expect(\"OS CSPRNG unavailable while generating invite suffix\")` in `random_suffix` (crates/astrid-kernel/src/kernel_router/admin/invite_handlers.rs:481). It draws 4 random bytes for a hex invite suffix from the OS CSPRNG and deliberately panics if that source is unavailable, refusing insecure fallback.","triggerScenarios":"Any admin invite-handler request that calls `random_suffix()` while the OS entropy source fails: seccomp-filtered getrandom, missing /dev/urandom, sandboxed processes, or pre-entropy-early-boot environments.","commonSituations":"Same environmental causes as token generation — hardened containers, restricted service sandboxes (systemd RestrictNamespaces/seccomp profiles), minimal VM images without entropy daemons.","solutions":["Fix the environment: allow getrandom(2) in the sandbox profile or mount /dev/urandom in the container.","Start an entropy maintenance service (rngd, haveged) if running on hardware/VMs with slow entropy init.","Have random_suffix return Result and map failure to an admin error response instead of panicking the handler thread.","Check kernel logs (dmesg) for crng init failures to confirm the diagnosis."],"exampleFix":"// before\nSysRng.try_fill_bytes(&mut bytes).expect(\"OS CSPRNG unavailable while generating invite suffix\");\n// after\nlet suffix = SysRng.try_fill_bytes(&mut bytes)\n    .ok()\n    .map(|_| hex::encode(bytes))\n    .ok_or_else(|| AdminError::RngUnavailable)?;","handlingStrategy":"try-catch","validationCode":"use rand::{TryRng, rngs::SysRng};\nfn rng_available() -> bool {\n    let mut b = [0u8; 1];\n    SysRng.try_fill_bytes(&mut b).is_ok()\n}","typeGuard":null,"tryCatchPattern":"let suffix = std::panic::catch_unwind(random_suffix)\n    .map_err(|_| AdminError::RngUnavailable)?; // return err_bad_input-style error instead of panicking","preventionTips":["Check sandbox/seccomp profiles allow getrandom(2) before deploying invite handlers.","Probe the CSPRNG once at admin-router startup.","Convert random_suffix to return Result so handler threads degrade gracefully.","Monitor dmesg/journald for entropy-init failures in production."],"tags":["random","security","entropy","panic","admin-api"],"backgroundTag":"missing-entropy-source","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"}