Skip to content

fix(export): neutralize formula-like cells in CSV output - #124

Merged
HC-ONLINE merged 1 commit into
mainfrom
fix/csv-formula-injection-neutralization
Oct 17, 2026
Merged

HC-ONLINE merged 1 commit into
mainfrom
fix/csv-formula-injection-neutralization

Conversation

@HC-ONLINE

Copy link
Copy Markdown
Owner

Pull Request Template

Description

Prevent CSV formula injection (CWE-1236) by neutralizing cell values that spreadsheets may interpret as formulas before they reach the CSV writer.

  • Add
    eutralize_formula() (prefixes a single quote to string values starting with =, +, -, @, TAB or CR) and apply it centrally in CSVExporter.write_item() after flattening and before writer.writerow() — covering all export paths (write_item, export_report, export_to_csv, streaming).
  • Neutralize header cells too via _writeheader(): flattened column names can embed third-party HTTP header names from scanned targets.
  • Leave non-string values (numbers, bools, None) and JSON/JSONL outputs untouched — they are not formula vectors.
  • Add 42 regression tests: the six dangerous prefixes, legitimate values (commas, quotes, newlines, internal dangerous chars), negative numbers as numbers, nested data, hostile \Server\ header via \export_report(), streaming, dangerous column names and CSV structure integrity.

Behavior change: affected cells now carry a leading apostrophe for spreadsheet consumers; strip it to recover the raw value.

Related Issue

Fixes # — (no linked issue; tracked as an internal security-audit finding on CSV export, CWE-1236).

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

Checklist

  • I have followed the style guidelines of this project (Ruff & Pyright)
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have updated the documentation accordingly

Verification evidence

  • \uv run pytest tests/unit/export/test_csv.py\ → 77 passed
  • \uv run pytest tests/unit/export/\ → 225 passed
  • \uv run pytest tests/unit/services/\ → 377 passed
  • \uv run pytest tests/integration/api/test_api_export_download.py tests/integration/api/test_download_endpoint.py\ → 21 passed
  • \uv run pytest tests/unit/\ → 1842 passed, 3 skipped
  • \uv run ruff check\ /
    uff format --check\ / \pyright\ → clean / 0 errors
  • Mutation check: with neutralization disabled, 13 regression tests fail (tests detect the original defect)
  • In-memory E2E (5 scenarios: stream, \export_to_csv, nested hostile data, legitimate values, 100-row streaming) → 0 violations

Screenshots (if applicable)

N/A — no UI changes.

@HC-ONLINE
HC-ONLINE merged commit fa68d4d into main Oct 17, 2026
8 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.

1 participant