MHSanaei/3x-ui · error

xray checksum: malformed SHA2-256 entry in digest

Error message

xray checksum: malformed SHA2-256 entry in digest

What it means

Returned by parseXrayDigestSHA256 when a line prefixed 'SHA2-256=' exists but the remaining hex value (after trim + lowercase) is not exactly 64 characters. The .dgst format is 'ALGO= <hex>'; a wrong-length value means the file format changed or the content is corrupted. Hard-coded expectation: SHA-256 hex is always 64 chars.

Source

Thrown at internal/web/service/server.go:980

	}
	raw, err := io.ReadAll(io.LimitReader(resp.Body, maxXrayDigestBytes))
	if err != nil {
		return "", fmt.Errorf("download xray checksum: %w", err)
	}
	return parseXrayDigestSHA256(raw)
}

// parseXrayDigestSHA256 extracts the lowercase SHA2-256 hex from an XTLS .dgst
// file, whose lines are "ALGO= <hex>" (the relevant one being "SHA2-256= ...").
func parseXrayDigestSHA256(dgst []byte) (string, error) {
	for line := range strings.SplitSeq(string(dgst), "\n") {
		rest, ok := strings.CutPrefix(strings.TrimSpace(line), "SHA2-256=")
		if !ok {
			continue
		}
		h := strings.ToLower(strings.TrimSpace(rest))
		if len(h) != 64 {
			return "", fmt.Errorf("xray checksum: malformed SHA2-256 entry in digest")
		}
		return h, nil
	}
	return "", fmt.Errorf("xray checksum: no SHA2-256 entry in digest")
}

func (s *ServerService) UpdateXray(version string) error {
	versions, err := s.GetXrayVersions()
	if err != nil {
		return err
	}
	if !slices.Contains(versions, version) {
		return fmt.Errorf("xray version %q is not in the fetched release list", version)
	}

	// 1. Stop xray before doing anything
	if err := s.StopXrayService(); err != nil {
		logger.Warning("failed to stop xray before update:", err)

View on GitHub (pinned to ad32144c42)

Solutions

  1. curl the .dgst URL and inspect the SHA2-256 line's actual shape
  2. Update the panel to a version matching the current .dgst format, or patch parseXrayDigestSHA256 for the new layout
  3. If the body looks mangled, retry without the proxy

Example fix

// before
h := strings.ToLower(strings.TrimSpace(rest))
if len(h) != 64 {
    return "", fmt.Errorf("xray checksum: malformed SHA2-256 entry in digest")
}
// after — tolerate 'hash  filename' lines
if h, _, ok := strings.Cut(rest, " "); ok {
    h = h
}
h = strings.ToLower(strings.TrimSpace(strings.Fields(rest)[0]))
if len(h) != 64 {
    return "", fmt.Errorf("xray checksum: malformed SHA2-256 entry in digest")
}
Defensive patterns

Strategy: validation

Validate before calling

h := strings.ToLower(strings.TrimSpace(rest))
if !regexp.MustCompile(`^[0-9a-f]{64}$`).MatchString(h) {
    return fmt.Errorf("digest entry not valid SHA-256 hex: %q", rest)
}

Type guard

func isValidSHA256Hex(s string) bool {
    if len(s) != 64 {
        return false
    }
    for _, c := range s {
        if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
            return false
        }
    }
    return true
}

Prevention

When it happens

Trigger: XTLS changes the .dgst line format (e.g. 'SHA2-256= file' multi-entry lines); a corrupted/truncated sidecar that keeps the prefix but mangles the hash; a proxy-rewritten .dgst body.

Common situations: Future upstream format drift breaking older panels; content-mangling middleboxes.

Understand the failure class

Related errors


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