router-for-me/CLIProxyAPI · error
plugin store url must not contain credentials
Error message
plugin store url must not contain credentials
What it means
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.
Source
Thrown at internal/pluginstore/auth.go:316
return false, fmt.Errorf("plugin store resolved auth missing header-name")
}
if len(item.HeaderValue) == 0 {
return false, fmt.Errorf("plugin store resolved auth header value is empty")
}
headers.Set(item.HeaderName, string(item.HeaderValue))
default:
return false, fmt.Errorf("unsupported plugin store resolved auth type %q", item.Type)
}
return true, nil
}
func validatePluginStoreRequestURL(auth []AuthConfig, requestURL string, kind string) error {
parsed, errParse := url.Parse(strings.TrimSpace(requestURL))
if errParse != nil || parsed.Scheme == "" || parsed.Host == "" {
return fmt.Errorf("invalid plugin store url")
}
if parsed.User != nil {
return fmt.Errorf("plugin store url must not contain credentials")
}
if hasSensitiveQueryParameter(parsed) {
return fmt.Errorf("plugin store url contains sensitive query parameter")
}
if strings.EqualFold(parsed.Scheme, "http") && !allowInsecurePluginStoreURL(auth, requestURL, kind) {
return fmt.Errorf("insecure plugin store url requires matching allow-insecure auth rule")
}
return nil
}
func allowInsecurePluginStoreURL(auth []AuthConfig, requestURL string, kind string) bool {
item, ok := matchingAuthConfig(auth, requestURL, kind)
return ok && item.AllowInsecure
}
func validateResolvedAuthExpiry(auth []ResolvedAuthConfig, expiresAt time.Time, now time.Time, requestURL string, kind string) error {
if expiresAt.IsZero() {
return nilView on GitHub (pinned to 78f0c4079e)
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
Example fix
# before
url: https://user:pass@plugins.example.com/index.json
# after
url: https://plugins.example.com/index.json
auth:
- match: https://plugins.example.com
type: basic
username-env: PLUGIN_USER
password-env: PLUGIN_PASS Defensive patterns
Strategy: validation
Validate before calling
func urlHasCredentials(raw string) bool {
u, err := url.Parse(strings.TrimSpace(raw))
return err == nil && u.User != nil
} Prevention
- Never embed user:pass@ in store URLs; keep secrets in auth rules referencing env vars
- Scan committed configs for '://[^/]*@' patterns in CI
When it happens
Trigger: A store/registry/artifact URL like https://user:pass@plugins.example.com/index.json is passed to a pluginstore fetch.
Common situations: 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).
Related errors
- plugin store url contains sensitive query parameter
- xai device token: create request: %w
- invalid auth file name
- plugin store auth missing header-name
- unsupported plugin store auth type %q
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/f170a7bfe4ef3853.
Report an issue: GitHub.