ruvnet/ruflo · error
peer url must not embed credentials
Error message
peer url must not embed credentials
What it means
validatePeerUrl rejects URLs containing userinfo (username or password) because peer URLs may end up in logs and registry files, which would leak embedded credentials. Any parseable http/https URL with a non-empty u.username or u.password throws this error.
Solutions
- Remove the credentials from the URL and pass them out-of-band (headers, env vars, a secrets manager)
- If the endpoint requires basic auth, configure it at the HTTP client layer rather than in the peer URL
- Rotate any credentials that were embedded in a URL — they may already be logged
- Re-test with the bare origin, e.g. 'https://peer.example.com'
Example fix
// before
validatePeerUrl('https://user:secret@peer.example.com');
// after
validatePeerUrl('https://peer.example.com'); // auth supplied separately via headers/env Defensive patterns
Strategy: validation
Validate before calling
const u = new URL(peerUrl);
if (u.username || u.password) {
throw new Error('peer url must not embed credentials; pass them out-of-band');
} Try / catch
try {
const origin = validatePeerUrl(rawUrl);
} catch (e) {
if (e.message === 'peer url must not embed credentials') {
console.error('Credentials found in peer URL — strip them and supply auth via headers/env; rotate the leaked credential');
} else throw e;
} Prevention
- Keep secrets out of URLs; use env vars or a secrets manager
- Search configs for '@' in URL hosts before deploying (grep for '://[^/]+@')
- If a credential was embedded in a URL, rotate it — it may have been logged
- Configure HTTP basic auth at the client layer, not in persisted peer entries
When it happens
Trigger: Calling validatePeerUrl or addPeer with URLs like 'https://user:pass@peer.example.com' or 'http://admin@host/' — any string where the credentials component is present, even empty-password basic auth.
Common situations: Copying a URL that included basic-auth credentials from a service dashboard or curl command; shared internal services that embed tokens in the host part; secrets pasted into config files.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
- FORBIDDEN_HEADER
- FORBIDDEN_PROTOCOL
- INVALID_PASSWORD_LENGTH
- peer url must be http or https
- AI budget file is a symlink (refusing)
AI-assisted analysis of ruvnet/ruflo@2602b642d9 (2026-09-15).
Data as JSON: /api/errors/130ffd79fdff0af0.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts:256
function writePeers(basePath: string, peers: FederationPeer[]): void {
ensureDir(basePath);
writeFileSync(peersPath(basePath), JSON.stringify(peers, null, 2) + '\n');
}
/**
* Reject anything that is not a plain http(s) URL to a host.
*
* Blocks credentials-in-URL (they would be logged), and non-http schemes such
* as `file:` which would turn a peer entry into a local file read.
*/
export function validatePeerUrl(raw: string): string {
let u: URL;
try { u = new URL(raw); } catch { throw new Error('peer url is not a valid URL'); }
if (u.protocol !== 'http:' && u.protocol !== 'https:') {
throw new Error('peer url must be http or https');
}
if (u.username || u.password) throw new Error('peer url must not embed credentials');
return u.origin;
}
export function addPeer(
basePath: string,
input: { nodeId: string; url: string; publicKey: string; label?: string },
): FederationPeer {
if (!NODE_ID_RE.test(input.nodeId ?? '')) throw new Error('nodeId must be 16 lowercase hex chars');
if (!HEX64_RE.test(input.publicKey ?? '')) throw new Error('publicKey must be 64 lowercase hex chars');
const url = validatePeerUrl(String(input.url));
const peers = readPeers(basePath);
if (peers.length >= MAX_PEERS) throw new Error(`peer registry is full (${MAX_PEERS})`);
const existing = peers.find(p => p.nodeId === input.nodeId);
if (existing) {
// Re-pinning a different key for a known nodeId is how a key-substitution
// attack would present. Require an explicit remove first.View on GitHub (pinned to 2602b642d9)