ruvnet/ruflo · error

roomId must not contain ..

Error message

roomId must not contain ..

What it means

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.

Solutions

  1. Strip or reject '..' sequences from the roomId before calling the API (e.g. collapse duplicate dots or refuse the input).
  2. Validate untrusted room IDs at the ingress boundary (HTTP handler, peer message) so malformed IDs never reach mergeEnvelopes/syncRoomFromPeer.
  3. If '..' was unintentional (e.g. generated name), fix the ID generation to avoid consecutive dots.
  4. Keep the check even after sanitizing — treat any '..' input as an attack attempt and reject the request.

Example fix

// before
const roomId = peerMessage.roomId; // 'rooms/../admin'
validateRoomId(roomId); // throws 'roomId must not contain ..'
// after
if (peerMessage.roomId.includes('..')) return reject('bad room id');
const roomId = peerMessage.roomId.replaceAll('..', '.');
validateRoomId(roomId);
Defensive patterns

Strategy: validation

Validate before calling

if (typeof roomId === 'string' && roomId.includes('..')) throw new Error('roomId must not contain ..');

Type guard

function isTraversalSafeRoomId(v: unknown): v is string {
  return typeof v === 'string' && v.length > 0 && v.length <= 128 && !v.includes('..');
}

Try / catch

try {
  validateRoomId(roomId);
} catch (e) {
  if (e.message === 'roomId must not contain ..') {
    console.warn(`Traversal attempt blocked for roomId ${JSON.stringify(roomId)}`);
    return { ok: false, reason: 'path-traversal' };
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of ruvnet/ruflo@2602b642d9 (2026-09-15). Data as JSON: /api/errors/1c5deb0c02320a42. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts:322

export function readEnvelopes(basePath: string, roomId: string): SignedEnvelope[] {
  const p = roomLogPath(basePath, roomId);
  if (!existsSync(p)) return [];
  const out: SignedEnvelope[] = [];
  for (const line of readFileSync(p, 'utf-8').split(/\r?\n/)) {
    if (!line.trim()) continue;
    try { out.push(JSON.parse(line)); } catch { /* skip malformed */ }
  }
  return out;
}

export function validateRoomId(roomId: string): string {
  if (!roomId || typeof roomId !== 'string') throw new Error('roomId is required');
  if (roomId.length > 128) throw new Error('roomId exceeds 128 chars');
  // Also blocks path traversal: `.` is allowed but `/` segments cannot form
  // `..` without tripping the explicit check below.
  if (!ROOM_ID_RE.test(roomId)) throw new Error('roomId has invalid characters');
  if (roomId.includes('..')) throw new Error('roomId must not contain ..');
  return roomId;
}

export interface MergeResult {
  merged: number;
  skippedDuplicate: number;
  skippedUnverified: number;
  skippedOversize: number;
  skippedHopLimit: number;
}

/**
 * Union-merge verified envelopes from a peer into the local room log.
 *
 * Idempotent: `envelopeId` is the merge key, so replaying the same batch is a
 * no-op. Anything that fails verification is dropped and counted rather than
 * quarantined — a receiver has no use for an envelope it cannot attribute.
 */

View on GitHub (pinned to 2602b642d9)