{"record":{"id":"019c69365690c54a","repo":"MHSanaei/3x-ui","slug":"panel-updater-exceeds-d-bytes","errorCode":null,"errorMessage":"panel updater exceeds %d bytes","messagePattern":"panel updater exceeds (.+?) bytes","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/web/service/panel/panel.go","lineNumber":393,"sourceCode":"\t}\n\tpath := file.Name()\n\tok := false\n\tdefer func() {\n\t\t_ = file.Close()\n\t\tif !ok {\n\t\t\t_ = os.Remove(path)\n\t\t}\n\t}()\n\n\tn, err := io.Copy(file, io.LimitReader(resp.Body, maxPanelUpdaterBytes+1))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"write panel updater: %w\", err)\n\t}\n\tif n == 0 {\n\t\treturn \"\", fmt.Errorf(\"panel updater download is empty\")\n\t}\n\tif n > maxPanelUpdaterBytes {\n\t\treturn \"\", fmt.Errorf(\"panel updater exceeds %d bytes\", maxPanelUpdaterBytes)\n\t}\n\tif err := file.Chmod(0o700); err != nil {\n\t\treturn \"\", err\n\t}\n\tok = true\n\treturn path, nil\n}\n\nfunc fetchLatestPanelVersion() (string, error) {\n\trelease, err := fetchPanelRelease(\"\")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif release.TagName == \"\" {\n\t\treturn \"\", fmt.Errorf(\"latest panel release tag is empty\")\n\t}\n\treturn release.TagName, nil\n}","sourceCodeStart":375,"sourceCodeEnd":411,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/web/service/panel/panel.go#L375-L411","documentation":"Returned when the panel updater body streams more than maxPanelUpdaterBytes (2 MiB, io.LimitReader caps at +1 byte so an over-limit download is detected, not just truncated). Because the updater script is small, an oversized body almost always means the URL returned something other than the updater — typically an HTML error page or login page from a proxy. Prevents writing and executing an arbitrary oversized payload.","triggerScenarios":"The updater URL is intercepted by a captive portal / proxy auth page (HTML, hundreds of KB to MBs); the release asset was replaced by a large artifact; a redirect page served inline instead of followed.","commonSituations":"Egress through an authenticating HTTP proxy; CDN edge serving a big block page; pointing the updater at a wrong tag whose asset is a full binary, not the small updater script.","solutions":["curl -sL the same URL and inspect Content-Type/size — HTML instead of the binary means proxy/captive-portal interception","Fix or bypass the outbound proxy so the real asset is fetched","If the genuine updater has grown past 2 MiB, bump maxPanelUpdaterBytes in internal/web/service/panel/panel.go and rebuild"],"exampleFix":"// before (panel.go:44)\nmaxPanelUpdaterBytes = 2 << 20\n// after — only if the real asset outgrew the cap\nmaxPanelUpdaterBytes = 8 << 20\n","handlingStrategy":"validation","validationCode":"resp, err := client.Head(assetURL)\nif err == nil && resp.ContentLength > maxPanelUpdaterBytes {\n    return fmt.Errorf(\"asset of %d bytes exceeds updater cap; check proxy interception\", resp.ContentLength)\n}\n","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Size-limit downloads with LimitReader(n+1) so over-cap is detected rather than truncated","Log Content-Type on failed updater downloads — HTML means a proxy, not GitHub","Review the cap constant whenever the upstream updater artifact changes shape"],"tags":["download","size-limit","updater","security"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}