{"record":{"id":"def7cdf7e9534927","repo":"siyuan-note/siyuan","slug":"invalid-download-url-s","errorCode":null,"errorMessage":"invalid download URL: %s","messagePattern":"invalid download URL: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":374,"sourceCode":"\n\t// commit/<sha>\n\tif len(parts) >= 4 && parts[2] == \"commit\" {\n\t\tsha := parts[3]\n\t\treturn normalizedSkillSource{\n\t\t\tdownloadURL: \"https://codeload.github.com/\" + ownerRepo + \"/zip/\" + sha,\n\t\t\tisZip:       true,\n\t\t}, nil\n\t}\n\n\t// 纯仓库地址：默认 main，失败回退 master\n\treturn codeloadSource(ownerRepo, \"main\"), nil\n}\n\n// downloadSkillSource 下载 skill 源，返回字节、Content-Type\nfunc downloadSkillSource(src normalizedSkillSource) (data []byte, contentType string, err error) {\n\tu, perr := url.Parse(src.downloadURL)\n\tif perr != nil || u.Host == \"\" {\n\t\treturn nil, \"\", fmt.Errorf(\"invalid download URL: %s\", src.downloadURL)\n\t}\n\tif cerr := CheckHostSSRF(u.Hostname()); cerr != nil {\n\t\treturn nil, \"\", cerr\n\t}\n\n\tdata, contentType, err = fetchBytes(src.downloadURL)\n\tif err == nil {\n\t\treturn data, contentType, nil\n\t}\n\n\t// codeload main 分支 404 时回退 master\n\tif src.isZip && src.branch == \"main\" {\n\t\townerRepo := strings.TrimPrefix(strings.TrimPrefix(src.downloadURL, \"https://codeload.github.com/\"), \"http://codeload.github.com/\")\n\t\townerRepo = strings.TrimSuffix(ownerRepo, \"/zip/refs/heads/main\")\n\t\tfallback := codeloadSource(ownerRepo, \"master\")\n\t\tdata, contentType, ferr := fetchBytes(fallback.downloadURL)\n\t\tif ferr != nil {\n\t\t\treturn nil, \"\", fmt.Errorf(\"download failed (tried main and master): %v\", err)","sourceCodeStart":356,"sourceCodeEnd":392,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/skill.go#L356-L392","documentation":"Thrown by downloadSkillSource when the already-normalized download URL fails url.Parse or has an empty host. This is a defensive check on an internally-constructed URL; reaching it usually indicates a bug in normalizeSkillURL/normalizeGitHubURL producing a malformed downloadURL.","triggerScenarios":"A code path constructs a normalizedSkillSource with an empty or malformed downloadURL (e.g. a codeload URL built from an empty ownerRepo); the SSRF/host check then cannot proceed. Not typically reachable from valid user input.","commonSituations":"A future refactor breaks URL construction; an unexpected branch in normalizeGitHubURL returns an empty downloadURL; a custom (non-github) host produced a URL without a host component.","solutions":["Inspect the normalizedSkillSource.downloadURL produced by normalizeSkillURL for the given input.","Ensure normalizeGitHubURL always returns a populated downloadURL for every accepted branch.","Report the input that triggered it so the normalization gap can be fixed upstream."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// defensive: surface the malformed internal URL for diagnosis\nres, err := util.InstallSkill(src)\nif err != nil && strings.HasPrefix(err.Error(), \"invalid download URL\") {\n    logging.LogErrorf(\"normalizeSkillURL produced a bad downloadURL for input %q\", src)\n}","preventionTips":["Report the exact input that triggers this so the normalization logic can be patched.","Treat this error as an internal bug, not a user mistake.","Add unit tests covering each normalizeGitHubURL branch to prevent regressions."],"tags":["skill-management","network","internal"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}