fix: sanitize CSV exports against formula injection (#103)
This commit is contained in:
parent
c9d212b8b8
commit
0b7811d882
@ -90,4 +90,39 @@ describe("buildBacklinksTabCsvFile", () => {
|
||||
);
|
||||
expect(file.content).toContain('"https://docs.example.com/start"');
|
||||
});
|
||||
it("sanitizes formula-like cell values to prevent CSV injection", () => {
|
||||
const file = buildBacklinksTabCsvFile({
|
||||
tab: "backlinks",
|
||||
target: "example.com",
|
||||
rows: {
|
||||
backlinks: [
|
||||
{
|
||||
domainFrom: "=cmd|' /C calc'!A0",
|
||||
urlFrom: "+https://evil.example/source",
|
||||
urlTo: "@https://evil.example/target",
|
||||
anchor: "\tformula",
|
||||
itemType: "organic",
|
||||
isDofollow: true,
|
||||
relAttributes: [],
|
||||
rank: 1,
|
||||
domainFromRank: 1,
|
||||
pageFromRank: 1,
|
||||
spamScore: 0,
|
||||
firstSeen: "2025-01-01",
|
||||
lastSeen: "2025-01-01",
|
||||
isLost: false,
|
||||
isBroken: false,
|
||||
linksCount: 1,
|
||||
},
|
||||
],
|
||||
referringDomains: [],
|
||||
topPages: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(file.content).toContain("\"'=cmd|' /C calc'!A0\"");
|
||||
expect(file.content).toContain('"\'+https://evil.example/source"');
|
||||
expect(file.content).toContain('"\'@https://evil.example/target"');
|
||||
expect(file.content).toContain('"\'\tformula"');
|
||||
});
|
||||
});
|
||||
|
||||
@ -3,7 +3,9 @@ import Papa from "papaparse";
|
||||
type CsvValue = string | number | boolean | null | undefined;
|
||||
|
||||
export function buildCsv(headers: string[], rows: CsvValue[][]): string {
|
||||
const normalizedRows = rows.map((row) => row.map((value) => value ?? ""));
|
||||
const normalizedRows = rows.map((row) =>
|
||||
row.map((value) => sanitizeCsvValue(value ?? "")),
|
||||
);
|
||||
|
||||
return Papa.unparse(
|
||||
{
|
||||
@ -17,6 +19,23 @@ export function buildCsv(headers: string[], rows: CsvValue[][]): string {
|
||||
);
|
||||
}
|
||||
|
||||
// Prevent CSV injection (formula injection) by prefixing dangerous characters
|
||||
// with a single quote. See OWASP guidance:
|
||||
// https://owasp.org/www-community/attacks/CSV_Injection
|
||||
function sanitizeCsvValue(
|
||||
value: string | number | boolean,
|
||||
): string | number | boolean {
|
||||
if (typeof value !== "string" || value.length === 0) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (["=", "+", "-", "@", "\t", "\r", "\n"].includes(value[0])) {
|
||||
return `'${value}`;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function downloadCsv(filename: string, content: string): void {
|
||||
const blob = new Blob([content], { type: "text/csv;charset=utf-8;" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user