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
- Print/inspect the offending string — if it starts with '<' or readable text, your source is not base64 (fetch error page, wrong Content-Type handling).
- Strip whitespace and newlines before decoding: strings.Map to remove \n, \r, spaces.
- Ensure the string was URL-unescaped exactly once before decoding.
- 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
- Check the HTTP Content-Type and first byte of subscription responses before base64-decoding.
- Strip whitespace/newlines from pasted base64 before decoding.
- URL-unescape exactly once before decoding.
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.