ruvnet/ruflo · error · Error

basePath contains disallowed characters

Error message

basePath contains disallowed characters

What it means

resolveBasePath() validates the basePath argument of every agentbbs tool (default '.agentbbs') against /\.\.[\\/]|\0/ before resolving it against the project cwd: any '..' followed by a slash or backslash, or any NUL byte, throws. This is deliberate path-traversal and binary-garbage protection — the bulletin board must stay rooted inside the project.

Solutions

  1. Use a simple relative directory (or omit basePath for the '.agentbbs' default) or an absolute path that does not contain '..' segments
  2. To share a store across projects, pass an absolute path like '/srv/agentbbs' — absolute paths skip the traversal-prone re-resolution
  3. Sanitize untrusted input: reject or strip '../', '..\', and NUL before passing basePath to the tool

Example fix

// before — traversal sequence rejected
await callMCPTool('agentbbs_post', { basePath: '../shared-bbs', roomId: '#ops', ... });

// after — absolute path or project-relative default
await callMCPTool('agentbbs_post', { basePath: '/srv/shared-bbs', roomId: '#ops', ... });
Defensive patterns

Strategy: validation

Validate before calling

const UNSAFE_BASEPATH = /\.\.[\\/]|\0/;

function safeBasePath(input?: string): string {
  const p = input && input.length > 0 ? input : '.agentbbs';
  if (UNSAFE_BASEPATH.test(p)) {
    throw new Error('basePath must not contain ../ or NUL bytes');
  }
  return p;
}

Type guard

const isSafeBasePath = (p: string): boolean => !/\.\.[\\/]|\0/.test(p);

Prevention

When it happens

Trigger: Passing basePath: '../../etc' or 'foo/../bar'; forwarding a user/chat-supplied path into an agentbbs tool without sanitization (classic prompt-injection escape attempt); a config value containing a NUL byte from binary corruption; Windows-style '..\' separators.

Common situations: Agents taking a basePath parameter from untrusted LLM output; automation scripts computing paths with join() that reintroduce '../' segments; attempts to share one BBS store between projects via parent-directory paths (use an absolute path instead).

Related errors


AI-assisted analysis of ruvnet/ruflo@9c61c86f06 (2026-08-18). Data as JSON: /api/errors/a5f36d0a4da18ae6. Report an issue: GitHub.

Appendix: source

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

      shell: process.platform === 'win32',
      windowsHide: true,
    });
    _cliAvailable = true;
  } catch {
    _cliAvailable = false;
  }
  return _cliAvailable;
}

function degradedResult(reason: string): { success: true; degraded: true; reason: string } {
  return { success: true, degraded: true, reason };
}

function resolveBasePath(input?: string): string {
  const p = input && typeof input === 'string' && input.length > 0
    ? input
    : '.agentbbs';
  if (/\.\.[\\/]|\0/.test(p)) throw new Error('basePath contains disallowed characters');
  const abs = isAbsolute(p) ? p : resolve(getProjectCwd(), p);
  return abs;
}

function validateRoomLabel(label: string): string {
  if (!label || typeof label !== 'string') throw new Error('roomLabel is required');
  if (label.length > 128) throw new Error('roomLabel exceeds 128 chars');
  // Rooms are conventionally `#sales`, `#finance`, etc. — keep `#` in the allow-list.
  if (!/^[A-Za-z0-9_.\-:/@#]+$/.test(label)) {
    throw new Error('roomLabel may only contain [A-Za-z0-9_.\\-:/@#]');
  }
  return label;
}

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');
  if (!/^[A-Za-z0-9_.\-:/@#]+$/.test(roomId)) {

View on GitHub (pinned to 9c61c86f06)