ruvnet/ruflo · error · Error

Invalid filename: ${fileName}

Error message

Invalid filename: ${fileName}

What it means

uploadToGCS() derives fileName from options.name or a generated `${contentId}.cfp.json` and validates it against /^[a-zA-Z0-9._-]+$/ while also rejecting '..'. The regex forbids slashes, spaces, unicode, and any path separators — this is the path-traversal guard applied before the object path is built and handed to gcloud. Custom names with directories (e.g. 'nested/dir/file.json') or '..' sequences fail here.

Source

Thrown at v3/@claude-flow/cli/src/transfer/storage/gcs.ts:123

    config?: GCSConfig;
    metadata?: Record<string, string>;
  } = {}
): Promise<GCSUploadResult> {
  const config = options.config || getGCSConfig();
  if (!config) {
    throw new Error(
      'GCS not configured. Set GCS_BUCKET environment variable.\n' +
      'Or authenticate: gcloud auth login && gcloud config set project YOUR_PROJECT'
    );
  }

  const contentId = generateContentId(content);
  const checksum = crypto.createHash('sha256').update(content).digest('hex');
  const fileName = options.name || `${contentId}.cfp.json`;

  // Validate filename to prevent path traversal
  if (!/^[a-zA-Z0-9._\-]+$/.test(fileName) || fileName.includes('..')) {
    throw new Error(`Invalid filename: ${fileName}`);
  }

  const objectPath = config.prefix ? `${config.prefix}/${fileName}` : fileName;

  // S-1: Validate bucket name and object path to prevent command injection
  if (!isValidBucketName(config.bucket)) {
    throw new Error(`Invalid GCS bucket name: ${config.bucket}`);
  }
  if (!isValidObjectPath(objectPath)) {
    throw new Error(`Invalid GCS object path: ${objectPath}`);
  }

  console.log(`[GCS] Uploading to gs://${config.bucket}/${objectPath}...`);

  // Write content to temp file
  const tempDir = process.env.TMPDIR || '/tmp';
  const tempFile = path.join(tempDir, `claude-flow-upload-${Date.now()}.json`);
  fs.writeFileSync(tempFile, content);

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Use a flat, simple name: letters, digits, dot, underscore, hyphen only — e.g. 'pattern-2024-08.cfp.json'
  2. To place the object under a folder, set config.prefix (GCSConfig) instead of putting the path in name
  3. Sanitize user-supplied names before calling upload (replace disallowed chars, strip '..')
  4. Omit options.name entirely to get the generated contentId-based name, which always passes

Example fix

// before
await uploadToGCS(content, { name: 'patterns/2024/pattern.json' }); // '/' fails regex -> Invalid filename

// after
await uploadToGCS(content, {
  name: 'pattern-2024-08-18.json',
  config: { bucket: 'my-bucket', prefix: 'patterns/2024' }, // folder goes in prefix
});
Defensive patterns

Strategy: validation

Validate before calling

function sanitizeGcsFileName(name: string): string {
  const cleaned = name.replace(/[^a-zA-Z0-9._-]/g, '-').replace(/\.\./g, '.');
  if (!/^[a-zA-Z0-9._-]+$/.test(cleaned) || cleaned.includes('..')) {
    throw new Error(`Filename cannot be sanitized: ${name}`);
  }
  return cleaned;
}
await uploadToGCS(content, { name: sanitizeGcsFileName(userProvidedName) });

Type guard

function isValidGcsFileName(name: string): boolean {
  return /^[a-zA-Z0-9._-]+$/.test(name) && !name.includes('..');
}

Prevention

When it happens

Trigger: Calling uploadToGCS(content, { name: 'patterns/2024/foo.json' }) — the slash fails the regex; names containing spaces, ':' or unicode; a name like 'config..json' is caught only by the includes('..') check when the regex alone would pass.

Common situations: Using human-readable names with spaces or dates like 'my patterns (final).json'; attempting to upload into a subfolder by embedding the path in name (the API wants prefix in config instead); names copied from other object stores that allow richer characters.

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@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/59b908c44d4f5a39. Report an issue: GitHub.