{"record":{"id":"354a8ec9c6330b6d","repo":"sipeed/picoclaw","slug":"credential-failed-to-generate-nonce-w","errorCode":null,"errorMessage":"credential: failed to generate nonce: %w","messagePattern":"credential: failed to generate nonce: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/credential/credential.go","lineNumber":230,"sourceCode":"\t\treturn \"\", fmt.Errorf(\"credential: failed to generate salt: %w\", err)\n\t}\n\n\tkey, err := deriveKey(passphrase, sshKeyPath, salt)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"credential: cipher init: %w\", err)\n\t}\n\tgcm, err := cipher.NewGCM(block)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"credential: gcm init: %w\", err)\n\t}\n\n\tnonce := make([]byte, nonceLen)\n\tif _, err := io.ReadFull(rand.Reader, nonce); err != nil {\n\t\treturn \"\", fmt.Errorf(\"credential: failed to generate nonce: %w\", err)\n\t}\n\n\tciphertext := gcm.Seal(nil, nonce, []byte(plaintext), nil)\n\tblob := make([]byte, 0, saltLen+nonceLen+len(ciphertext))\n\tblob = append(blob, salt...)\n\tblob = append(blob, nonce...)\n\tblob = append(blob, ciphertext...)\n\treturn EncScheme + base64.StdEncoding.EncodeToString(blob), nil\n}\n\n// isWithinDir reports whether path is contained within (or equal to) dir.\n// Uses filepath.IsLocal on the relative path for robust cross-platform traversal detection.\nfunc isWithinDir(path, dir string) bool {\n\trel, err := filepath.Rel(filepath.Clean(dir), filepath.Clean(path))\n\treturn err == nil && filepath.IsLocal(rel)\n}\n\n// allowedSSHKeyPath reports whether path is in a permitted location for SSH key files:","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/credential/credential.go#L212-L248","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: single-shot call fails at boot-time entropy starvation\nenc, err := credential.Encrypt(pass, keyPath, secret)\nif err != nil {\n    return err\n}\n\n// after: retry only the randomness-dependent failure a few times\nvar enc string\nerr = retry(3, 500*time.Millisecond, func() error {\n    var e error\n    enc, e = credential.Encrypt(pass, keyPath, secret)\n    if e != nil && strings.Contains(e.Error(), \"failed to generate nonce\") {\n        return e // transient entropy starvation, retry\n    }\n    return retry.Stop(e)\n})","handlingStrategy":"retry","validationCode":"// Probe the OS entropy source before encrypting.\nfunc entropyAvailable() error {\n    buf := make([]byte, 12)\n    if _, err := io.ReadFull(rand.Reader, buf); err != nil {\n        return fmt.Errorf(\"crypto/rand unavailable: %w\", err)\n    }\n    return nil\n}","typeGuard":"func isNonceGenFailure(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"failed to generate nonce\")\n}","tryCatchPattern":"var enc string\nerr := Encrypt(pass, keyPath, secret)\nfor i := 0; isNonceGenFailure(err) && i < 3; i++ {\n    time.Sleep(500 * time.Millisecond) // early-boot entropy may recover\n    enc, err = Encrypt(pass, keyPath, secret)\n}\nif err != nil {\n    return fmt.Errorf(\"encryption aborted: %w\", err) // never fall back to a weaker scheme\n}","preventionTips":["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"],"tags":["crypto","entropy","go","runtime","containers"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}