mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: sanitize workspace agent logs before insert (#24028)
Workspace agent logs could still fail after the earlier invalid UTF-8 fix because NUL bytes are valid Go/protobuf strings but are rejected by Postgres text columns. The legacy HTTP log upload path also bypassed the old sanitization entirely, and both server insert paths computed logs_length from the unsanitized input. Add a shared log-output sanitizer in agentsdk, use it in the protobuf conversion path and both server-side insert paths, and compute OutputLength from the sanitized string so overflow accounting matches what is actually stored. This keeps the old invalid UTF-8 behavior while also handling embedded NUL bytes consistently across DRPC and HTTP log ingestion. Refs [#23292 ](https://github.com/coder/coder/issues/23292) Refs [#13433 ](https://github.com/coder/coder/issues/13433)
This commit is contained in:
@@ -376,7 +376,7 @@ func ProtoFromLog(log Log) (*proto.Log, error) {
|
||||
}
|
||||
return &proto.Log{
|
||||
CreatedAt: timestamppb.New(log.CreatedAt),
|
||||
Output: strings.ToValidUTF8(log.Output, "❌"),
|
||||
Output: SanitizeLogOutput(log.Output),
|
||||
Level: proto.Log_Level(lvl),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ func TestLogSender_SkipHugeLog(t *testing.T) {
|
||||
require.ErrorIs(t, err, context.Canceled)
|
||||
}
|
||||
|
||||
func TestLogSender_InvalidUTF8(t *testing.T) {
|
||||
func TestLogSender_SanitizeOutput(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCtx := testutil.Context(t, testutil.WaitShort)
|
||||
ctx, cancel := context.WithCancel(testCtx)
|
||||
@@ -243,7 +243,7 @@ func TestLogSender_InvalidUTF8(t *testing.T) {
|
||||
uut.Enqueue(ls1,
|
||||
Log{
|
||||
CreatedAt: t0,
|
||||
Output: "test log 0, src 1\xc3\x28",
|
||||
Output: "test log 0, src 1\x00\xc3\x28",
|
||||
Level: codersdk.LogLevelInfo,
|
||||
},
|
||||
Log{
|
||||
@@ -260,10 +260,10 @@ func TestLogSender_InvalidUTF8(t *testing.T) {
|
||||
|
||||
req := testutil.TryReceive(ctx, t, fDest.reqs)
|
||||
require.NotNil(t, req)
|
||||
require.Len(t, req.Logs, 2, "it should sanitize invalid UTF-8, but still send")
|
||||
// the 0xc3, 0x28 is an invalid 2-byte sequence in UTF-8. The sanitizer replaces 0xc3 with ❌, and then
|
||||
// interprets 0x28 as a 1-byte sequence "("
|
||||
require.Equal(t, "test log 0, src 1❌(", req.Logs[0].GetOutput())
|
||||
require.Len(t, req.Logs, 2, "it should sanitize invalid output, but still send")
|
||||
// The sanitizer replaces the NUL byte and invalid UTF-8 with ❌ while
|
||||
// preserving the valid "(" byte that follows 0xc3.
|
||||
require.Equal(t, "test log 0, src 1❌❌(", req.Logs[0].GetOutput())
|
||||
require.Equal(t, proto.Log_INFO, req.Logs[0].GetLevel())
|
||||
require.Equal(t, "test log 1, src 1", req.Logs[1].GetOutput())
|
||||
require.Equal(t, proto.Log_INFO, req.Logs[1].GetLevel())
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package agentsdk
|
||||
|
||||
import "strings"
|
||||
|
||||
// SanitizeLogOutput replaces invalid UTF-8 and NUL characters in log output.
|
||||
// Invalid UTF-8 cannot be transported in protobuf string fields, and PostgreSQL
|
||||
// rejects NUL bytes in text columns.
|
||||
func SanitizeLogOutput(s string) string {
|
||||
s = strings.ToValidUTF8(s, "❌")
|
||||
return strings.ReplaceAll(s, "\x00", "❌")
|
||||
}
|
||||
@@ -17,6 +17,54 @@ import (
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
func TestSanitizeLogOutput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
in: "hello world",
|
||||
want: "hello world",
|
||||
},
|
||||
{
|
||||
name: "invalid utf8",
|
||||
in: "test log\xc3\x28",
|
||||
want: "test log❌(",
|
||||
},
|
||||
{
|
||||
name: "nul byte",
|
||||
in: "before\x00after",
|
||||
want: "before❌after",
|
||||
},
|
||||
{
|
||||
name: "invalid utf8 and nul byte",
|
||||
in: "before\x00middle\xc3\x28after",
|
||||
want: "before❌middle❌(after",
|
||||
},
|
||||
{
|
||||
name: "nul byte at edges",
|
||||
in: "\x00middle\x00",
|
||||
want: "❌middle❌",
|
||||
},
|
||||
{
|
||||
name: "invalid utf8 at edges",
|
||||
in: "\xc3middle\xc3",
|
||||
want: "❌middle❌",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Equal(t, tt.want, agentsdk.SanitizeLogOutput(tt.in))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartupLogsWriter_Write(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user