From 6d86add671b1aff7a0b6102d86dd1c814726c06a Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Sat, 18 Jan 2025 17:33:15 -0800 Subject: [PATCH] fix(contribs/gnodev/pkg/emitter): use html/template not text/template for HTML generation This change uses html/template instead of text/template for HTML generation and also locks in tests to detect such subtle regressions and thus help prevent future cross-side scripting (XSS) attacks if later the scripts evolve and take in user input. Fixes #3544 --- contribs/gnodev/pkg/emitter/middleware.go | 2 +- .../gnodev/pkg/emitter/middleware_test.go | 43 +++++++++++++++++++ .../gnodev/pkg/emitter/static/hotreload.js | 4 +- 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 contribs/gnodev/pkg/emitter/middleware_test.go diff --git a/contribs/gnodev/pkg/emitter/middleware.go b/contribs/gnodev/pkg/emitter/middleware.go index 9c53cfe158e..e4def43f919 100644 --- a/contribs/gnodev/pkg/emitter/middleware.go +++ b/contribs/gnodev/pkg/emitter/middleware.go @@ -5,10 +5,10 @@ import ( _ "embed" "encoding/json" "fmt" + "html/template" "net/http" "strings" "sync" - "text/template" "github.com/gnolang/gno/contribs/gnodev/pkg/events" ) diff --git a/contribs/gnodev/pkg/emitter/middleware_test.go b/contribs/gnodev/pkg/emitter/middleware_test.go new file mode 100644 index 00000000000..063e46d0c84 --- /dev/null +++ b/contribs/gnodev/pkg/emitter/middleware_test.go @@ -0,0 +1,43 @@ +package emitter + +import ( + "fmt" + "net/http" + "net/http/httptest" + "regexp" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMiddlewareUsesHTMLTemplate(t *testing.T) { + tests := []struct { + name string + remote string + want string + }{ + {"normal remote", "localhost:9999", "const ws = new WebSocket('ws://localhost:9999');"}, + {"xss'd remote", `localhost:9999');alert('pwned`, "const ws = new WebSocket('ws://localhost:9999');alert('pwned');"}, + } + + // As the code revolves, add more search patterns here. + var reWebsocket = regexp.MustCompile("const ws = new WebSocket[^\n]+") + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rec := httptest.NewRecorder() + mdw := NewMiddleware(tt.remote, http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Content-Type", "text/html") + fmt.Fprintf(rw, "") + })) + rec.Header().Set("Content-Type", "text/html") + req := httptest.NewRequest("GET", "https://gno.land/example", nil) + mdw.ServeHTTP(rec, req) + + targets := reWebsocket.FindAllString(rec.Body.String(), -1) + require.True(t, len(targets) > 0) + body := targets[0] + require.Equal(t, body, tt.want) + }) + } +} diff --git a/contribs/gnodev/pkg/emitter/static/hotreload.js b/contribs/gnodev/pkg/emitter/static/hotreload.js index aabad4f341c..28e47c1ea15 100644 --- a/contribs/gnodev/pkg/emitter/static/hotreload.js +++ b/contribs/gnodev/pkg/emitter/static/hotreload.js @@ -1,6 +1,8 @@ (function() { // Define the events that will trigger a page reload - const eventsReload = {{ .ReloadEvents | json }}; + const eventsReload = [ + {{range .ReloadEvents}}'{{.}}',{{end}} + ]; // Establish the WebSocket connection to the event server const ws = new WebSocket('ws://{{- .Remote -}}');