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

  1. Re-generate the credential with the library's own Encrypt/CLI so the encoding is guaranteed standard base64
  2. If hand-repairing: strip whitespace, convert -/_ to +/, restore padding — or simply re-encrypt, which is safer
  3. Store enc:// values in YAML single-quoted scalars or block literals to prevent line-wrap/folding damage
  4. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/3acdaa2775745aeb. Report an issue: GitHub.