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

  1. Remove user:pass@ from the artifact URL and use a plain anonymous URL
  2. 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)
  3. 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

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


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