sipeed/picoclaw · error
credential: passphrase must not be empty
Error message
credential: passphrase must not be empty
What it means
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.
Source
Thrown at pkg/credential/credential.go:206
return "", fmt.Errorf("credential: enc:// gcm init: %w", err)
}
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", fmt.Errorf("%w: %w", ErrDecryptionFailed, err)
}
return string(plaintext), nil
}
// Encrypt encrypts plaintext and returns an enc:// credential string.
//
// passphrase is required (PICOCLAW_KEY_PASSPHRASE value).
// sshKeyPath is the SSH private key file to use; pass "" to auto-detect via
// PICOCLAW_SSH_KEY_PATH env var or ~/.ssh/picoclaw_ed25519.key.
// An SSH private key must be resolvable or Encrypt returns an error.
func Encrypt(passphrase, sshKeyPath, plaintext string) (string, error) {
if passphrase == "" {
return "", fmt.Errorf("credential: passphrase must not be empty")
}
sshKeyPath = pickSSHKeyPath(sshKeyPath)
salt := make([]byte, saltLen)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
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 {View on GitHub (pinned to 49183d7e8d)
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
Example fix
# before export PICOCLAW_KEY_PASSPHRASE= # empty picoclaw encrypt ... # after export PICOCLAW_KEY_PASSPHRASE='correct horse battery staple' picoclaw encrypt ...
Defensive patterns
Strategy: validation
Validate before calling
pass := os.Getenv("PICOCLAW_KEY_PASSPHRASE")
if pass == "" {
return errors.New("PICOCLAW_KEY_PASSPHRASE is not set; refusing to encrypt with empty passphrase")
}
encVal, err := credential.Encrypt(pass, "", plaintext) Try / catch
if _, err := credential.Encrypt(pass, keyPath, plaintext); err != nil {
if strings.Contains(err.Error(), "passphrase must not be empty") {
// fetch the passphrase from the proper secret source and retry once
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- credential: enc:// payload too short
- credential: failed to generate salt: %w
- no refresh token available
- credential: file:// reference has no filename
- credential: enc:// invalid base64: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/e686301ffbc2c39f.
Report an issue: GitHub.