sipeed/picoclaw · error
credential: enc:// invalid base64: %w
Error message
credential: enc:// invalid base64: %w
What it means
Returned by resolveEncrypted when the base64 payload after the enc:// prefix cannot be decoded with standard base64. The encrypted credential format is `enc://<std-base64(salt|nonce|ciphertext)>`, so any deviation from strict standard-base64 (base64url characters, missing padding, embedded whitespace/newlines, truncated copy) fails at DecodeString before decryption is attempted.
Source
Thrown at pkg/credential/credential.go:168
}
// Plaintext credential — return unchanged.
return raw, nil
}
// resolveEncrypted decrypts an enc:// credential using PassphraseProvider.
func resolveEncrypted(raw string) (string, error) {
passphrase := PassphraseProvider()
if passphrase == "" {
return "", ErrPassphraseRequired
}
sshKeyPath := pickSSHKeyPath("") // override="": consult env then auto-detect
b64 := strings.TrimPrefix(raw, EncScheme)
blob, err := base64.StdEncoding.DecodeString(b64)
if err != nil {
return "", fmt.Errorf("credential: enc:// invalid base64: %w", err)
}
if len(blob) < saltLen+nonceLen+1 {
return "", fmt.Errorf("credential: enc:// payload too short")
}
salt := blob[:saltLen]
nonce := blob[saltLen : saltLen+nonceLen]
ciphertext := blob[saltLen+nonceLen:]
key, err := deriveKey(passphrase, sshKeyPath, salt)
if err != nil {
return "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("credential: enc:// cipher init: %w", err)
}
gcm, err := cipher.NewGCM(block)View on GitHub (pinned to 49183d7e8d)
Solutions
- Re-generate the credential with the library's own Encrypt/CLI so the encoding is guaranteed standard base64
- If hand-repairing: strip whitespace, convert -/_ to +/, restore padding — or simply re-encrypt, which is safer
- Store enc:// values in YAML single-quoted scalars or block literals to prevent line-wrap/folding damage
- Verify with `base64 -d <<< '<payload>'` that it decodes before blaming the passphrase
Example fix
# before (base64url chars, no padding) api_key: enc://a-b_c # after (re-encrypt; std base64 with padding) api_key: enc://c2FsdHwxMjM0NTY3ODlweQ==
Defensive patterns
Strategy: validation
Validate before calling
// Validate enc:// shape before resolving.
func encBase64OK(raw string) error {
if !strings.HasPrefix(raw, "enc://") { return nil }
_, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(raw, "enc://"))
return err
} Type guard
func isStdBase64EncPayload(raw string) bool {
if !strings.HasPrefix(raw, "enc://") { return true }
_, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(raw, "enc://"))
return err == nil
} Try / catch
if _, err := resolver.Resolve(raw); err != nil {
var b64Err base64.CorruptInputError
if errors.As(err, &b64Err) {
// transport/copy corruption — re-copy the value or re-encrypt; not a passphrase problem
}
return err
} Prevention
- Only produce enc:// values via the library's Encrypt/CLI
- Store them in quoted YAML scalars or block literals to avoid wrap/fold damage
- Diff copied values against the source when decryption setup 'mysteriously' fails
When it happens
Trigger: An enc:// value containing `-` or `_` (base64url alphabet), missing `=` padding, a stray space/quote/newline from copy-paste, or a hand-truncated string. base64.StdEncoding.DecodeString rejects all of these and the error is wrapped.
Common situations: Copying enc:// values out of terminals/docs that wrap lines or smart-quote; tooling that emits base64url (JWT-adjacent ecosystems) being used to produce the value; YAML folding inserting whitespace; partial selection when copying.
Related errors
- credential: enc:// payload too short
- credential: passphrase must not be empty
- credential: failed to generate salt: %w
- failed to decode audio data: %w
- no refresh token available
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/3acdaa2775745aeb.
Report an issue: GitHub.