diff --git a/pkg/iam/log_export_csv.go b/pkg/iam/log_export_csv.go index a65762758..9e2df8f8e 100644 --- a/pkg/iam/log_export_csv.go +++ b/pkg/iam/log_export_csv.go @@ -99,7 +99,7 @@ func (s *LogExportService) streamAuditLogCSV( filter *coredata.AuditLogEntryFilter, w *safecsv.Writer, ) error { - if err := w.Write(auditLogExportCSVHeader); err != nil { + if err := w.WriteRow(auditLogExportCSVHeader...); err != nil { return fmt.Errorf("cannot write audit log CSV header: %w", err) } @@ -128,8 +128,7 @@ func (s *LogExportService) streamAuditLogCSV( } for _, entry := range entries { - row := auditLogEntryCSVRow(organizationName, entry, actorsByID[entry.ActorID]) - if err := w.Write(row); err != nil { + if err := writeAuditLogEntryCSVRow(w, organizationName, entry, actorsByID[entry.ActorID]); err != nil { return fmt.Errorf("cannot write audit log CSV row: %w", err) } } @@ -154,7 +153,7 @@ func (s *LogExportService) streamSCIMEventCSV( filter *coredata.SCIMEventFilter, w *safecsv.Writer, ) error { - if err := w.Write(scimEventExportCSVHeader); err != nil { + if err := w.WriteRow(scimEventExportCSVHeader...); err != nil { return fmt.Errorf("cannot write SCIM event CSV header: %w", err) } @@ -183,8 +182,7 @@ func (s *LogExportService) streamSCIMEventCSV( } for _, event := range events { - row := scimEventCSVRow(organizationName, event, profileLookup) - if err := w.Write(row); err != nil { + if err := writeSCIMEventCSVRow(w, organizationName, event, profileLookup); err != nil { return fmt.Errorf("cannot write SCIM event CSV row: %w", err) } } @@ -200,12 +198,13 @@ func (s *LogExportService) streamSCIMEventCSV( ) } -func auditLogEntryCSVRow( +func writeAuditLogEntryCSVRow( + w *safecsv.Writer, organizationName string, entry *coredata.AuditLogEntry, actor auditLogActorExportInfo, -) []string { - return []string{ +) error { + return w.WriteRow( organizationName, entry.ID.String(), entry.CreatedAt.Format(time.RFC3339), @@ -216,17 +215,18 @@ func auditLogEntryCSVRow( entry.Action, entry.ResourceType, entry.ResourceID.String(), - } + ) } -func scimEventCSVRow( +func writeSCIMEventCSVRow( + w *safecsv.Writer, organizationName string, event *coredata.SCIMEvent, lookup scimProfileExportLookup, -) []string { +) error { profile := lookup.forEvent(event) - return []string{ + return w.WriteRow( organizationName, event.ID.String(), event.CreatedAt.Format(time.RFC3339), @@ -238,7 +238,7 @@ func scimEventCSVRow( strconv.Itoa(event.StatusCode), stringPtrValue(event.ErrorMessage), event.IPAddress.String(), - } + ) } func loadAuditLogActorExportInfo( diff --git a/pkg/safecsv/writer.go b/pkg/safecsv/writer.go index 789ef32ff..d382861df 100644 --- a/pkg/safecsv/writer.go +++ b/pkg/safecsv/writer.go @@ -41,12 +41,16 @@ func NewWriter(w io.Writer) *Writer { return &Writer{inner: csv.NewWriter(w)} } +// Write encodes a CSV record after sanitizing every field. Prefer WriteRow for +// literal column values at call sites. func (w *Writer) Write(record []string) error { return w.inner.Write(SanitizeRecord(record)) } +// WriteRow encodes one CSV record from fields, sanitizing each value before +// encoding (spreadsheet formula injection mitigation). func (w *Writer) WriteRow(fields ...string) error { - return w.Write(fields) + return w.inner.Write(SanitizeRecord(fields)) } func (w *Writer) Flush() {