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

  1. Ensure `/dev/urandom` is available inside the container — run `head -c 32 /dev/urandom | xxd` from inside the pod; it must succeed.
  2. If using a restricted seccomp profile, allow the `getrandom` syscall (syscall number 318 on amd64) or mount `/dev/urandom` via a hostPath/device.
  3. Switch to providing the key explicitly via `WEBAPP_SERVER_SIGNING_KEY` or `WEBAPP_SERVER_SIGNING_SEED` so generation is never attempted at runtime.
  4. 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

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


AI-assisted analysis of hoppscotch/hoppscotch@1acb8a3a75 (2026-08-12). Data as JSON: /api/errors/c6f295016b49a551. Report an issue: GitHub.