MHSanaei/3x-ui · error

base64 decode failed

Error message

base64 decode failed

What it means

base64DecodeFlexible tries standard-padded base64 first, then raw URL-safe base64 (with padding stripped); if both decoders reject the input it returns 'base64 decode failed'. It exists to accept the many base64 variants found in share links (padded/unpadded, standard/URL-safe alphabets). Reaching this error means the input is not valid base64 in either alphabet — usually because the payload is plain text, HTML, or truncated.

Source

Thrown at internal/util/link/outbound.go:954

	return splitComma(s)
}

func padBase64(s string) string {
	for len(s)%4 != 0 {
		s += "="
	}
	return s
}

func base64DecodeFlexible(s string) (string, error) {
	s = padBase64(s)
	if b, err := base64.StdEncoding.DecodeString(s); err == nil {
		return string(b), nil
	}
	if b, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(s, "=")); err == nil {
		return string(b), nil
	}
	return "", fmt.Errorf("base64 decode failed")
}

// SlugRemark turns a free-form remark into a tag segment, keeping Unicode
// letters and digits (so non-ASCII remarks like Cyrillic stay readable) and
// replacing every other run of characters with a single dash.
var slugRe = regexp.MustCompile(`[^\p{L}\p{N}]+`)

func SlugRemark(remark string) string {
	s := strings.ToLower(strings.TrimSpace(remark))
	s = slugRe.ReplaceAllString(s, "-")
	s = strings.Trim(s, "-")
	if s == "" {
		return ""
	}
	// collapse runs of dashes
	for strings.Contains(s, "--") {
		s = strings.ReplaceAll(s, "--", "-")
	}

View on GitHub (pinned to ad32144c42)

Solutions

  1. Print/inspect the offending string — if it starts with '<' or readable text, your source is not base64 (fetch error page, wrong Content-Type handling).
  2. Strip whitespace and newlines before decoding: strings.Map to remove \n, \r, spaces.
  3. Ensure the string was URL-unescaped exactly once before decoding.
  4. If you control the producer, emit standard base64 with padding or raw URL-safe base64 consistently.

Example fix

// before
decoded, err := base64DecodeFlexible(payload) // payload has \n inside

// after
payload = strings.NewReplacer("\n", "", "\r", "", " ", "").Replace(payload)
decoded, err := base64DecodeFlexible(payload)
Defensive patterns

Strategy: validation

Validate before calling

func looksLikeBase64(s string) bool {
	s = strings.NewReplacer("\n", "", "\r", "", " ", "").Replace(s)
	if len(s) == 0 { return false }
	for _, r := range s {
		if !(r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '+' || r == '/' || r == '-' || r == '_' || r == '=') {
			return false
		}
	}
	return true
}

Try / catch

decoded, err := base64DecodeFlexible(payload)
if err != nil {
    return fmt.Errorf("subscription payload not base64 (likely HTML/text, got %q...): %w", payload[:min(40, len(payload))], err)
}

Prevention

When it happens

Trigger: Decoding the userinfo segment of vmess:// links, the base64 body of ss:// links, or base64 subscription bodies where the string contains characters outside both alphabets (e.g. '-', '_' mixed with '+', '/', or non-ASCII), has internal whitespace, or was cut mid-encoding.

Common situations: A subscription URL returns an HTML error page (Cloudflare challenge, 404 page) that then gets base64-decoded as if it were a subscription; copy-paste of a link losing trailing characters; double-encoding mismatches; payloads already URL-unescaped incorrectly so '%' remains.

Related errors


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/c8121963f5ccb3c5. Report an issue: GitHub.