hoppscotch/hoppscotch · critical
failed to generate key pair: %w
Error message
failed to generate key pair: %w
What it means
`generateAndPersist` calls `ed25519.GenerateKey(rand.Reader)` to create a fresh signing key when no env var or on-disk key is found. If the crypto random source returns an error, the function wraps it: `failed to generate key pair: %w`. `rand.Reader` is Go's `crypto/rand` which reads from the OS CSPRNG (`/dev/urandom` on Linux, `RtlGenRandom` on Windows). Failure here means the operating system could not provide cryptographic randomness — an exceptional, system-level fault.
Source
Thrown at packages/hoppscotch-selfhost-web/webapp-server/internal/crypto/keys.go:124
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0700); err != nil {
return fmt.Errorf("failed to create key directory: %w", err)
}
encoded := base64.StdEncoding.EncodeToString(priv)
if err := os.WriteFile(path, []byte(encoded), 0600); err != nil {
return fmt.Errorf("failed to write key file: %w", err)
}
return nil
}
// generateAndPersist creates a new key and tries to save it.
// If we can't persist, we log the key so operators can set it manually.
func generateAndPersist(keyPath string) (*KeyPair, error) {
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate key pair: %w", err)
}
kp := &KeyPair{
SigningKey: priv,
VerifyingKey: pub,
}
if err := saveToFile(keyPath, priv); err == nil {
log.Printf("Generated and saved signing key to: %s", keyPath)
log.Printf("Verifying key: %s", base64.StdEncoding.EncodeToString(pub))
return kp, nil
}
// couldn't persist, log the key so it can be set via env var
// this is annoying but better than silent failures
keyB64 := base64.StdEncoding.EncodeToString(priv)
log.Println("========================================")View on GitHub (pinned to 1acb8a3a75)
Solutions
- Ensure `/dev/urandom` is available inside the container — run `head -c 32 /dev/urandom | xxd` from inside the pod; it must succeed.
- If using a restricted seccomp profile, allow the `getrandom` syscall (syscall number 318 on amd64) or mount `/dev/urandom` via a hostPath/device.
- Switch to providing the key explicitly via `WEBAPP_SERVER_SIGNING_KEY` or `WEBAPP_SERVER_SIGNING_SEED` so generation is never attempted at runtime.
- On Kubernetes, do not set `readOnlyRootFilesystem` in a way that masks `/dev`; or use `WEBAPP_SERVER_SIGNING_SECRET` to derive the key deterministically.
Example fix
# before — container masks /dev, GenerateKey fails docker run --read-only --security-opt seccomp=blockall webapp-server # → failed to generate key pair: ... # after — allow getrandom / mount urandom, or pass a key explicitly docker run -e WEBAPP_SERVER_SIGNING_SECRET=my-shared-secret webapp-server
Defensive patterns
Strategy: fallback
Validate before calling
// Provide a key via env var so GenerateKey is never called.
// In your Dockerfile / deployment:
// ENV WEBAPP_SERVER_SIGNING_SECRET=...
// or derive a stable seed:
// ENV WEBAPP_SERVER_SIGNING_SEED=<base64 of 32 bytes>
// Then in Go you can also assert the random source is usable:
func ensureEntropyAvailable() error {
f, err := os.Open("/dev/urandom")
if err != nil { return fmt.Errorf("no entropy source: %w", err) }
f.Close()
return nil
} Try / catch
// In main(), handle GenerateKeyPair failure by falling back to
// a deterministic secret (if acceptable for the deployment).
kp, err := crypto.GenerateKeyPair()
if err != nil {
log.Printf("key generation failed: %v; falling back to WEBAPP_SERVER_SIGNING_SECRET", err)
if secret := os.Getenv("WEBAPP_SERVER_SIGNING_SECRET"); secret != "" {
kp, err = crypto.GenerateKeyPair() // will pick up the secret
}
if err != nil {
log.Fatalf("cannot obtain signing key: %v", err)
}
} Prevention
- Always set one of WEBAPP_SERVER_SIGNING_KEY, _SEED, or _SECRET in production so generation is never attempted.
- Ensure the container has /dev/urandom mounted and the getrandom syscall allowed.
- Mount a persistent volume at /data/webapp-server so generated keys survive restarts.
- Monitor startup logs for 'SIGNING KEY PERSISTENCE FAILED' to catch silent generation issues.
When it happens
Trigger: The container or sandbox has no `/dev/urandom` mounted (e.g. a minimal chroot, a severely restricted seccomp profile, or a broken `devtmpfs`). File-descriptor exhaustion on Linux preventing the open of `/dev/urandom`. A custom `rand.Reader` was injected that returns an error. Extremely early in system boot before entropy is seeded on some embedded kernels.
Common situations: Running the webapp-server in a Docker container with `--security-opt no-new-privileges` plus an overly strict seccomp/AppArmor profile that blocks `/dev/urandom`; a scratch/distroless image missing the device node; a Kubernetes pod with a broken `readOnlyRootFilesystem` setup that masks `/dev`.
Related errors
- invalid WEBAPP_SERVER_SIGNING_KEY: %w
- WEBAPP_SERVER_SIGNING_KEY must be %d bytes, got %d
- invalid WEBAPP_SERVER_SIGNING_SEED: %w
- WEBAPP_SERVER_SIGNING_SEED must be %d bytes, got %d
- Method not allowed
AI-assisted analysis of hoppscotch/hoppscotch@1acb8a3a75 (2026-08-12).
Data as JSON: /api/errors/c6f295016b49a551.
Report an issue: GitHub.