sipeed/picoclaw · error
credential: enc:// payload too short
Error message
credential: enc:// payload too short
What it means
Returned by resolveEncrypted when the decoded blob is shorter than saltLen+nonceLen+1 = 16+12+1 = 29 bytes. The payload layout is salt(16) | nonce(12) | ciphertext(>=1), so anything under 29 bytes cannot even be structurally split, let alone decrypted. This fires after base64 decoding succeeded, i.e. the value is well-formed base64 but far too short.
Source
Thrown at pkg/credential/credential.go:171
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)
if err != nil {
return "", fmt.Errorf("credential: enc:// gcm init: %w", err)
}View on GitHub (pinned to 49183d7e8d)
Solutions
- Regenerate the value with the provided Encrypt/encrypt CLI — it always produces the full salt|nonce|ciphertext blob
- Never hand-base64 a plaintext and prefix enc://; the scheme is AES-GCM with a fixed header layout, not an encoding
- If a template truncated the value, store enc:// credentials in YAML block scalars or quoted strings that don't elide
Example fix
# before (hand-encoded plaintext, too short) api_key: enc://c2stMTIz # after (produced by credential.Encrypt) api_key: enc://<salt|nonce|ciphertext std-base64 from Encrypt>
Defensive patterns
Strategy: validation
Validate before calling
// Structural pre-check: decoded payload must be at least salt(16)+nonce(12)+1.
func encPayloadLongEnough(raw string) error {
if !strings.HasPrefix(raw, "enc://") { return nil }
blob, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(raw, "enc://"))
if err != nil { return err }
if len(blob) < 29 { // saltLen(16)+nonceLen(12)+1
return fmt.Errorf("enc:// payload too short (%d bytes)", len(blob))
}
return nil
} Type guard
func isWellFormedEncCredential(raw string) bool {
if !strings.HasPrefix(raw, "enc://") { return true }
blob, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(raw, "enc://"))
return err == nil && len(blob) >= 29
} Try / catch
if _, err := resolver.Resolve(raw); err != nil {
if strings.Contains(err.Error(), "payload too short") {
// hand-crafted value: replace with output of Encrypt, do not attempt repair
}
return err
} Prevention
- Never hand-base64 plaintext into enc://; the format is salt|nonce|AES-GCM ciphertext
- Generate fixtures with Encrypt in tests, not with short dummy strings
- Reject enc:// values below ~40 base64 chars in config linting
When it happens
Trigger: An enc:// value whose base64 decodes to fewer than 29 bytes — e.g. someone base64-encoded a plaintext key directly (16-byte AES key -> 16 bytes), a truncated blob that still happens to be valid base64, or a placeholder/test string like enc://QUJD.
Common situations: Users hand-crafting enc:// values by base64-encoding the raw secret instead of running the encryptor; test fixtures with dummy enc:// strings; values mangled by config templating that drops long strings.
Related errors
- credential: passphrase must not be empty
- no refresh token available
- credential: file:// reference has no filename
- credential: enc:// invalid base64: %w
- credential: failed to generate salt: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/1e1a048e1e01a08c.
Report an issue: GitHub.