router-for-me/CLIProxyAPI · error
artifacts[%d]: pinned artifact url must not contain credenti
Error message
artifacts[%d]: pinned artifact url must not contain credentials
What it means
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.
Source
Thrown at internal/pluginstore/manifest.go:157
return errVersion
}
if releaseVersion != normalizeVersion(version) {
return fmt.Errorf("release-tag %q resolves version %q, want %q", releaseTag, releaseVersion, normalizeVersion(version))
}
return nil
default:
return fmt.Errorf("unsupported install type %q", m.Install.Type)
}
}
func validatePinnedArtifactURLs(artifacts []Artifact) error {
for index, artifact := range artifacts {
parsed, errParse := url.Parse(strings.TrimSpace(artifact.URL))
if errParse != nil {
return fmt.Errorf("artifacts[%d]: invalid artifact url", index)
}
if parsed.User != nil {
return fmt.Errorf("artifacts[%d]: pinned artifact url must not contain credentials", index)
}
if parsed.RawQuery != "" || parsed.Fragment != "" {
return fmt.Errorf("artifacts[%d]: pinned artifact url must not contain query or fragment", index)
}
}
return nil
}
func validateManifestPluginID(id string) error {
id = strings.TrimSpace(id)
if id == "" {
return fmt.Errorf("missing required field id")
}
if !validPluginID(id) {
return fmt.Errorf("invalid plugin id %q", id)
}
return nil
}View on GitHub (pinned to 78f0c4079e)
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
Example fix
# before artifacts: - url: https://ci:hunter2@dl.acme.io/plug.so # artifacts[0]: pinned artifact url must not contain credentials # after artifacts: - url: https://dl.acme.io/plug.so
Defensive patterns
Strategy: validation
Validate before calling
for i, a := range plan.Artifacts {
if u, err := url.Parse(strings.TrimSpace(a.URL)); err == nil && u.User != nil {
return fmt.Errorf("artifacts[%d] embeds credentials", i)
}
}
_ = m.Validate() Type guard
func credentialFreeURL(raw string) bool { u, err := url.Parse(strings.TrimSpace(raw)); return err == nil && u.User == nil } Try / catch
if err := m.Validate(); err != nil && strings.Contains(err.Error(), "must not contain credentials") { /* strip userinfo, rotate leaked secret, re-validate */ } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- artifacts[%d]: pinned artifact url must not contain query or
- direct plugin sync artifact %d must use https
- zip entry has empty name
- zip entry %s uses backslash path separators
- zip entry %s is absolute
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/23a0ffa5d9b786ff.
Report an issue: GitHub.