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 nil

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Remove user:pass@ from the URL
  2. Add an auth rule with type: basic and username-env/password-env that matches the store URL
  3. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/f170a7bfe4ef3853. Report an issue: GitHub.