{"record":{"id":"d21554d169cfd212","repo":"ruvnet/ruflo","slug":"roomid-has-invalid-characters","errorCode":null,"errorMessage":"roomId has invalid characters","messagePattern":"roomId has invalid characters","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts","lineNumber":321,"sourceCode":"}\n\nexport function readEnvelopes(basePath: string, roomId: string): SignedEnvelope[] {\n  const p = roomLogPath(basePath, roomId);\n  if (!existsSync(p)) return [];\n  const out: SignedEnvelope[] = [];\n  for (const line of readFileSync(p, 'utf-8').split(/\\r?\\n/)) {\n    if (!line.trim()) continue;\n    try { out.push(JSON.parse(line)); } catch { /* skip malformed */ }\n  }\n  return out;\n}\n\nexport function validateRoomId(roomId: string): string {\n  if (!roomId || typeof roomId !== 'string') throw new Error('roomId is required');\n  if (roomId.length > 128) throw new Error('roomId exceeds 128 chars');\n  // Also blocks path traversal: `.` is allowed but `/` segments cannot form\n  // `..` without tripping the explicit check below.\n  if (!ROOM_ID_RE.test(roomId)) throw new Error('roomId has invalid characters');\n  if (roomId.includes('..')) throw new Error('roomId must not contain ..');\n  return roomId;\n}\n\nexport interface MergeResult {\n  merged: number;\n  skippedDuplicate: number;\n  skippedUnverified: number;\n  skippedOversize: number;\n  skippedHopLimit: number;\n}\n\n/**\n * Union-merge verified envelopes from a peer into the local room log.\n *\n * Idempotent: `envelopeId` is the merge key, so replaying the same batch is a\n * no-op. Anything that fails verification is dropped and counted rather than\n * quarantined — a receiver has no use for an envelope it cannot attribute.","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/ruvnet/ruflo/blob/2602b642d92234c710ffbe96bfb33007d481ceab/v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts#L303-L339","documentation":"validateRoomId enforces a strict allow-list pattern (ROOM_ID_RE) on room IDs before any federation merge or sync touches them. The 'roomId has invalid characters' error means the supplied ID matched the length/required checks but contained characters outside the allowed set, preventing injection or malformed room keys from entering the federation layer.","triggerScenarios":"Calling mergeEnvelopes, syncRoomFromPeer, or starting the server with a roomId that fails ROOM_ID_RE — e.g. one containing spaces, unicode, control characters, or symbols not permitted by the regex.","commonSituations":"Room IDs built by concatenating user input or URL fragments without sanitizing; IDs copied from logs with trailing whitespace; programmatic IDs generated with characters like ':' or '#' that the allow-list rejects.","solutions":["Inspect the failing roomId and remove/rename characters not matching ROOM_ID_RE (stick to the allow-list, e.g. alphanumerics, '-', '_', '/', '.').","Sanitize or re-encode the room ID at its source of creation so all rooms are created with valid IDs.","Normalize input (trim whitespace, percent-decode URL fragments) before passing it to validateRoomId.","Add a test with the exact rejected ID to confirm which characters violate the pattern."],"exampleFix":"// before\nconst roomId = `rooms/${topic}:${channel}`;\nvalidateRoomId(roomId); // ':' rejected -> 'roomId has invalid characters'\n// after\nconst roomId = `rooms/${topic}-${channel.replace(/[^a-zA-Z0-9._/-]/g, '')}`;\nvalidateRoomId(roomId);","handlingStrategy":"validation","validationCode":"const ROOM_ID_SAFE = /^[a-zA-Z0-9._/-]{1,128}$/;\nif (!ROOM_ID_SAFE.test(roomId)) throw new Error(`invalid roomId: ${JSON.stringify(roomId)}`);","typeGuard":"function isValidRoomId(v: unknown): v is string {\n  return typeof v === 'string' && /^[a-zA-Z0-9._/-]{1,128}$/.test(v);\n}","tryCatchPattern":"try {\n  validateRoomId(roomId);\n} catch (e) {\n  if (e.message === 'roomId has invalid characters') {\n    console.error(`Rejected roomId ${JSON.stringify(roomId)}: allowed chars only`);\n    return { ok: false, reason: 'invalid-room-id' };\n  }\n  throw e;\n}","preventionTips":["Generate room IDs from a constrained alphabet (alphanumerics, '-', '_', '/', '.')","Trim and normalize user input before constructing a roomId","Unit-test ID generation with unicode/symbol inputs","Validate IDs at the ingress boundary (HTTP handler, peer message) not just at call time"],"tags":["validation","input-sanitization","room-id"],"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"}