sipeed/picoclaw · error
credential: failed to generate nonce: %w
Error message
credential: failed to generate nonce: %w
What it means
Thrown by credential.Encrypt (pkg/credential/credential.go:204) when io.ReadFull cannot fill the 12-byte nonce buffer from crypto/rand.Reader before AES-256-GCM sealing. Every Encrypt call needs fresh randomness for salt and nonce; the library refuses to encrypt rather than proceed with a predictable nonce. The error wraps the underlying rand.Reader error with %w.
Source
Thrown at pkg/credential/credential.go:230
return "", fmt.Errorf("credential: failed to generate salt: %w", err)
}
key, err := deriveKey(passphrase, sshKeyPath, salt)
if err != nil {
return "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("credential: cipher init: %w", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("credential: gcm init: %w", err)
}
nonce := make([]byte, nonceLen)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", fmt.Errorf("credential: failed to generate nonce: %w", err)
}
ciphertext := gcm.Seal(nil, nonce, []byte(plaintext), nil)
blob := make([]byte, 0, saltLen+nonceLen+len(ciphertext))
blob = append(blob, salt...)
blob = append(blob, nonce...)
blob = append(blob, ciphertext...)
return EncScheme + base64.StdEncoding.EncodeToString(blob), nil
}
// isWithinDir reports whether path is contained within (or equal to) dir.
// Uses filepath.IsLocal on the relative path for robust cross-platform traversal detection.
func isWithinDir(path, dir string) bool {
rel, err := filepath.Rel(filepath.Clean(dir), filepath.Clean(path))
return err == nil && filepath.IsLocal(rel)
}
// allowedSSHKeyPath reports whether path is in a permitted location for SSH key files:View on GitHub (pinned to 49183d7e8d)
Solutions
- Confirm the entropy source inside the exact container/host: run `head -c 12 /dev/urandom > /dev/null && echo ok` and a small Go program that reads crypto/rand
- Update the container runtime / seccomp profile (or OCI default) to allow the getrandom syscall, or upgrade to a runtime that whitelists it
- Check fd exhaustion: `ulimit -n` and the process's open-fd count; raise the limit for the daemon
- Retry Encrypt after a short delay - early-boot entropy starvation is transient
- On kernels without getrandom, ensure a readable /dev/urandom device node exists in the container
Example fix
// before: single-shot call fails at boot-time entropy starvation
enc, err := credential.Encrypt(pass, keyPath, secret)
if err != nil {
return err
}
// after: retry only the randomness-dependent failure a few times
var enc string
err = retry(3, 500*time.Millisecond, func() error {
var e error
enc, e = credential.Encrypt(pass, keyPath, secret)
if e != nil && strings.Contains(e.Error(), "failed to generate nonce") {
return e // transient entropy starvation, retry
}
return retry.Stop(e)
}) Defensive patterns
Strategy: retry
Validate before calling
// Probe the OS entropy source before encrypting.
func entropyAvailable() error {
buf := make([]byte, 12)
if _, err := io.ReadFull(rand.Reader, buf); err != nil {
return fmt.Errorf("crypto/rand unavailable: %w", err)
}
return nil
} Type guard
func isNonceGenFailure(err error) bool {
return err != nil && strings.Contains(err.Error(), "failed to generate nonce")
} Try / catch
var enc string
err := Encrypt(pass, keyPath, secret)
for i := 0; isNonceGenFailure(err) && i < 3; i++ {
time.Sleep(500 * time.Millisecond) // early-boot entropy may recover
enc, err = Encrypt(pass, keyPath, secret)
}
if err != nil {
return fmt.Errorf("encryption aborted: %w", err) // never fall back to a weaker scheme
} Prevention
- Keep host/container runtimes current so getrandom(2) is seccomp-whitelisted
- Do not strip /dev/urandom from container device sets
- Monitor host entropy availability (`cat /proc/sys/kernel/random/entropy_avail`) on virtualized fleets
- Never retry with a cached nonce or fall back to a deterministic nonce on this error
When it happens
Trigger: Calling credential.Encrypt(passphrase, sshKeyPath, plaintext) on a host where the OS entropy source fails: Linux getrandom(2) blocked by a container seccomp/AppArmor profile, a pre-3.17 kernel with /dev/urandom unavailable, entropy not yet initialized in very early boot, or fd exhaustion when the runtime falls back to opening /dev/urandom. Note the salt read (line 211) already succeeded, so this is specifically the second randomness read failing.
Common situations: Hardened Docker/gVisor containers whose seccomp profile predates getrandom(2) whitelisting; minimal VMs booted with hardware RNG unavailable; CI sandboxes stripping /dev randomness devices; extremely old kernels or Plan 9 with an unreadable /dev/random.
Related errors
- credential: keygen: ed25519 key generation failed: %w
- seahorse assemble: %w
- generating PKCE: %w
- generating state: %w
- execute %s: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/354a8ec9c6330b6d.
Report an issue: GitHub.