Merge commit from fork

Carlos Alexandro Becker and Tomer Fichman created

* sec: escape ansi sequences on user input

fixes HSA-fv2r-r8mp-pg48

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* Apply suggestion from @Tomer-PL

Co-authored-by: Tomer Fichman <tomer@irregular.com>

* chore: fmt

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: Tomer Fichman <tomer@irregular.com>

Change summary

pkg/backend/access_token.go | 2 ++
pkg/backend/repo.go         | 2 ++
pkg/backend/user.go         | 1 +
pkg/backend/webhooks.go     | 2 ++
pkg/ssh/cmd/commit.go       | 5 +++--
pkg/ssh/cmd/webhooks.go     | 6 ++++--
pkg/utils/utils.go          | 8 ++++++++
7 files changed, 22 insertions(+), 4 deletions(-)

Detailed changes

pkg/backend/access_token.go 🔗

@@ -7,12 +7,14 @@ import (
 
 	"github.com/charmbracelet/soft-serve/pkg/db"
 	"github.com/charmbracelet/soft-serve/pkg/proto"
+	"github.com/charmbracelet/soft-serve/pkg/utils"
 )
 
 // CreateAccessToken creates an access token for user.
 func (b *Backend) CreateAccessToken(ctx context.Context, user proto.User, name string, expiresAt time.Time) (string, error) {
 	token := GenerateToken()
 	tokenHash := HashToken(token)
+	name = utils.Sanitize(name)
 
 	if err := b.db.TransactionContext(ctx, func(tx *db.Tx) error {
 		_, err := b.store.CreateAccessToken(ctx, tx, name, user.ID(), tokenHash, expiresAt)

pkg/backend/repo.go 🔗

@@ -544,6 +544,7 @@ func (d *Backend) SetHidden(ctx context.Context, name string, hidden bool) error
 // It implements backend.Backend.
 func (d *Backend) SetDescription(ctx context.Context, name string, desc string) error {
 	name = utils.SanitizeRepo(name)
+	desc = utils.Sanitize(desc)
 	rp := filepath.Join(d.repoPath(name))
 
 	// Delete cache
@@ -617,6 +618,7 @@ func (d *Backend) SetPrivate(ctx context.Context, name string, private bool) err
 // It implements backend.Backend.
 func (d *Backend) SetProjectName(ctx context.Context, repo string, name string) error {
 	repo = utils.SanitizeRepo(repo)
+	name = utils.Sanitize(name)
 
 	// Delete cache
 	d.cache.Delete(repo)

pkg/backend/user.go 🔗

@@ -279,6 +279,7 @@ func (d *Backend) AddPublicKey(ctx context.Context, username string, pk ssh.Publ
 //
 // It implements backend.Backend.
 func (d *Backend) CreateUser(ctx context.Context, username string, opts proto.UserOptions) (proto.User, error) {
+	username = utils.Sanitize(username)
 	username = strings.ToLower(username)
 	if err := utils.ValidateUsername(username); err != nil {
 		return nil, err

pkg/backend/webhooks.go 🔗

@@ -9,6 +9,7 @@ import (
 	"github.com/charmbracelet/soft-serve/pkg/db/models"
 	"github.com/charmbracelet/soft-serve/pkg/proto"
 	"github.com/charmbracelet/soft-serve/pkg/store"
+	"github.com/charmbracelet/soft-serve/pkg/utils"
 	"github.com/charmbracelet/soft-serve/pkg/webhook"
 	"github.com/google/uuid"
 )
@@ -17,6 +18,7 @@ import (
 func (b *Backend) CreateWebhook(ctx context.Context, repo proto.Repository, url string, contentType webhook.ContentType, secret string, events []webhook.Event, active bool) error {
 	dbx := db.FromContext(ctx)
 	datastore := store.FromContext(ctx)
+	url = utils.Sanitize(url)
 
 	return dbx.TransactionContext(ctx, func(tx *db.Tx) error {
 		lastID, err := datastore.CreateWebhook(ctx, tx, repo.ID(), url, secret, int(contentType), active)

pkg/ssh/cmd/commit.go 🔗

@@ -10,6 +10,7 @@ import (
 	"github.com/charmbracelet/soft-serve/pkg/backend"
 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
 	"github.com/charmbracelet/soft-serve/pkg/ui/styles"
+	"github.com/charmbracelet/soft-serve/pkg/utils"
 	"github.com/spf13/cobra"
 )
 
@@ -59,9 +60,9 @@ func commitCommand() *cobra.Command {
 
 			s := strings.Builder{}
 			commitLine := "commit " + commitSHA
-			authorLine := "Author: " + commit.Author.Name
+			authorLine := "Author: " + utils.Sanitize(commit.Author.Name)
 			dateLine := "Date:   " + commit.Committer.When.UTC().Format(time.UnixDate)
-			msgLine := strings.ReplaceAll(commit.Message, "\r\n", "\n")
+			msgLine := strings.ReplaceAll(utils.Sanitize(commit.Message), "\r\n", "\n")
 			statsLine := renderStats(diff, commonStyle, color)
 			diffLine := renderDiff(patch, color)
 

pkg/ssh/cmd/webhooks.go 🔗

@@ -7,6 +7,7 @@ import (
 
 	"github.com/charmbracelet/lipgloss/v2/table"
 	"github.com/charmbracelet/soft-serve/pkg/backend"
+	"github.com/charmbracelet/soft-serve/pkg/utils"
 	"github.com/charmbracelet/soft-serve/pkg/webhook"
 	"github.com/dustin/go-humanize"
 	"github.com/google/uuid"
@@ -69,7 +70,7 @@ func webhookListCommand() *cobra.Command {
 
 				table = table.Row(
 					strconv.FormatInt(h.ID, 10),
-					h.URL,
+					utils.Sanitize(h.URL),
 					strings.Join(events, ","),
 					strconv.FormatBool(h.Active),
 					humanize.Time(h.CreatedAt),
@@ -122,7 +123,8 @@ func webhookCreateCommand() *cobra.Command {
 				return webhook.ErrInvalidContentType
 			}
 
-			return be.CreateWebhook(ctx, repo, strings.TrimSpace(args[1]), ct, secret, evs, active)
+			url := utils.Sanitize(args[1])
+			return be.CreateWebhook(ctx, repo, strings.TrimSpace(url), ct, secret, evs, active)
 		},
 	}
 

pkg/utils/utils.go 🔗

@@ -5,10 +5,13 @@ import (
 	"path"
 	"strings"
 	"unicode"
+
+	"github.com/charmbracelet/x/ansi"
 )
 
 // SanitizeRepo returns a sanitized version of the given repository name.
 func SanitizeRepo(repo string) string {
+	repo = Sanitize(repo)
 	// We need to use an absolute path for the path to be cleaned correctly.
 	repo = strings.TrimPrefix(repo, "/")
 	repo = "/" + repo
@@ -20,6 +23,11 @@ func SanitizeRepo(repo string) string {
 	return repo[1:]
 }
 
+// Sanitize strips ANSI escape codes from the given string.
+func Sanitize(s string) string {
+	return ansi.Strip(s)
+}
+
 // ValidateUsername returns an error if any of the given usernames are invalid.
 func ValidateUsername(username string) error {
 	if username == "" {