T09 · Insecure Skill Coding Practices
- Location
scripts/lib/common.ts:113- Finding
Spreadsheet Formula Injection in Generated CSV Reports
- Content
View full analysis
Vulnerability Details
File Location:
scripts/lib/common.ts, lines 113–125
Vulnerability Type: CSV/spreadsheet formula injection
Risk Level: MediumVulnerable Code
ts function csvEscape(value: string): string { return /[",\n\r]/.test(value) ? `"${value.replaceAll('"', '""')}"` : value; } export function toCsv(rows: Row[], headers?: string[]): string { const cols = headers ?? [...new Set(rows.flatMap((r) => Object.keys(r)))]; const lines = [cols.map(csvEscape).join(",")]; for (const row of rows) { lines.push(cols.map((c) => csvEscape(row[c] ?? "")).join(",")); } return lines.join("\n") + "\n"; }The affected serializer is reached by the following executable entrypoints:
scripts/contact-accuracy-audit.ts:379–382scripts/validate-emails.ts:383–389scripts/select-current-role.ts:405–411scripts/validate-linkedin-names.ts:326–329
The intended human-review workflow is documented in
recipes/review-and-iterate.md:25–44, which instructs users to hand over generated files or place rows in a shared spreadsheet.Technical Analysis
The shared
csvEscape()function only applies RFC 4180 quoting for commas, quotation marks, and line breaks. It does not neutralize values beginning with spreadsheet formula indicators such as=,+,-, or@.CSV quoting is not a security boundary. Spreadsheet software may still interpret a quoted value such as
"=HYPERLINK(...)"as a formula when the CSV is opened or imported.Rows can originate from Cargo workflow output, provider data, scraped public content, or imported JSON/CSV files through
readRows(). The scripts retain the original row properties using object spread and then pass those properties totoCsv(). Consequently, an attacker-controlled company, contact, or scraped field can reach the CSV without formula neutralization.Attack Path
- An attacker places a formula-prefixed string in an externally sourced field that the Skill may ingest, ...[truncated 1785 chars]
- Remediation
View remediation
Remediation Suggestions
- Make spreadsheet-safe serialization the default for all CSV exports.
- Before RFC 4180 escaping, neutralize cells whose first meaningful character is
=,+,-, or@. Also account for leading spaces, tabs, carriage returns, and other characters that spreadsheet applications may ignore before formula detection. - Prefix dangerous values with a single quotation mark or another application-compatible neutralization marker. Preserve a separate raw JSON export when exact unmodified values are required.
- Apply the protection to both headers and values because attacker-controlled JSON objects can also supply property names.
- Centralize the fix in
scripts/lib/common.tsso all four current CSV-producing scripts inherit it. - Add regression fixtures covering:
=HYPERLINK(...)+SUM(...)-1+1@SUM(...)- Formula markers preceded by spaces, tabs, or carriage returns
- Values containing commas and quotation marks in combination with formula markers
- Document that JSON is the preferred format for machine-to-machine processing and that raw, unneutralized values must not be directly imported into spreadsheets.
