fix: escape formula characters in CSAT CSV export (#15335)

## Description

The CSAT survey response CSV export builds rows with the standard
library `CSV`, writing values verbatim, including the feedback message,
which is free text submitted by end users. When a cell begins with a
formula character (`=`, `+`, `-`, `@`, and tab/CR), spreadsheet
applications interpret it as a formula on open. This switches the three
`CSV.generate_line` calls in the export template to
`CSVSafe.generate_line` (the `csv-safe` gem already used by the v2
report exports), which prefixes such fields so they are treated as text.

No new dependency, no behavioural change beyond neutralising
formula-leading cells.

Ref https://linear.app/chatwoot/issue/CW-7473

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

`bundle exec rspec
spec/controllers/api/v1/accounts/csat_survey_responses_controller_spec.rb`
— added a case asserting a formula-leading feedback value is neutralised
in the downloaded CSV.

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
This commit is contained in:
Vishnu Narayanan
2026-08-13 16:23:05 +05:30
committed by GitHub
parent 13de83d1dc
commit ba1973ea40
2 changed files with 15 additions and 3 deletions

View File

@@ -11,7 +11,7 @@
]
headers << I18n.t('reports.csat.headers.review_notes') if ChatwootApp.enterprise?
-%>
<%= CSV.generate_line(headers) -%>
<%= CSVSafe.generate_line(headers) -%>
<% @csat_survey_responses.each do |csat_response| %>
<% assigned_agent = csat_response.assigned_agent %>
<% contact = csat_response.contact %>
@@ -29,10 +29,10 @@
]
row << csat_response.csat_review_notes if ChatwootApp.enterprise?
-%>
<%= CSV.generate_line(row).html_safe -%>
<%= CSVSafe.generate_line(row).html_safe -%>
<% end %>
<%=
CSV.generate_line([
CSVSafe.generate_line([
I18n.t(
'reports.period',
since: Date.strptime(params[:since], '%s'),