mihomo-party-org/clash-party · error
Plugin URL must not contain userinfo
Error message
Plugin URL must not contain userinfo
What it means
parseDownloadUrl rejects URLs that embed userinfo (user:password@host) with 'Plugin URL must not contain userinfo'. Credentials in URLs leak into logs, error messages, and proxies, so they are forbidden as a security measure.
Source
Thrown at src/main/resolve/plugin/remote.ts:14
import { getAppConfig } from '../../config/app'
import { MAX_PLUGIN_FILE_BYTES } from './constants'
import { requestOnce } from './http-client'
import { createGuardedLookup, isForbiddenHost } from './net-guard'
function parseDownloadUrl(url: string): URL {
let parsed: URL
try {
parsed = new URL(url)
} catch {
throw new Error('Invalid plugin URL')
}
if (parsed.protocol !== 'https:') throw new Error('Plugin URL must use https')
if (parsed.username || parsed.password) throw new Error('Plugin URL must not contain userinfo')
if (parsed.hash) throw new Error('Plugin URL must not contain a fragment')
if (isForbiddenHost(parsed.hostname)) throw new Error('Plugin URL must use a public host')
return parsed
}
export async function fetchRemotePlugin(url: string): Promise<string> {
const parsed = parseDownloadUrl(url)
const { subscriptionTimeout = 30000, pluginUseProxy } = await getAppConfig()
let proxy: { host: string; port: number } | undefined
if (pluginUseProxy) {
const { getControledMihomoConfig } = await import('../../config/controledMihomo')
const { 'mixed-port': port = 7890 } = await getControledMihomoConfig()
proxy = { host: '127.0.0.1', port }
}
const response = await requestOnce(parsed.toString(), {
method: 'GET',
headers: { Accept: 'application/json, application/octet-stream' },View on GitHub (pinned to 911e090537)
Solutions
- Remove the user:password@ part and rely on publicly accessible https hosting.
- If auth is required, put credentials in request headers/credentials handling rather than the URL, or host the file behind a tokenized (non-userinfo) signed link.
- Ask the provider for a signed https URL (query-token or header-token) instead of basic-auth-in-URL.
Example fix
// before
await fetchRemotePlugin('https://user:secret@files.example.com/plugin.yaml')
// after
await fetchRemotePlugin('https://files.example.com/plugin.yaml') // host the file without URL-embedded credentials Defensive patterns
Strategy: validation
Validate before calling
const u = new URL(input)
if (u.username || u.password) throw new Error('remove user:password@ from the URL') Type guard
const hasNoUserinfo = (s: string): boolean => {
try { const u = new URL(s); return !u.username && !u.password } catch { return false }
} Try / catch
try {
await fetchRemotePlugin(input)
} catch (e) {
if (e.message === 'Plugin URL must not contain userinfo') {
showUrlInputError('Credentials in the URL are not allowed; use a public or signed https link')
} else throw e
} Prevention
- Sanitize pasted URLs: strip user:pass@ before storing
- Prefer signed/tokenized https links over basic-auth-in-URL
- Redact credentials from logs if a userinfo URL slips into config
- Educate users that authenticated mirrors must expose a public https link
When it happens
Trigger: Passing a URL of the form https://user:pass@example.com/plugin.yaml — e.g. someone put basic-auth credentials into the URL for an authenticated mirror.
Common situations: Copying an authenticated download link from a tool that embeds tokens in the URL; internal artifacts servers fronted with basic auth in the URL; shared bookmarks containing embedded credentials.
Related errors
- Plugin URL must use https
- Plugin URL must use a public host
- Invalid core path: directory traversal detected
- transient
- Forbidden header: ${k}
AI-assisted analysis of mihomo-party-org/clash-party@911e090537 (2026-08-30).
Data as JSON: /api/errors/00b83f5642bea275.
Report an issue: GitHub.