fix(cli): harden CSV formula-injection escape (sec)#119
Open
sroussey wants to merge 1 commit into
Open
Conversation
… formula injection (sec) escapeCsvValue's `^[=+\-@\t\r]/` check missed three classes of bypass: leading ASCII whitespace, U+00A0 NBSP, and dangerous chars after embedded newlines inside a quoted multi-line cell (Excel/Sheets re-parse each physical line). A SEC-supplied issuer name like ` =cmd|'/c calc'!A0` passed through unprefixed. Now strips leading whitespace (incl. NBSP) before the dangerous-lead test and defuses every line after `\n`/`\r\n` independently. Regression test matrix covers all six leading chars (incl. TAB/CR), leading space, NBSP, and LF/CRLF multi-line cases.
sroussey
added a commit
that referenced
this pull request
Jun 2, 2026
Layers two refinements on top of the line-by-line CSV defusing in #119: 1. Tighten LEADING_WS so it only strips space-like characters that spreadsheets silently ignore (ASCII space, NBSP, SHY, ZWSP, ZWNJ, ZWJ, LRM, RLM, BOM). \t and \r are themselves dangerous formula leads, so we no longer strip them away before the DANGEROUS_LEAD check — otherwise "\t=cmd" or "\r=cmd" would slip through as non-dangerous after the strip. 2. Split on /(\r\n|\r|\n)/ instead of /(\r?\n)/ so that a bare CR inside a multi-line cell is also a line boundary; the line after the CR is independently defused. Excel re-parses every physical line of a quoted cell, including lines separated by lone CR. Tests cover the zero-width-prefix bypasses (ZWSP/ZWNJ/ZWJ/LRM/RLM/SHY/BOM + "=cmd"), mixed-WS bypasses ("ZWSP space =cmd"), bare-CR-followed-by-formula ("safe\r=cmd"), and a negative control to prove ZWSP-then-benign is left alone. All 44 cases in TableRenderer.test.ts pass. https://claude.ai/code/session_01Wws8oZpB5imjKL2e7DRXtc
Open
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hardens
escapeCsvValueinsrc/cli/output/TableRenderer.tsagainst three classes of CSV formula-injection bypass that the previous^[=+\-@\t\r]/check missed. Follows OWASP CSV Injection guidance for neutralizing untrusted cell content emitted bysec query --format csvbefore a spreadsheet opens it.Bypasses now closed
=cmd|'/c calc'!A0(note the leading space) was reaching the spreadsheet unprefixed and executing."safe\n=cmd"exposed the second line as a formula even when the first line was benign.Fix
DANGEROUS_LEAD(/^[=+\-@\t\r]/) +LEADING_WS(/^[\s ]+/) constants.needsFormulaPrefix(line)strips leading WS/NBSP before the dangerous-lead test.defuseLine(line)returns"'" + linewhen dangerous.escapeCsvValuenow splits the value on/(\r?\n)/, defuses each data line independently, preserves the original separators, and only then applies RFC 4180 quoting (now also triggered by\r, not just\n).escapeCsvValueis re-exported via__testingpurely for unit tests.Test plan
New
describe("escapeCsvValue")block insrc/cli/output/TableRenderer.test.ts:=cmd,+cmd,-cmd,@cmd,\tcmd,\rcmd.=cmd→' =cmd.=cmd→' =cmd.abcand123left unchanged.safe\n=cmd→"safe\n'=cmd".safe\r\n=cmd→"safe\r\n'=cmd".,wrapped in quotes."doubled inside wrapped cell.escapeCsvValue("")returns"".renderTabletests (json/csv/table format) still pass — header/data rows, comma/quote/null handling, original 5-row formula-prefix test, benign leads, table padding/truncation/pagination.Out of scope
Plans I and J (PR #118's branch) are intentionally not touched here; the PR author for that branch will apply them.
Generated by Claude Code