{"record":{"id":"8e961192977eb844","repo":"sipeed/picoclaw","slug":"credential-failed-to-generate-salt-w","errorCode":null,"errorMessage":"credential: failed to generate salt: %w","messagePattern":"credential: failed to generate salt: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/credential/credential.go","lineNumber":212,"sourceCode":"\t}\n\treturn string(plaintext), nil\n}\n\n// Encrypt encrypts plaintext and returns an enc:// credential string.\n//\n// passphrase is required (PICOCLAW_KEY_PASSPHRASE value).\n// sshKeyPath is the SSH private key file to use; pass \"\" to auto-detect via\n// PICOCLAW_SSH_KEY_PATH env var or ~/.ssh/picoclaw_ed25519.key.\n// An SSH private key must be resolvable or Encrypt returns an error.\nfunc Encrypt(passphrase, sshKeyPath, plaintext string) (string, error) {\n\tif passphrase == \"\" {\n\t\treturn \"\", fmt.Errorf(\"credential: passphrase must not be empty\")\n\t}\n\tsshKeyPath = pickSSHKeyPath(sshKeyPath)\n\n\tsalt := make([]byte, saltLen)\n\tif _, err := io.ReadFull(rand.Reader, salt); err != nil {\n\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)","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/credential/credential.go#L194-L230","documentation":"Returned by Encrypt when io.ReadFull fails while filling the 16-byte salt from crypto/rand.Reader. The OS CSPRNG essentially never fails on Linux/macOS, so in practice this surfaces only under abnormal conditions: a custom/mocked rand.Reader in tests, a broken /dev/urandom in a constrained container, or fd exhaustion at OS level. It is wrapped with %w so the OS error is preserved.","triggerScenarios":"Code that swaps package-level rand.Reader with a failing stub (unit tests) and then calls Encrypt; exotic sandboxed environments where /dev/urandom is unavailable or reads error; heavy fd/entropy pressure during early boot on minimal VMs.","commonSituations":"Test suites overriding crypto/rand.Reader without restoring it, leaking into subsequent Encrypt calls; stripped-down containers (no /dev/urandom mount); extremely rare kernel-level issues. In normal deployments this error is a signal that the runtime environment, not the code, is broken.","solutions":["If tests override rand.Reader, restore the original (t.Cleanup) so production paths see the real CSPRNG","In containers/VMs, verify `ls -l /dev/urandom` and `head -c 16 /dev/urandom | xxd` works as the service user","Retry the encrypt operation once — a transient read failure is the only recoverable flavor; persistent failure means the environment must be fixed","If persistent, escalate to the platform level (container spec, kernel entropy settings) rather than working around in app code"],"exampleFix":"// before (test leaks a stubbed reader)\nrand.Reader = stubReader\ncallEncrypt() // later production code fails here\n\n// after\nt.Cleanup(func() { rand.Reader = origReader })","handlingStrategy":"retry","validationCode":"// Verify the CSPRNG is usable in this environment before encrypt workloads.\nprobe := make([]byte, 16)\nif _, err := io.ReadFull(rand.Reader, probe); err != nil {\n\treturn fmt.Errorf(\"crypto/rand unavailable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"var enc string\nerr := retry(2, func() error { // transient OS entropy read failures only\n\tvar e error\n\tenc, e = credential.Encrypt(pass, keyPath, plaintext)\n\treturn e\n})\nif err != nil && strings.Contains(err.Error(), \"generate salt\") {\n\t// environment-level CSPRNG failure — stop and fix the platform, don't loop","preventionTips":["In tests, always restore crypto/rand.Reader via t.Cleanup after stubbing","Container images: ensure /dev/urandom exists and is readable by the service user","Include a tiny crypto/rand probe in startup health checks for hardened/sandboxed deployments"],"tags":["go","credentials","encryption","crypto-rand","environment"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}