JuliusBrussee/caveman · critical

nonce entropy: %w

Error message

nonce entropy: %w

What it means

crypto/rand.Read failed while generating the 12-byte AES-GCM nonce in local-mode Encrypt. The kernel's CSPRNG essentially never fails on Linux/macOS in normal operation; documented failure modes are file-descriptor exhaustion (getrandom(2)/dev/urandom open fails), early boot before the entropy pool is ready on minimal VMs, or broken sandbox/seccomp profiles blocking the syscall. Since a repeated or predictable nonce destroys GCM security, the operation aborts.

Source

Thrown at shared/platform/secretbox/secretbox.go:80

	}
	if runtimeenv.IsProduction() {
		return nil, fmt.Errorf("secretbox: production requires CAVE_KMS_PROVIDER=scaleway")
	}
	keyBytes, err := loadKey()
	if err != nil {
		return nil, err
	}
	block, err := aes.NewCipher(keyBytes)
	if err != nil {
		return nil, fmt.Errorf("aes cipher: %w", err)
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("aes-gcm: %w", err)
	}
	nonce := make([]byte, gcm.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		return nil, fmt.Errorf("nonce entropy: %w", err)
	}
	// Seal appends the ciphertext+tag to nonce, so the returned slice is the
	// full nonce||ciphertext envelope.
	return gcm.Seal(nonce, nonce, plaintext, nil), nil
}

// EncryptPayloadKey wraps an artifact data-encryption key. Production uses the
// dedicated payload KEK; local development retains the same AES-GCM envelope as
// other local secrets.
func EncryptPayloadKey(plaintext []byte) ([]byte, error) {
	if useKMS() {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		wrapped, err := kms.EncryptPayload(ctx, plaintext)
		if err != nil {
			return nil, fmt.Errorf("secretbox: payload KMS encrypt: %w", err)
		}
		return wrapped, nil

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Check the wrapped error (errno) — EMFILE means fd exhaustion: fix the leak or raise the limit; EPERM means the sandbox blocks the syscall: allow getrandom(2)/open of /dev/urandom.
  2. On minimal VMs, ensure the boot path seeds entropy (haveged, virtio-rng, or waiting for the crng ready flag) before the service starts.
  3. This is environmental, not a code bug — retrying without fixing the environment repeats the failure.

Example fix

# before: container blocks getrandom -> "nonce entropy: operation not permitted"
docker run --security-opt seccomp=broken.json mysvc

# after: default profile (or one permitting getrandom) 
docker run mysvc
Defensive patterns

Strategy: fallback

Validate before calling

// Cheap environmental preflight: confirm the CSPRNG answers before serving.
func entropyAvailable() bool {
    b := make([]byte, 1)
    _, err := rand.Read(b)
    return err == nil
}

Try / catch

if _, err := secretbox.Encrypt(pt); err != nil {
    if strings.Contains(err.Error(), "nonce entropy") {
        // do NOT retry blindly: diagnose fd limits / seccomp / boot entropy; fail closed
    }
}

Prevention

When it happens

Trigger: Local Encrypt under a container/sandbox whose seccomp or device-cgroup policy blocks getrandom or /dev/urandom; a very early-started process on an embedded/minimal kernel with an unseeded CRNG; ulimit -n exhausted so the random device cannot be opened.

Common situations: Custom Docker/seccomp profiles, gVisor/Firecracker microVMs at boot, sidecar-less initrd environments, or a malicious/accidental fd leak filling the process's descriptor table.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/9dc511a3e3bed169. Report an issue: GitHub.