{"record":{"id":"644d8130ea77ff7a","repo":"ruvnet/ruflo","slug":"publickey-must-be-64-lowercase-hex-chars","errorCode":null,"errorMessage":"publicKey must be 64 lowercase hex chars","messagePattern":"publicKey must be 64 lowercase hex chars","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts","lineNumber":265,"sourceCode":" * Blocks credentials-in-URL (they would be logged), and non-http schemes such\n * as `file:` which would turn a peer entry into a local file read.\n */\nexport function validatePeerUrl(raw: string): string {\n  let u: URL;\n  try { u = new URL(raw); } catch { throw new Error('peer url is not a valid URL'); }\n  if (u.protocol !== 'http:' && u.protocol !== 'https:') {\n    throw new Error('peer url must be http or https');\n  }\n  if (u.username || u.password) throw new Error('peer url must not embed credentials');\n  return u.origin;\n}\n\nexport function addPeer(\n  basePath: string,\n  input: { nodeId: string; url: string; publicKey: string; label?: string },\n): FederationPeer {\n  if (!NODE_ID_RE.test(input.nodeId ?? '')) throw new Error('nodeId must be 16 lowercase hex chars');\n  if (!HEX64_RE.test(input.publicKey ?? '')) throw new Error('publicKey must be 64 lowercase hex chars');\n  const url = validatePeerUrl(String(input.url));\n\n  const peers = readPeers(basePath);\n  if (peers.length >= MAX_PEERS) throw new Error(`peer registry is full (${MAX_PEERS})`);\n\n  const existing = peers.find(p => p.nodeId === input.nodeId);\n  if (existing) {\n    // Re-pinning a different key for a known nodeId is how a key-substitution\n    // attack would present. Require an explicit remove first.\n    if (existing.publicKey !== input.publicKey) {\n      throw new Error(`nodeId ${input.nodeId} is already pinned to a different publicKey; remove it first`);\n    }\n    existing.url = url;\n    if (input.label) existing.label = input.label;\n    writePeers(basePath, peers);\n    return existing;\n  }\n","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/ruvnet/ruflo/blob/2602b642d92234c710ffbe96bfb33007d481ceab/v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts#L247-L283","documentation":"addPeer() validates that a peer's Ed25519-style public key is exactly 64 lowercase hex characters before writing it to the federation peer registry. The key is pinned for TOFU (trust-on-first-use) identity verification, so any malformed key is rejected up front. The library throws this error instead of accepting a key it could later use to authenticate peers.","triggerScenarios":"Calling addPeer(basePath, { nodeId, url, publicKey }) where publicKey is not 64 lowercase hex chars: uppercase hex, 32/33-char base58/NaCl key pasted by mistake, key with whitespace/newline, base64-encoded key, or a truncated string.","commonSituations":"Copying a public key from an SSH key or terminal output that wrapped/truncated it; mixing key formats between node versions (e.g. base64 in v1, hex in v2); a config file containing a placeholder like 'REPLACE_ME'; shell quoting stripping or adding characters.","solutions":["Re-encode the peer's public key as 64 lowercase hex characters (e.g. hex of the 32-byte key) before calling addPeer.","Trim whitespace/newlines from the key string: publicKey.trim().toLowerCase().","Verify the key length with publicKey.length === 64 and /H{64}/.test(publicKey); if using a base64 key, convert with Buffer.from(key,'base64').toString('hex').","Regenerate/export the peer's identity key in hex format if the source format is not convertible."],"exampleFix":"// before\naddPeer(base, { nodeId, url, publicKey: 'AbC123...' });\n// after\nconst pk = rawKey.trim().toLowerCase();\nif (!/^[0-9a-f]{64}$/.test(pk)) throw new Error('bad key');\naddPeer(base, { nodeId, url, publicKey: pk });","handlingStrategy":"validation","validationCode":"function isValidPublicKey(k) { return typeof k === 'string' && /^[0-9a-f]{64}$/.test(k.trim()); }\nif (!isValidPublicKey(input.publicKey)) throw new Error('peer publicKey must be 64 lowercase hex chars');","typeGuard":"const isHex64 = (v: unknown): v is string => typeof v === 'string' && /^[0-9a-f]{64}$/.test(v);","tryCatchPattern":null,"preventionTips":["Normalize keys with .trim().toLowerCase() at config load time","Keep keys in config files without line wrapping or shell interpolation","Store and transport keys only in hex between services","Add a startup assertion validating every configured peer key"],"tags":["validation","hex-format","public-key","input-validation"],"backgroundTag":"invalid-argument-format","analyzedSha":"2602b642d92234c710ffbe96bfb33007d481ceab","analyzedAt":"2026-09-15T22:58:14.805Z","contentChangedAt":"2026-09-15T22:58:14.805Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}