Skip to content

Commit 6c0fe41

Browse files
committed
🔒 Added escaping to member export CSV fields
fix https://linear.app/tryghost/issue/ENG-805/ refs https://owasp.org/www-community/attacks/CSV_Injection - it's possible for certain fields in a member CSV export to be executed by software that opens the CSVs - we can protect against this for the user by escaping any forumulae in the CSV fields - papaparse provides this option natively, so it's just a case of providing the field to the unparse method - credits to Harvey Spec (phulelouch) for reporting
1 parent b927895 commit 6c0fe41

File tree

2 files changed

+29
-0
lines changed

2 files changed

+29
-0
lines changed

‎packages/members-csv/lib/unparse.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const unparse = (rows, columns = DEFAULT_COLUMNS.slice()) => {
6161
});
6262

6363
return papaparse.unparse(mappedRows, {
64+
escapeFormulae: true,
6465
columns
6566
});
6667
};

‎packages/members-csv/test/unparse.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,32 @@ [email protected],"banana, avocado"`;
103103
const expected = `email,tiers\r\[email protected],Bronze Level`;
104104
assert.equal(result, expected);
105105
});
106+
107+
it('escapes fields starting with CSV injection characters', async function () {
108+
const json = [{
109+
110+
name: '=1+2',
111+
note: 'Early supporter'
112+
}];
113+
114+
const result = unparse(json);
115+
assert.ok(result);
116+
117+
const expected = `id,email,name,note,subscribed_to_emails,complimentary_plan,stripe_customer_id,created_at,deleted_at,labels,tiers\r\n,[email protected],"'=1+2",Early supporter,,,,,,,`;
118+
assert.equal(result, expected);
119+
});
120+
121+
it('escapes fields with CSV injection characters and quotes', async function () {
122+
const json = [{
123+
124+
name: `=1+2'" `,
125+
note: 'Early supporter'
126+
}];
127+
128+
const result = unparse(json);
129+
assert.ok(result);
130+
131+
const expected = `id,email,name,note,subscribed_to_emails,complimentary_plan,stripe_customer_id,created_at,deleted_at,labels,tiers\r\n,[email protected],"'=1+2'"" ",Early supporter,,,,,,,`;
132+
assert.equal(result, expected);
133+
});
106134
});

0 commit comments

Comments
 (0)