f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
106 lines
2.5 KiB
Go
106 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestSanitizeTerminal(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
}{
|
|
{
|
|
name: "plain text",
|
|
in: "hello world",
|
|
want: "hello world",
|
|
},
|
|
{
|
|
name: "newline and tab preserved; CR stripped",
|
|
in: "line1\nline2\ttab\rcr",
|
|
want: "line1\nline2\ttabcr",
|
|
},
|
|
{
|
|
// Bare \r on a TTY returns the cursor to column 0
|
|
// and lets later text overwrite earlier output —
|
|
// a spoofing vector even without ANSI escapes.
|
|
name: "strips bare CR to block overwrite attacks",
|
|
in: "safe output\rEVIL",
|
|
want: "safe outputEVIL",
|
|
},
|
|
{
|
|
// CRLF from Windows-style sources collapses to LF;
|
|
// terminals treat \n alone as a newline so display
|
|
// still looks right.
|
|
name: "CRLF collapses to LF",
|
|
in: "line1\r\nline2",
|
|
want: "line1\nline2",
|
|
},
|
|
{
|
|
name: "strips ESC (C0)",
|
|
in: "safe\x1bdangerous",
|
|
want: "safedangerous",
|
|
},
|
|
{
|
|
name: "strips ANSI CSI (cursor up)",
|
|
in: "before\x1b[1Aoverwritten",
|
|
want: "before[1Aoverwritten",
|
|
},
|
|
{
|
|
// ESC (0x1b) is dropped, so the OSC introducer is
|
|
// broken. The literal "]8;;…" bytes remain but are
|
|
// no longer interpreted as an escape sequence.
|
|
name: "strips OSC 8 hyperlink escape",
|
|
in: "click \x1b]8;;https://evil.example\x1b\\me\x1b]8;;\x1b\\",
|
|
want: "click ]8;;https://evil.example\\me]8;;\\",
|
|
},
|
|
{
|
|
// BEL (0x07) is a C0 control and also dropped.
|
|
name: "strips OSC 52 clipboard escape",
|
|
in: "benign \x1b]52;c;ZXZpbA==\x07 end",
|
|
want: "benign ]52;c;ZXZpbA== end",
|
|
},
|
|
{
|
|
name: "strips title-set OSC 2",
|
|
in: "\x1b]2;rm -rf /\x07after",
|
|
want: "]2;rm -rf /after",
|
|
},
|
|
{
|
|
name: "strips NUL and DEL",
|
|
in: "a\x00b\x7fc",
|
|
want: "abc",
|
|
},
|
|
{
|
|
name: "strips raw C1 bytes",
|
|
in: "x\x80y\x9fz",
|
|
want: "xyz",
|
|
},
|
|
{
|
|
name: "preserves multibyte UTF-8",
|
|
in: "héllo 日本語 😀",
|
|
want: "héllo 日本語 😀",
|
|
},
|
|
{
|
|
name: "empty string",
|
|
in: "",
|
|
want: "",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := sanitizeTerminal(tc.in)
|
|
assert.Equal(t, tc.want, got,
|
|
"sanitizeTerminal(%q)", tc.in)
|
|
// Post-condition: no ESC byte or NUL in output.
|
|
assert.False(t, strings.ContainsRune(got, 0x1b),
|
|
"output still contains ESC: %q", got)
|
|
assert.False(t, strings.ContainsRune(got, 0),
|
|
"output still contains NUL: %q", got)
|
|
})
|
|
}
|
|
}
|