{"record":{"id":"1c5deb0c02320a42","repo":"ruvnet/ruflo","slug":"roomid-must-not-contain","errorCode":null,"errorMessage":"roomId must not contain ..","messagePattern":"roomId must not contain \\.\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts","lineNumber":322,"sourceCode":"\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.\n */","sourceCodeStart":304,"sourceCodeEnd":340,"githubUrl":"https://github.com/ruvnet/ruflo/blob/2602b642d92234c710ffbe96bfb33007d481ceab/v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts#L304-L340","documentation":"validateRoomId explicitly rejects any roomId containing '..' because dot-dot segments can form path traversal when room IDs are mapped to filesystem or resource paths. The regex allows individual dots, so this separate check is the backstop that guarantees no '..' sequence ever reaches path-sensitive code.","triggerScenarios":"Calling mergeEnvelopes, syncRoomFromPeer, or server startup with a roomId like 'rooms/../secrets', 'a..b', or any ID containing a literal '..' substring — often from untrusted peer-supplied or user-supplied input.","commonSituations":"Accepting room IDs from remote federation peers without validation; user attempts like '../../etc' typed into a room name; naive join operations producing 'room-..-backup' style names.","solutions":["Strip or reject '..' sequences from the roomId before calling the API (e.g. collapse duplicate dots or refuse the input).","Validate untrusted room IDs at the ingress boundary (HTTP handler, peer message) so malformed IDs never reach mergeEnvelopes/syncRoomFromPeer.","If '..' was unintentional (e.g. generated name), fix the ID generation to avoid consecutive dots.","Keep the check even after sanitizing — treat any '..' input as an attack attempt and reject the request."],"exampleFix":"// before\nconst roomId = peerMessage.roomId; // 'rooms/../admin'\nvalidateRoomId(roomId); // throws 'roomId must not contain ..'\n// after\nif (peerMessage.roomId.includes('..')) return reject('bad room id');\nconst roomId = peerMessage.roomId.replaceAll('..', '.');\nvalidateRoomId(roomId);","handlingStrategy":"validation","validationCode":"if (typeof roomId === 'string' && roomId.includes('..')) throw new Error('roomId must not contain ..');","typeGuard":"function isTraversalSafeRoomId(v: unknown): v is string {\n  return typeof v === 'string' && v.length > 0 && v.length <= 128 && !v.includes('..');\n}","tryCatchPattern":"try {\n  validateRoomId(roomId);\n} catch (e) {\n  if (e.message === 'roomId must not contain ..') {\n    console.warn(`Traversal attempt blocked for roomId ${JSON.stringify(roomId)}`);\n    return { ok: false, reason: 'path-traversal' };\n  }\n  throw e;\n}","preventionTips":["Treat '..' in any identifier from untrusted sources as an attack and reject at ingress","Never build room IDs by naive string concatenation of user input","Log rejected IDs for security auditing","Re-validate IDs received from federation peers even if your own layer validated them at send time"],"tags":["security","path-traversal","validation"],"backgroundTag":"path-traversal-blocked","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"}