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
- 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.
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
- 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
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
- basePath contains disallowed characters
- Invalid filename
- Invalid GCS object path
- memory path contains disallowed characters
- build input escapes repository
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)