{"record":{"id":"d5eb6e415d52e31b","repo":"router-for-me/CLIProxyAPI","slug":"artifacts-d-invalid-artifact-url","errorCode":null,"errorMessage":"artifacts[%d]: invalid artifact url","messagePattern":"artifacts\\[(.+?)\\]: invalid artifact url","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/manifest.go","lineNumber":154,"sourceCode":"\t\t}\n\t\treleaseVersion, errVersion := ReleaseVersion(Release{TagName: releaseTag})\n\t\tif errVersion != nil {\n\t\t\treturn errVersion\n\t\t}\n\t\tif releaseVersion != normalizeVersion(version) {\n\t\t\treturn fmt.Errorf(\"release-tag %q resolves version %q, want %q\", releaseTag, releaseVersion, normalizeVersion(version))\n\t\t}\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported install type %q\", m.Install.Type)\n\t}\n}\n\nfunc validatePinnedArtifactURLs(artifacts []Artifact) error {\n\tfor index, artifact := range artifacts {\n\t\tparsed, errParse := url.Parse(strings.TrimSpace(artifact.URL))\n\t\tif errParse != nil {\n\t\t\treturn fmt.Errorf(\"artifacts[%d]: invalid artifact url\", index)\n\t\t}\n\t\tif parsed.User != nil {\n\t\t\treturn fmt.Errorf(\"artifacts[%d]: pinned artifact url must not contain credentials\", index)\n\t\t}\n\t\tif parsed.RawQuery != \"\" || parsed.Fragment != \"\" {\n\t\t\treturn fmt.Errorf(\"artifacts[%d]: pinned artifact url must not contain query or fragment\", index)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validateManifestPluginID(id string) error {\n\tid = strings.TrimSpace(id)\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"missing required field id\")\n\t}\n\tif !validPluginID(id) {\n\t\treturn fmt.Errorf(\"invalid plugin id %q\", id)","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/manifest.go#L136-L172","documentation":"validatePinnedArtifactURLs() url.Parse()s each trimmed artifact URL in a direct install plan; a parse failure (control characters, unmatched %, malformed bracket host, etc.) rejects artifacts[index] with this message. Only syntactic parseability is checked here.","triggerScenarios":"Direct-install manifest whose install.artifacts[i].url contains characters net/url cannot parse: raw spaces, \"%%\", \"http://ex ample.com/x.so\", or a URL built by string concatenation with unescaped input.","commonSituations":"Templating that injects filenames with spaces into URLs; Windows paths pasted as URLs (\"C:\\libs\\plug.dll\"); encoding bugs producing double-escapes.","solutions":["URL-encode the artifact URL properly (percent-encode spaces and special characters)","Verify each artifact URL with url.Parse (or curl) before shipping the manifest","Build URLs with net/url.URL or a templating filter that escapes, not naive concatenation"],"exampleFix":"# before\nartifacts:\n  - url: http://dl.acme.com/my plugin.so\n# artifacts[0]: invalid artifact url\n\n# after\nartifacts:\n  - url: http://dl.acme.com/my%20plugin.so","handlingStrategy":"validation","validationCode":"for i, a := range plan.Artifacts {\n    if _, err := url.Parse(strings.TrimSpace(a.URL)); err != nil {\n        return fmt.Errorf(\"artifacts[%d]: bad url: %w\", i, err)\n    }\n}\n_ = m.Validate()","typeGuard":"func parseableURL(u string) bool { _, err := url.Parse(strings.TrimSpace(u)); return err == nil }","tryCatchPattern":"if err := m.Validate(); err != nil && strings.Contains(err.Error(), \"invalid artifact url\") { /* percent-encode offending URL, re-validate */ }","preventionTips":["Build artifact URLs with net/url or an escaping template filter","Verify every artifact URL with url.Parse in CI manifest lint"],"tags":["pluginstore","manifest","artifact-url","url-parsing"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}