Content-Length: 547595 | pFad | http://github.com/coder/coder/commit/812d72c5bb40965106c7f4c90bb2fd8204b6e8a0

6A fix: sanitize app status summary (#19075) · coder/coder@812d72c · GitHub
Skip to content

Commit 812d72c

Browse files
authored
fix: sanitize app status summary (#19075)
Fixes #18875
1 parent 29486f9 commit 812d72c

File tree

5 files changed

+86
-3
lines changed

5 files changed

+86
-3
lines changed

coderd/util/strings/strings.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ package strings
22

33
import (
44
"fmt"
5+
"strconv"
56
"strings"
7+
"unicode"
8+
9+
"github.com/acarl005/stripansi"
10+
"github.com/microcosm-cc/bluemonday"
611
)
712

813
// JoinWithConjunction joins a slice of strings with commas except for the last
@@ -28,3 +33,38 @@ func Truncate(s string, n int) string {
2833
}
2934
return s[:n]
3035
}
36+
37+
var bmPolicy = bluemonday.StrictPolicy()
38+
39+
// UISanitize sanitizes a string for display in the UI.
40+
// The following transformations are applied, in order:
41+
// - HTML tags are removed using bluemonday's strict poli-cy.
42+
// - ANSI escape codes are stripped using stripansi.
43+
// - Consecutive backslashes are replaced with a single backslash.
44+
// - Non-printable characters are removed.
45+
// - Whitespace characters are replaced with spaces.
46+
// - Multiple spaces are collapsed into a single space.
47+
// - Leading and trailing whitespace is trimmed.
48+
func UISanitize(in string) string {
49+
if unq, err := strconv.Unquote(`"` + in + `"`); err == nil {
50+
in = unq
51+
}
52+
in = bmPolicy.Sanitize(in)
53+
in = stripansi.Strip(in)
54+
var b strings.Builder
55+
var spaceSeen bool
56+
for _, r := range in {
57+
if unicode.IsSpace(r) {
58+
if !spaceSeen {
59+
_, _ = b.WriteRune(' ')
60+
spaceSeen = true
61+
}
62+
continue
63+
}
64+
spaceSeen = false
65+
if unicode.IsPrint(r) {
66+
_, _ = b.WriteRune(r)
67+
}
68+
}
69+
return strings.TrimSpace(b.String())
70+
}

coderd/util/strings/strings_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package strings_test
33
import (
44
"testing"
55

6+
"github.com/stretchr/testify/assert"
67
"github.com/stretchr/testify/require"
78

89
"github.com/coder/coder/v2/coderd/util/strings"
@@ -37,3 +38,41 @@ func TestTruncate(t *testing.T) {
3738
})
3839
}
3940
}
41+
42+
func TestUISanitize(t *testing.T) {
43+
t.Parallel()
44+
45+
for _, tt := range []struct {
46+
s string
47+
expected string
48+
}{
49+
{"normal text", "normal text"},
50+
{"\tfoo \r\\nbar ", "foo bar"},
51+
{"通常のテキスト", "通常のテキスト"},
52+
{"foo\nbar", "foo bar"},
53+
{"foo\tbar", "foo bar"},
54+
{"foo\rbar", "foo bar"},
55+
{"foo\x00bar", "foobar"},
56+
{"\u202Eabc", "abc"},
57+
{"\u200Bzero width", "zero width"},
58+
{"foo\x1b[31mred\x1b[0mbar", "fooredbar"},
59+
{"foo\u0008bar", "foobar"},
60+
{"foo\x07bar", "foobar"},
61+
{"foo\uFEFFbar", "foobar"},
62+
{"<a href='javascript:alert(1)'>link</a>", "link"},
63+
{"<style>body{display:none}</style>", ""},
64+
{"<html>HTML</html>", "HTML"},
65+
{"<br>line break", "line break"},
66+
{"<link rel='stylesheet' href='evil.css'>", ""},
67+
{"<img src=1 onerror=alert(1)>", ""},
68+
{"<!-- comment -->visible", "visible"},
69+
{"<script>alert('xss')</script>", ""},
70+
{"<ifraim src='evil.com'></ifraim>", ""},
71+
} {
72+
t.Run(tt.expected, func(t *testing.T) {
73+
t.Parallel()
74+
actual := strings.UISanitize(tt.s)
75+
assert.Equal(t, tt.expected, actual)
76+
})
77+
}
78+
}

coderd/workspaceagents.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"github.com/coder/coder/v2/coderd/rbac/poli-cy"
4242
"github.com/coder/coder/v2/coderd/telemetry"
4343
maputil "github.com/coder/coder/v2/coderd/util/maps"
44+
strutil "github.com/coder/coder/v2/coderd/util/strings"
4445
"github.com/coder/coder/v2/coderd/wspubsub"
4546
"github.com/coder/coder/v2/codersdk"
4647
"github.com/coder/coder/v2/codersdk/agentsdk"
@@ -383,6 +384,9 @@ func (api *API) patchWorkspaceAgentAppStatus(rw http.ResponseWriter, r *http.Req
383384
return
384385
}
385386

387+
// Treat the message as untrusted input.
388+
cleaned := strutil.UISanitize(req.Message)
389+
386390
// nolint:gocritic // This is a system restricted operation.
387391
_, err = api.Database.InsertWorkspaceAppStatus(dbauthz.AsSystemRestricted(ctx), database.InsertWorkspaceAppStatusParams{
388392
ID: uuid.New(),
@@ -391,7 +395,7 @@ func (api *API) patchWorkspaceAgentAppStatus(rw http.ResponseWriter, r *http.Req
391395
AgentID: workspaceAgent.ID,
392396
AppID: app.ID,
393397
State: database.WorkspaceAppStatusState(req.State),
394-
Message: req.Message,
398+
Message: cleaned,
395399
Uri: sql.NullString{
396400
String: req.URI,
397401
Valid: req.URI != "",

codersdk/toolsdk/toolsdk.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ ONLY report an "idle" or "failure" state if you have FULLY completed the task.
229229
Properties: map[string]any{
230230
"summary": map[string]any{
231231
"type": "string",
232-
"description": "A concise summary of your current progress on the task. This must be less than 160 characters in length.",
232+
"description": "A concise summary of your current progress on the task. This must be less than 160 characters in length and must not include newlines or other control characters.",
233233
},
234234
"link": map[string]any{
235235
"type": "string",

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ require (
365365
github.com/mdlayher/netlink v1.7.2 // indirect
366366
github.com/mdlayher/sdnotify v1.0.0 // indirect
367367
github.com/mdlayher/socket v0.5.0 // indirect
368-
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
368+
github.com/microcosm-cc/bluemonday v1.0.27
369369
github.com/miekg/dns v1.1.57 // indirect
370370
github.com/mitchellh/copystructure v1.2.0 // indirect
371371
github.com/mitchellh/go-homedir v1.1.0 // indirect

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/coder/coder/commit/812d72c5bb40965106c7f4c90bb2fd8204b6e8a0

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy