semaphoreui/semaphore · error

base64 decode

Error message

base64 decode: %w

What it means

Returned by DecryptAESGCM in util/encryption.go when the input ciphertext string is not valid standard base64. The function expects the AES-256-GCM ciphertext to be base64-encoded (nonce prefixed); failing to decode means the stored value is corrupted, truncated, was written in a different encoding (e.g. base64url without padding), or is plaintext from a legacy scheme. The %w wraps encoding/base64's CorruptInputError with the offending byte offset.

Solutions

  1. Check the wrapped CorruptInputError offset to locate the first invalid character in the stored value
  2. If the value came from a legacy or non-encrypted scheme, route it through the legacy plaintext path (an empty key returns the ciphertext unchanged) or migrate it
  3. Re-encrypt the secret with EncryptAESGCM to restore a well-formed base64 blob
  4. Verify the value was not mangled by storage or copy-paste (whitespace, URL-unsafe characters, missing padding)
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at util/encryption.go:41 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/3821b36907454b2d. Report an issue: GitHub.

Appendix: source

Thrown at util/encryption.go:41

	gcm, err := newGCM(encodedKey)
	if err != nil {
		return "", err
	}

	nonce := make([]byte, gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", err
	}

	return base64.StdEncoding.EncodeToString(gcm.Seal(nonce, nonce, plaintext, nil)), nil
}

// DecryptAESGCM decrypts an AES-256-GCM ciphertext.
func DecryptAESGCM(encodedCiphertext, encodedKey string) ([]byte, error) {
	ciphertext, err := base64.StdEncoding.DecodeString(encodedCiphertext)
	if err != nil {
		return nil, fmt.Errorf("base64 decode: %w", err)
	}

	if encodedKey == "" {
		return ciphertext, nil
	}

	gcm, err := newGCM(encodedKey)
	if err != nil {
		return nil, err
	}

	nonceSize := gcm.NonceSize()
	if len(ciphertext) < nonceSize {
		return nil, errors.New("ciphertext too short")
	}

	nonce, payload := ciphertext[:nonceSize], ciphertext[nonceSize:]
	return gcm.Open(nil, nonce, payload, nil)

View on GitHub (pinned to 1774ccb71a)