Skip to content

WEB-1025: sanitize CSV/XLSX exports to prevent formula injection#3706

Merged
IOhacker merged 1 commit into
openMF:devfrom
foodeveloper:WEB-1025-sanitize-csv-export-formula-injection
Jul 10, 2026
Merged

WEB-1025: sanitize CSV/XLSX exports to prevent formula injection#3706
IOhacker merged 1 commit into
openMF:devfrom
foodeveloper:WEB-1025-sanitize-csv-export-formula-injection

Conversation

@Kenan-Abdelbaqi-FOO

@Kenan-Abdelbaqi-FOO Kenan-Abdelbaqi-FOO commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Description

Exported CSV and XLSX files were built by writing field values directly into CSV rows and spreadsheet cells. Any value beginning with a formula-trigger character (= + - @ | %) is interpreted as a formula by Excel/LibreOffice, which exposes the app to CSV / Formula Injection (CWE-1236) — potentially client-side command execution, data exfiltration via spreadsheet functions, and tampering with exported report/audit content.

This change makes exports safe by neutralizing such values so spreadsheets
treat them as plain text:

  • Adds a centralized sanitizeCsvValue() utility (src/app/core/utils/csv.utils.ts) that prefixes values starting with a formula-trigger character with a single quote, per OWASP CSV Injection guidance.
  • Applies it at every client-side CSV/XLSX serialization point:
    • reports/run-report/table-and-sms/table-and-sms.component.tsexportToXLS() and downloadCSV()
    • reports/run-report/run-report.component.tsexportToXLS()
    • system/audit-trails/audit-trails.component.tsdownloadCSV() (raw value is sanitized before JSON.stringify quoting, otherwise the leading " would hide the trigger character)

Server-generated downloads (BIRT/Pentaho outputs, receipts, bulk-import files) are out of scope, as their content is produced by the backend and cannot be sanitized on the front end.

No new dependencies are required; normal values export unchanged (no regression).

Related issues and discussion

WEB-1025

Screenshots, if any

N/A — no UI changes (export logic only).

Checklist

  • If you have multiple commits please combine them into one commit by squashing them.

  • Read and understood the contribution guidelines at web-app/.github/CONTRIBUTING.md.

Summary by CodeRabbit

  • Bug Fixes
    • CSV and XLSX exports now sanitize both cell values and headers to reduce spreadsheet formula injection risk.
    • Report, table, and audit trail downloads now consistently handle null/undefined as empty values before generating files.
  • Tests
    • Added Jest coverage for CSV sanitization to ensure safe strings remain unchanged and risky leading characters are escaped.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key: "pre_merge_checks"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ac92e226-000e-4f6f-82bc-1a90a48030c8

📥 Commits

Reviewing files that changed from the base of the PR and between 755b433 and 89186ba.

📒 Files selected for processing (5)
  • src/app/core/utils/csv.utils.spec.ts
  • src/app/core/utils/csv.utils.ts
  • src/app/reports/run-report/run-report.component.ts
  • src/app/reports/run-report/table-and-sms/table-and-sms.component.ts
  • src/app/system/audit-trails/audit-trails.component.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/app/core/utils/csv.utils.ts
  • src/app/reports/run-report/table-and-sms/table-and-sms.component.ts
  • src/app/reports/run-report/run-report.component.ts
  • src/app/system/audit-trails/audit-trails.component.ts

Walkthrough

A shared sanitizeCsvValue utility is added to normalize and escape spreadsheet-formula trigger values. It is applied to cell and header values in run-report, table-and-sms, and audit-trails export paths, and covered by a new test suite.

Changes

CSV Injection Sanitization

Layer / File(s) Summary
Sanitization utility
src/app/core/utils/csv.utils.ts, src/app/core/utils/csv.utils.spec.ts
Adds sanitizeCsvValue and tests null/undefined handling, formula-trigger escaping, safe values, and coercion behavior.
Run report XLSX export sanitization
src/app/reports/run-report/run-report.component.ts
Imports and applies sanitizeCsvValue to row cell values and header entries in exportToXLS.
Table-and-SMS XLSX and CSV export sanitization
src/app/reports/run-report/table-and-sms/table-and-sms.component.ts
Imports and applies sanitizeCsvValue to cell/header values in exportToXLS and downloadCSV.
Audit trails CSV export sanitization
src/app/system/audit-trails/audit-trails.component.ts
Removes the old JSON.stringify replacer and refactors downloadCSV to resolve, format, sanitize, then stringify each field value.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: sanitizing CSV/XLSX exports to prevent formula injection.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/app/core/utils/csv.utils.ts (1)

22-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add unit tests for the security-critical sanitizeCsvValue utility.

This function is the central defense against formula injection (CWE-1236) and is now relied upon by three export paths. Unit tests would guard against regressions if the regex or prefix logic is modified.

🧪 Suggested test cases
import { sanitizeCsvValue } from './csv.utils';

describe('sanitizeCsvValue', () => {
  it('should return empty string for null', () => {
    expect(sanitizeCsvValue(null)).toBe('');
  });

  it('should return empty string for undefined', () => {
    expect(sanitizeCsvValue(undefined)).toBe('');
  });

  it('should prefix = with single quote', () => {
    expect(sanitizeCsvValue('=cmd|"/C calc"!A0')).toBe("'=cmd|\"/C calc\"!A0");
  });

  it('should prefix + with single quote', () => {
    expect(sanitizeCsvValue('+1+1')).toBe("'+1+1");
  });

  it('should prefix - with single quote', () => {
    expect(sanitizeCsvValue('-5')).toBe("'-5");
  });

  it('should prefix @ with single quote', () => {
    expect(sanitizeCsvValue('`@SUM`(A1:A3)')).toBe("'`@SUM`(A1:A3)");
  });

  it('should prefix | with single quote', () => {
    expect(sanitizeCsvValue('|cmd')).toBe("'|cmd");
  });

  it('should prefix % with single quote', () => {
    expect(sanitizeCsvValue('%calc')).toBe("'%calc");
  });

  it('should prefix tab with single quote', () => {
    expect(sanitizeCsvValue('\t=cmd')).toBe("'\t=cmd");
  });

  it('should prefix carriage return with single quote', () => {
    expect(sanitizeCsvValue('\r=cmd')).toBe("'\r=cmd");
  });

  it('should not modify safe values', () => {
    expect(sanitizeCsvValue('hello')).toBe('hello');
    expect(sanitizeCsvValue(123)).toBe('123');
    expect(sanitizeCsvValue(true)).toBe('true');
  });

  it('should stringify objects', () => {
    expect(sanitizeCsvValue({})).toBe('[object Object]');
  });
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/core/utils/csv.utils.ts` around lines 22 - 33, Add unit tests for the
security-sensitive sanitizeCsvValue utility to lock down its null/undefined
handling, stringification, and formula-injection prefixing behavior. Create
tests around sanitizeCsvValue in csv.utils.ts covering the documented trigger
characters (=, +, -, @, |, %, tab, carriage return) plus safe values and
object/string coercion, so regressions in the regex or quote-prefix logic are
caught early.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/app/core/utils/csv.utils.ts`:
- Around line 22-33: Add unit tests for the security-sensitive sanitizeCsvValue
utility to lock down its null/undefined handling, stringification, and
formula-injection prefixing behavior. Create tests around sanitizeCsvValue in
csv.utils.ts covering the documented trigger characters (=, +, -, @, |, %, tab,
carriage return) plus safe values and object/string coercion, so regressions in
the regex or quote-prefix logic are caught early.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5a3619bc-c0de-4c11-96ec-2a4c2734b876

📥 Commits

Reviewing files that changed from the base of the PR and between 25c8efa and 755b433.

📒 Files selected for processing (4)
  • src/app/core/utils/csv.utils.ts
  • src/app/reports/run-report/run-report.component.ts
  • src/app/reports/run-report/table-and-sms/table-and-sms.component.ts
  • src/app/system/audit-trails/audit-trails.component.ts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Kenan-Abdelbaqi-FOO
Kenan-Abdelbaqi-FOO force-pushed the WEB-1025-sanitize-csv-export-formula-injection branch from 755b433 to 89186ba Compare July 8, 2026 15:13

@IOhacker IOhacker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@IOhacker
IOhacker merged commit 025693f into openMF:dev Jul 10, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants