{"record":{"id":"23a0ffa5d9b786ff","repo":"router-for-me/CLIProxyAPI","slug":"artifacts-d-pinned-artifact-url-must-not-contai","errorCode":null,"errorMessage":"artifacts[%d]: pinned artifact url must not contain credentials","messagePattern":"artifacts\\[(.+?)\\]: pinned artifact url must not contain credentials","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/manifest.go","lineNumber":157,"sourceCode":"\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)\n\t}\n\treturn nil\n}","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/manifest.go#L139-L175","documentation":"validatePinnedArtifactURLs() rejects any artifact URL whose parsed URL has a userinfo component (scheme://user:pass@host). Pinned download URLs must be credential-free so tokens never sit in manifests or logs.","triggerScenarios":"Direct-install manifest artifact URL like \"https://ci:secret@dl.acme.com/plug.so\" or \"ftp://anonymous@mirror/plug.tar.gz\" — parsed.User is non-nil for any user[:pass]@ segment.","commonSituations":"Copy-pasting an authenticated curl/wget URL from internal docs; mirroring behind basic auth; CI injecting credentials into the URL variable used to render the manifest.","solutions":["Remove user:pass@ from the artifact URL and use a plain anonymous URL","Serve the artifact from a location that does not need embedded credentials (pre-signed URLs belong in query params only if the server supports expiring links — but note error 687 forbids query strings too, so use short-lived path tokens or a public bucket)","Rotate any credential that was embedded, since it has likely leaked into manifests/logs"],"exampleFix":"# before\nartifacts:\n  - url: https://ci:hunter2@dl.acme.io/plug.so\n# artifacts[0]: pinned artifact url must not contain credentials\n\n# after\nartifacts:\n  - url: https://dl.acme.io/plug.so","handlingStrategy":"validation","validationCode":"for i, a := range plan.Artifacts {\n    if u, err := url.Parse(strings.TrimSpace(a.URL)); err == nil && u.User != nil {\n        return fmt.Errorf(\"artifacts[%d] embeds credentials\", i)\n    }\n}\n_ = m.Validate()","typeGuard":"func credentialFreeURL(raw string) bool { u, err := url.Parse(strings.TrimSpace(raw)); return err == nil && u.User == nil }","tryCatchPattern":"if err := m.Validate(); err != nil && strings.Contains(err.Error(), \"must not contain credentials\") { /* strip userinfo, rotate leaked secret, re-validate */ }","preventionTips":["Never paste authenticated curl URLs into manifests","Scan manifests for '://user:pass@' patterns in CI and fail","Rotate any credential that ever touched a manifest"],"tags":["pluginstore","security","artifact-url","credentials"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}