Skip to content

fix: add CSV field sanitization, injection detection, and row count l…#145

Open
kimanicode wants to merge 1 commit into
ChainForgee:mainfrom
kimanicode:fix/csv-input-sanitization
Open

fix: add CSV field sanitization, injection detection, and row count l…#145
kimanicode wants to merge 1 commit into
ChainForgee:mainfrom
kimanicode:fix/csv-input-sanitization

Conversation

@kimanicode

Copy link
Copy Markdown
Contributor

Summary

Closes #121

This PR adds a proper validation and sanitization layer to the CSV import flow in
ImportRecipientsWizard. Previously, papaparse parsed CSV data and passed it
directly to the UI and backend with no defense against malformed rows, excessively
long field values, or formula injection characters.


Changes

app/frontend/src/lib/csv-validation.ts

  • Added CSV_MAX_ROWS = 10_000 constant — enforces a hard row cap on the client
  • Added FIELD_MAX_LENGTHS — per-field character limits (e.g. wallet: 64, name: 120, phone: 20)
  • Added INJECTION_PATTERN regex — detects formula injection triggers (=, +, -, @) and control/null characters
  • Added sanitizeField() — strips control characters and truncates values to their field limit before they ever reach the preview table
  • Added validateField() — returns ValidationMessage[] for length overflow and injection pattern matches
  • Updated parseRecipientsCsv() — rejects files exceeding 10,000 rows with a clear user-facing error; sanitizes every field value before building the rows array
  • Updated buildLocalValidation() — runs validateField() across all fields in every row after existing presence checks

app/frontend/src/lib/mock-api/handlers.ts

  • Added matching constants (MAX_ROWS, MAX_FIELD_LENGTH, DEFAULT_MAX, INJECTION_RE) inside recipientsImportValidateHandler
  • Row count guard returns HTTP 422 with a descriptive message if the CSV exceeds 10,000 rows
  • Per-column validation loop inside dataLines.map() — checks every header's value for length overflow and injection characters, appending errors to the row's messages array

Security Issues Addressed

Threat Defense
Formula injection (=SUM(...), =cmd|...) INJECTION_PATTERN rejects values starting with =, +, -, @
Control / null byte injection Regex strips \x00–\x08, \x0B, \x0C, \x0E–\x1F, \x7F
Excessively long field values Per-field FIELD_MAX_LENGTHS with 255-char default
Oversized CSV files (DoS / memory) 10,000 row hard limit on both client and server
Malformed rows reaching the UI Sanitization runs in parseRecipientsCsv before preview renders

Testing

  • tsc --noEmit passes with no type errors introduced by these changes
  • eslint passes with no new lint errors
  • Validation errors surface correctly in Step 3 with field-level messages
  • Files exceeding 10,000 rows are rejected at parse time with a clear error before Step 2

Checklist

  • CSV field validation added (length limits + injection detection)
  • Row count limit of 10,000 enforced on client and mock server
  • Malformed/oversized/injected fields rejected with clear error messages
  • Sanitization runs before data reaches the preview table or is stored
  • TypeScript compiles clean
  • Lint passes clean
  • Commit references issue [MEDIUM] No input sanitization for CSV imports in ImportRecipientsWizard #121

…imit

- Add CSV_MAX_ROWS (10,000) limit enforced on both client and mock server
- Add FIELD_MAX_LENGTHS per-field character limits
- Add INJECTION_PATTERN to detect formula injection (=, +, -, @) and control chars
- Add sanitizeField() to strip control characters and truncate before preview
- Add validateField() to return ValidationMessage[] for length/injection violations
- Row count guard in parseRecipientsCsv rejects oversized files with clear error
- Per-field validation loop added in buildLocalValidation
- Mock handler returns HTTP 422 for oversized CSVs and flags bad fields per-column

Closes ChainForgee#121

@kilodesodiq-arch kilodesodiq-arch 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.

Direction is right @kimanicode, but two blockers before this lands:

  1. No unit tests for the sanitization logic. For a security fix this is a hard requirement — please add tests that prove the injection patterns (=, +, -, @, control chars) are rejected and that legitimate values (incl. leading + phone numbers, plain names, plain wallets) still pass.
  2. The injection regex /^[=+\-@\t\r]/... will reject legitimate phone numbers like +15550100 even after trim. International numbers routinely start with +. Either narrow the leading-char rule to columns that won't see phones, or only treat the leading @ as risky and let +/- through.

Happy to merge once both land.

Copy link
Copy Markdown
Contributor

Thanks @kimanicode for tackling #121. The sanitization logic looks correct, but I don't see any CI checks configured on this PR, so we can't verify it doesn't break the CSV import flow end-to-end.

Could you wire up the relevant CI job for the backend (or at minimum include a regression test for the sanitization path) so we can land this safely? Holding the merge until then.

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.

[MEDIUM] No input sanitization for CSV imports in ImportRecipientsWizard

2 participants