{"record":{"id":"e686301ffbc2c39f","repo":"sipeed/picoclaw","slug":"credential-passphrase-must-not-be-empty","errorCode":null,"errorMessage":"credential: passphrase must not be empty","messagePattern":"credential: passphrase must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/credential/credential.go","lineNumber":206,"sourceCode":"\t\treturn \"\", fmt.Errorf(\"credential: enc:// gcm init: %w\", err)\n\t}\n\n\tplaintext, err := gcm.Open(nil, nonce, ciphertext, nil)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"%w: %w\", ErrDecryptionFailed, err)\n\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 {","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/credential/credential.go#L188-L224","documentation":"Returned by Encrypt when the passphrase argument is empty. Encryption derives its AES key from passphrase + SSH key, so an empty passphrase is refused outright rather than producing values encrypted under a weak/empty secret. Callers typically source the passphrase from PICOCLAW_KEY_PASSPHRASE; note this guard is on Encrypt's own argument, distinct from the ErrPassphraseRequired sentinel used on the decrypt path.","triggerScenarios":"Calling credential.Encrypt(\"\", ...) — concretely, an encrypt CLI/wrapper that reads PICOCLAW_KEY_PASSPHRASE when the env var is unset or empty and passes it through without checking.","commonSituations":"Running the encrypt command in a fresh shell/CI job where PICOCLAW_KEY_PASSPHRASE was never exported; `.env` file not loaded; empty-string export (`export PICOCLAW_KEY_PASSPHRASE=`) from a templated script; secrets manager lookup returning empty silently.","solutions":["Export a real passphrase before encrypting: `export PICOCLAW_KEY_PASSPHRASE=$(...secure source...)`","If a wrapper invokes Encrypt, fail fast with a clear message when the env var is missing rather than passing \"\" (see validationCode)","For CI, inject the passphrase via the secrets mechanism instead of assuming the environment carries it"],"exampleFix":"# before\nexport PICOCLAW_KEY_PASSPHRASE=   # empty\npicoclaw encrypt ...\n\n# after\nexport PICOCLAW_KEY_PASSPHRASE='correct horse battery staple'\npicoclaw encrypt ...","handlingStrategy":"validation","validationCode":"pass := os.Getenv(\"PICOCLAW_KEY_PASSPHRASE\")\nif pass == \"\" {\n\treturn errors.New(\"PICOCLAW_KEY_PASSPHRASE is not set; refusing to encrypt with empty passphrase\")\n}\nencVal, err := credential.Encrypt(pass, \"\", plaintext)","typeGuard":null,"tryCatchPattern":"if _, err := credential.Encrypt(pass, keyPath, plaintext); err != nil {\n\tif strings.Contains(err.Error(), \"passphrase must not be empty\") {\n\t\t// fetch the passphrase from the proper secret source and retry once\n\t}\n\treturn err\n}","preventionTips":["Fail fast on empty env vars in wrappers (test -n \"$PICOCLAW_KEY_PASSPHRASE\")","Load .env via the app's config mechanism instead of assuming shell state","Treat empty secret-manager lookups as errors in provisioning scripts"],"tags":["go","credentials","encryption","validation","environment"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}