1717package browser
1818
1919import (
20+ "encoding/json"
21+ "strings"
2022 "testing"
2123
2224 "github.com/gojue/moling/pkg/comm"
@@ -43,3 +45,59 @@ func TestBrowserServer(t *testing.T) {
4345 t .Fatalf ("Failed to create BrowserServer: %s" , err .Error ())
4446 }
4547}
48+
49+ // TestHoverSelectorEscaping verifies that the selector is safely JSON-encoded
50+ // before being embedded in the JavaScript expression, preventing code injection.
51+ func TestHoverSelectorEscaping (t * testing.T ) {
52+ tests := []struct {
53+ name string
54+ selector string
55+ // wantPrefix checks that the selector is embedded as a JSON-encoded double-quoted string
56+ wantJSPrefix string
57+ wantJSSuffix string
58+ }{
59+ {
60+ name : "normal selector" ,
61+ selector : "body" ,
62+ wantJSPrefix : `document.querySelector("body").dispatchEvent` ,
63+ },
64+ {
65+ name : "injection attempt with single quotes and comma operator" ,
66+ selector : "body'),document.title='PWNED',document.querySelector('body" ,
67+ // After JSON encoding, the selector is a double-quoted string literal.
68+ // The single quotes and commas stay inside the string and are NOT executable.
69+ wantJSPrefix : `document.querySelector("body'),document.title='PWNED',document.querySelector('body").dispatchEvent` ,
70+ },
71+ {
72+ name : "injection attempt with semicolons and IIFE" ,
73+ selector : "body'); (function(){ /* exfiltration */ })(); document.querySelector('body" ,
74+ wantJSPrefix : `document.querySelector("body'); (function(){ /* exfiltration */ })(); document.querySelector('body").dispatchEvent` ,
75+ },
76+ {
77+ name : "selector with double quotes is escaped by JSON" ,
78+ selector : `div[data-id="foo"]` ,
79+ // json.Marshal escapes inner double quotes as \", so they cannot break out of the JS string.
80+ wantJSPrefix : `document.querySelector("div[data-id=\"foo\"]").dispatchEvent` ,
81+ },
82+ }
83+
84+ for _ , tc := range tests {
85+ t .Run (tc .name , func (t * testing.T ) {
86+ selectorJSON , err := json .Marshal (tc .selector )
87+ if err != nil {
88+ t .Fatalf ("json.Marshal failed: %v" , err )
89+ }
90+ js := `document.querySelector(` + string (selectorJSON ) + `).dispatchEvent(new Event('mouseover'))`
91+
92+ // The embedded selector must be wrapped in JSON double-quotes (not single-quotes).
93+ // This ensures injected single-quote characters cannot break out of the JS string context.
94+ if ! strings .HasPrefix (string (selectorJSON ), `"` ) || ! strings .HasSuffix (string (selectorJSON ), `"` ) {
95+ t .Errorf ("selector was not JSON-encoded as a double-quoted string: %s" , string (selectorJSON ))
96+ }
97+
98+ if tc .wantJSPrefix != "" && ! strings .HasPrefix (js , tc .wantJSPrefix ) {
99+ t .Errorf ("JS expression did not start with expected prefix\n want: %s\n got: %s" , tc .wantJSPrefix , js )
100+ }
101+ })
102+ }
103+ }
0 commit comments