{"record":{"id":"f170a7bfe4ef3853","repo":"router-for-me/CLIProxyAPI","slug":"plugin-store-url-must-not-contain-credentials","errorCode":null,"errorMessage":"plugin store url must not contain credentials","messagePattern":"plugin store url must not contain credentials","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/pluginstore/auth.go","lineNumber":316,"sourceCode":"\t\t\treturn false, fmt.Errorf(\"plugin store resolved auth missing header-name\")\n\t\t}\n\t\tif len(item.HeaderValue) == 0 {\n\t\t\treturn false, fmt.Errorf(\"plugin store resolved auth header value is empty\")\n\t\t}\n\t\theaders.Set(item.HeaderName, string(item.HeaderValue))\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unsupported plugin store resolved auth type %q\", item.Type)\n\t}\n\treturn true, nil\n}\n\nfunc validatePluginStoreRequestURL(auth []AuthConfig, requestURL string, kind string) error {\n\tparsed, errParse := url.Parse(strings.TrimSpace(requestURL))\n\tif errParse != nil || parsed.Scheme == \"\" || parsed.Host == \"\" {\n\t\treturn fmt.Errorf(\"invalid plugin store url\")\n\t}\n\tif parsed.User != nil {\n\t\treturn fmt.Errorf(\"plugin store url must not contain credentials\")\n\t}\n\tif hasSensitiveQueryParameter(parsed) {\n\t\treturn fmt.Errorf(\"plugin store url contains sensitive query parameter\")\n\t}\n\tif strings.EqualFold(parsed.Scheme, \"http\") && !allowInsecurePluginStoreURL(auth, requestURL, kind) {\n\t\treturn fmt.Errorf(\"insecure plugin store url requires matching allow-insecure auth rule\")\n\t}\n\treturn nil\n}\n\nfunc allowInsecurePluginStoreURL(auth []AuthConfig, requestURL string, kind string) bool {\n\titem, ok := matchingAuthConfig(auth, requestURL, kind)\n\treturn ok && item.AllowInsecure\n}\n\nfunc validateResolvedAuthExpiry(auth []ResolvedAuthConfig, expiresAt time.Time, now time.Time, requestURL string, kind string) error {\n\tif expiresAt.IsZero() {\n\t\treturn nil","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/auth.go#L298-L334","documentation":"validatePluginStoreRequestURL found a userinfo component (user:pass@host) in the store URL. Embedding credentials in the URL is explicitly forbidden for plugin stores because they leak into logs, error messages, and the request line; auth must instead be expressed via the auth rules (basic type with username-env/password-env). The check is parsed.User != nil, so even a bare 'user@host' triggers it.","triggerScenarios":"A store/registry/artifact URL like https://user:pass@plugins.example.com/index.json is passed to a pluginstore fetch.","commonSituations":"Developer pastes a private-registry URL straight out of a browser or CI script that inline-embeds credentials; migrating from a tool that allowed userinfo URLs (git, npm scope auth).","solutions":["Remove user:pass@ from the URL","Add an auth rule with type: basic and username-env/password-env that matches the store URL","Confirm the request now authenticates via the header set by the auth rule"],"exampleFix":"# before\nurl: https://user:pass@plugins.example.com/index.json\n\n# after\nurl: https://plugins.example.com/index.json\nauth:\n  - match: https://plugins.example.com\n    type: basic\n    username-env: PLUGIN_USER\n    password-env: PLUGIN_PASS","handlingStrategy":"validation","validationCode":"func urlHasCredentials(raw string) bool {\n\tu, err := url.Parse(strings.TrimSpace(raw))\n\treturn err == nil && u.User != nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never embed user:pass@ in store URLs; keep secrets in auth rules referencing env vars","Scan committed configs for '://[^/]*@' patterns in CI"],"tags":["security","url","auth","plugin-store"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}