remote_preview_hardening_test.go RawHistoryFolder
1package ports
2
3import (
4 "bytes"
5 "math"
6 "strings"
7 "testing"
8
9 renderer "github.com/bnema/vev-vt"
10 "github.com/bnema/vev/internal/domain"
11)
12
13const (
14 remotePreviewGoldenStatusOffset = 2
15 remotePreviewGoldenCellsOffset = 2 + 1 + 16 + 2 + len("tab-1") + 8 + 2 + 2 + 4
16 remotePreviewGoldenCellFlagsOffset = remotePreviewGoldenCellsOffset + 4
17 remotePreviewGoldenStyleFlagsOffset = remotePreviewGoldenCellsOffset + 5
18 remotePreviewGoldenStyleAttrsOffset = remotePreviewGoldenCellsOffset + 6
19 remotePreviewGoldenSecondCellOffset = remotePreviewGoldenCellsOffset + previewCellWireSize
20 remotePreviewGoldenUnderlineStyleOffset = remotePreviewGoldenSecondCellOffset + 12
21)
22
23func remotePreviewGoldenPayload() []byte {
24 return []byte{
25 0x00, 0x01, 0x00,
26 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
27 0x00, 0x05, 't', 'a', 'b', '-', '1',
28 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
29 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
30 0x00, 0x00, 0x00, 0x6f, 0x00,
31 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
32 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
33 0x00, 0x00, 0x00, 0x6b, 0x00,
34 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
35 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
36 }
37}
38
39func TestRemotePreviewCodecGoldenRoundTrip(t *testing.T) {
40 golden := remotePreviewGoldenPayload()
41 got, err := UnmarshalRemotePreview(golden)
42 if err != nil {
43 t.Fatalf("UnmarshalRemotePreview() error = %v", err)
44 }
45 if got.Version != RemotePreviewSchemaVersion || got.Status != RemotePreviewOK {
46 t.Fatalf("header = (%d, %d), want (%d, %d)", got.Version, got.Status, RemotePreviewSchemaVersion, RemotePreviewOK)
47 }
48 var wantLifecycle domain.SessionLifecycleID
49 for i := range wantLifecycle {
50 wantLifecycle[i] = byte(i + 1)
51 }
52 if got.LifecycleID != wantLifecycle || got.TabID != "tab-1" || got.Revision != 1 || got.Width != 2 || got.Height != 1 {
53 t.Fatalf("metadata = %#v, want lifecycle/tab/revision/size from golden", got)
54 }
55 if len(got.Cells) != 2 || got.Cells[0].Rune != 'o' || got.Cells[1].Rune != 'k' {
56 t.Fatalf("cells = %#v, want [o k]", got.Cells)
57 }
58 if !bytes.Equal(MarshalRemotePreview(got), golden) {
59 t.Fatalf("marshal changed golden payload: %x", MarshalRemotePreview(got))
60 }
61}
62
63func TestRemotePreviewCodecRejectsInvalidStatusFlagsAndRanges(t *testing.T) {
64 golden := remotePreviewGoldenPayload()
65 tests := []struct {
66 name string
67 mutate func([]byte)
68 }{
69 {name: "status", mutate: func(b []byte) { b[remotePreviewGoldenStatusOffset] = 0xff }},
70 {name: "cell flags", mutate: func(b []byte) { b[remotePreviewGoldenCellFlagsOffset] = 0x02 }},
71 {name: "style flags", mutate: func(b []byte) { b[remotePreviewGoldenStyleFlagsOffset] = 0x80 }},
72 {name: "style attrs", mutate: func(b []byte) { b[remotePreviewGoldenStyleAttrsOffset] = 0x80 }},
73 {name: "underline range", mutate: func(b []byte) { b[remotePreviewGoldenUnderlineStyleOffset] = byte(renderer.UnderlineDashed + 1) }},
74 }
75 for _, test := range tests {
76 t.Run(test.name, func(t *testing.T) {
77 payload := append([]byte(nil), golden...)
78 test.mutate(payload)
79 if _, err := UnmarshalRemotePreview(payload); err == nil {
80 t.Fatal("malformed payload decoded successfully")
81 }
82 })
83 }
84}
85
86func TestRemotePreviewCodecRejectsWideCellBoundaryAndInvalidIDs(t *testing.T) {
87 var lifecycle domain.SessionLifecycleID
88 lifecycle[0] = 1
89 valid := RemotePreview{
90 Version: RemotePreviewSchemaVersion, Status: RemotePreviewOK,
91 LifecycleID: lifecycle, TabID: "tab-1", Revision: 1, Width: 2, Height: 1,
92 Cells: []renderer.Cell{{Rune: '界'}, {Continuation: true}},
93 }
94 if MarshalRemotePreview(valid) == nil {
95 t.Fatal("valid wide-cell preview did not marshal")
96 }
97 for _, malformed := range []RemotePreview{
98 {Version: RemotePreviewSchemaVersion, Status: RemotePreviewOK, LifecycleID: lifecycle, TabID: "tab-1", Revision: 1, Width: 1, Height: 1, Cells: []renderer.Cell{{Rune: '界'}}},
99 {Version: RemotePreviewSchemaVersion, Status: RemotePreviewOK, LifecycleID: lifecycle, TabID: "tab-1", Revision: 1, Width: 2, Height: 1, Cells: []renderer.Cell{{Continuation: true}, {Rune: 'x'}}},
100 {Version: RemotePreviewSchemaVersion, Status: RemotePreviewOK, LifecycleID: lifecycle, TabID: "tab-1", Revision: 1, Width: 2, Height: 1, Cells: []renderer.Cell{{Rune: '界'}, {Continuation: true, Rune: 'x'}}},
101 {Version: RemotePreviewSchemaVersion, Status: RemotePreviewOK, LifecycleID: lifecycle, TabID: "tab-1", Revision: 1, Width: 2, Height: 2, Cells: []renderer.Cell{{Rune: 'x'}, {Rune: '界'}, {Continuation: true}, {Rune: 'y'}}},
102 } {
103 if MarshalRemotePreview(malformed) != nil {
104 t.Fatalf("malformed wide-cell preview marshaled: %#v", malformed.Cells)
105 }
106 }
107 invalidLifecycle := valid
108 invalidLifecycle.LifecycleID = domain.SessionLifecycleID{}
109 if MarshalRemotePreview(invalidLifecycle) != nil {
110 t.Fatal("zero lifecycle ID marshaled")
111 }
112 invalidTab := valid
113 invalidTab.TabID = "tab id"
114 if MarshalRemotePreview(invalidTab) != nil {
115 t.Fatal("invalid tab ID marshaled")
116 }
117}
118
119func TestRemotePreviewRejectsControlRunes(t *testing.T) {
120 valid := RemotePreview{
121 Version: RemotePreviewSchemaVersion, Status: RemotePreviewOK,
122 LifecycleID: previewTargetForTest().LifecycleID, TabID: "tab-1", Revision: 1, Width: 1, Height: 1,
123 Cells: []renderer.Cell{{Rune: 'x'}},
124 }
125 for _, control := range []rune{'\x01', '\x1b', '\x7f', '\u0085'} {
126 preview := valid
127 preview.Cells = []renderer.Cell{{Rune: control}}
128 if err := ValidateRemotePreview(preview); err != ErrInvalidRemotePreview {
129 t.Fatalf("control rune %U: ValidateRemotePreview() error = %v, want %v", control, err, ErrInvalidRemotePreview)
130 }
131 if MarshalRemotePreview(preview) != nil {
132 t.Fatalf("control rune %U was marshaled", control)
133 }
134 }
135}
136
137func TestRemotePreviewRequestRejectsOversizedNestedRouteData(t *testing.T) {
138 target := previewTargetForTest()
139 target.Endpoint = strings.Repeat("h", math.MaxUint16+1)
140 request := RemotePreviewRequest{Version: RemotePreviewSchemaVersion, Target: target, Width: 20, Height: 5}
141 if MarshalRemotePreviewRequest(request) != nil {
142 t.Fatal("oversized endpoint was truncated and marshaled")
143 }
144}
145
146func TestRemoteTargetWireRejectsInvalidSelectorCombinations(t *testing.T) {
147 cases := []struct {
148 name string
149 mutate func(*domain.RemoteSessionTarget)
150 }{
151 {name: "zero lifecycle", mutate: func(target *domain.RemoteSessionTarget) { target.LifecycleID = domain.SessionLifecycleID{} }},
152 {name: "live and stopped selector", mutate: func(target *domain.RemoteSessionTarget) {
153 target.StoppedTab = domain.NewOrdinalTabSelector(0, "main", 1)
154 }},
155 {name: "stopped with live ID", mutate: func(target *domain.RemoteSessionTarget) {
156 target.Stopped = true
157 target.LiveTabID = "tab-1"
158 target.StoppedTab = domain.NewOrdinalTabSelector(0, "main", 1)
159 }},
160 {name: "stable selector with ordinal fields", mutate: func(target *domain.RemoteSessionTarget) {
161 target.Stopped = true
162 target.LiveTabID = ""
163 target.StoppedTab = domain.TabSelector{Kind: domain.TabSelectorByStableID, StableID: "tab-1", Ordinal: 1}
164 }},
165 {name: "ordinal selector with zero count", mutate: func(target *domain.RemoteSessionTarget) {
166 target.Stopped = true
167 target.LiveTabID = ""
168 target.StoppedTab = domain.NewOrdinalTabSelector(0, "main", 0)
169 }},
170 {name: "oversized tab name", mutate: func(target *domain.RemoteSessionTarget) {
171 target.Stopped = true
172 target.LiveTabID = ""
173 target.StoppedTab = domain.NewOrdinalTabSelector(0, strings.Repeat("n", 257), 1)
174 }},
175 }
176 for _, test := range cases {
177 t.Run(test.name, func(t *testing.T) {
178 target := *testRemoteTarget(false)
179 test.mutate(&target)
180 message := AttachTarget{Endpoint: target.Endpoint, Session: target.SessionName, Intent: IntentAttach, RemoteTarget: &target, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}
181 if MarshalAttachTarget(message) != nil {
182 t.Fatal("invalid target was marshaled")
183 }
184 })
185 }
186}
187
188func TestRemoteTargetWireRichGoldenRoundTrip(t *testing.T) {
189 target := testRemoteTarget(false)
190 got := MarshalAttachTarget(AttachTarget{
191 Endpoint: target.Endpoint, Session: target.SessionName, Intent: IntentAttach,
192 RemoteTarget: target, EnvironmentPolicy: EnvironmentPolicyDaemonOwned,
193 })
194 want := []byte{
195 0x00, 0x0f, 'b', 'u', 'i', 'l', 'd', '@', 'm', 'u', 'l', 'e', ':', '2', '2', '2', '2',
196 0x00, 0x04, 'w', 'o', 'r', 'k', IntentAttach,
197 0x01,
198 0x00, 0x0f, 'b', 'u', 'i', 'l', 'd', '@', 'm', 'u', 'l', 'e', ':', '2', '2', '2', '2',
199 0x00, 0x09, 'm', 'u', 'l', 'e', ':', '2', '2', '2', '2',
200 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
201 0x00, 0x04, 'w', 'o', 'r', 'k', 0x00,
202 0x00, 0x05, 't', 'a', 'b', '-', '3', 0x00, 0x01,
203 }
204 if !bytes.Equal(got, want) {
205 t.Fatalf("rich target bytes = %x, want %x", got, want)
206 }
207 decoded, err := UnmarshalAttachTarget(got)
208 if err != nil {
209 t.Fatalf("UnmarshalAttachTarget() error = %v", err)
210 }
211 if decoded.RemoteTarget == nil || *decoded.RemoteTarget != *target || decoded.EnvironmentPolicy != EnvironmentPolicyDaemonOwned {
212 t.Fatalf("decoded target = %#v, policy = %d", decoded.RemoteTarget, decoded.EnvironmentPolicy)
213 }
214}