Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@
mux.HandlePath(http.MethodGet, "/healthz", frontEndHandlerWithLocation(o.consolePath))
mux.HandlePath(http.MethodGet, "/favicon.ico", frontEndHandlerWithLocation(o.consolePath))
mux.HandlePath(http.MethodGet, "/swagger.json", frontEndHandlerWithLocation(o.consolePath))
mux.HandlePath(http.MethodGet, "/data/{data}", o.dataFromExtension(remoteServer.(server.UIExtensionServer)))
mux.HandlePath(http.MethodGet, "/get", o.getAtestBinary)
mux.HandlePath(http.MethodPost, "/runner/{suite}/{case}", service.WebRunnerHandler)
mux.HandlePath(http.MethodGet, "/api/v1/sbom", service.SBomHandler)
Expand Down Expand Up @@ -531,6 +532,27 @@
})
}

func (o *serverOption) dataFromExtension(extServer server.UIExtensionServer) func(w http.ResponseWriter,
r *http.Request, pathParams map[string]string) {
return func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
ctx := r.Context()
for k, v := range r.Header {
if !strings.HasPrefix(k, "X-Extension-") {
continue
}
ctx = context.WithValue(ctx, k, v)
}
result, err := extServer.GetPageOfStatic(ctx, &server.SimpleName{
Name: pathParams["data"],
})
if err == nil {
w.Write([]byte(result.GetMessage()))

Check warning

Code scanning / CodeQL

Reflected cross-site scripting Medium

Cross-site scripting vulnerability due to
user-provided value
.

Copilot Autofix

AI 3 months ago

To address the XSS vulnerability, we should escape any untrusted data before embedding it in an HTTP response likely to be rendered as HTML by browsers. In this case, the output from result.GetMessage() should be HTML-escaped, especially since extension code receives arbitrary header values from the user and could include them in the message.

Best fix:

  • In the handler function, change the line that writes the message to the response to write the HTML-escaped version of the message.
  • Use Go's standard html.EscapeString() function (import "html" package) for HTML escaping.
  • Only the relevant region of cmd/server.go needs to be changed: around line 549, and a new import added at the top.

No changes are required to the proto or server files; only the handler that produces the HTTP response needs adjusting.

Suggested changeset 1
cmd/server.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/cmd/server.go b/cmd/server.go
--- a/cmd/server.go
+++ b/cmd/server.go
@@ -32,7 +32,7 @@
 	"strings"
 	"syscall"
 	"time"
-
+	"html"
 	"github.com/linuxsuren/api-testing/pkg/apispec"
 
 	"github.com/linuxsuren/api-testing/pkg/runner"
@@ -546,9 +546,9 @@
 			Name: pathParams["data"],
 		})
 		if err == nil {
-			w.Write([]byte(result.GetMessage()))
+			w.Write([]byte(html.EscapeString(result.GetMessage())))
 		} else {
-			w.Write([]byte(err.Error()))
+			w.Write([]byte(html.EscapeString(err.Error())))
 		}
 	}
 }
EOF
@@ -32,7 +32,7 @@
"strings"
"syscall"
"time"

"html"
"github.com/linuxsuren/api-testing/pkg/apispec"

"github.com/linuxsuren/api-testing/pkg/runner"
@@ -546,9 +546,9 @@
Name: pathParams["data"],
})
if err == nil {
w.Write([]byte(result.GetMessage()))
w.Write([]byte(html.EscapeString(result.GetMessage())))
} else {
w.Write([]byte(err.Error()))
w.Write([]byte(html.EscapeString(err.Error())))
}
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
} else {
w.Write([]byte(err.Error()))
}
}
}

func (o *serverOption) getAtestBinary(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
name := util.EmptyThenDefault(r.URL.Query().Get("name"), "atest")

Expand Down
4 changes: 4 additions & 0 deletions console/atest-ui/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ export default defineConfig({
target: 'http://127.0.0.1:8080',
changeOrigin: true,
},
'/data': {
target: 'http://127.0.0.1:8080',
changeOrigin: true,
},
},
},
})
30 changes: 30 additions & 0 deletions pkg/server/remote_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1531,6 +1531,36 @@ func (s *server) GetPageOfCSS(ctx context.Context, in *SimpleName) (result *Comm
return
}

func (s *server) GetPageOfStatic(ctx context.Context, in *SimpleName) (result *CommonResult, err error) {
result = &CommonResult{}
extNameInter := ctx.Value("X-Extension-Name")
if extNameInter == nil {
result.Message = "X-Extension-Name is required"
result.Success = false
return
}

var extName string
switch v := extNameInter.(type) {
case []string:
extName = v[0]
case string:
extName = v
}

if loader, ok := uiExtensionLoaders[extName]; ok {
if js, err := loader.GetPageOfStatic(in.Name); err == nil {
result.Message = js
result.Success = true
} else {
result.Message = err.Error()
}
} else {
result.Message = fmt.Sprintf("not found loader for %s", extName)
}
return
}

// implement the mock server

// Start starts the mock server
Expand Down
248 changes: 128 additions & 120 deletions pkg/server/server.pb.go

Large diffs are not rendered by default.

103 changes: 103 additions & 0 deletions pkg/server/server.pb.gw.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions pkg/server/server.proto
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,11 @@ service UIExtension {
get: "/api/v1/extension/pages/{name}/css"
};
}
rpc GetPageOfStatic(SimpleName) returns (CommonResult) {
option (google.api.http) = {
get: "/api/v1/extension/pages/{name}/static"
};
}
}

message Menu {
Expand Down
36 changes: 36 additions & 0 deletions pkg/server/server_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/testing/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Loader interface {
GetMenus() ([]*Menu, error)
GetPageOfJS(string) (string, error)
GetPageOfCSS(string) (string, error)
GetPageOfStatic(string) (string, error)
}

type Menu struct {
Expand Down
5 changes: 5 additions & 0 deletions pkg/testing/loader_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,3 +606,8 @@ func (l *fileLoader) GetPageOfCSS(s string) (string, error) {
// no need to implement
return "", nil
}

func (l *fileLoader) GetPageOfStatic(s string) (string, error) {
// no need to implement
return "", nil
}
4 changes: 4 additions & 0 deletions pkg/testing/loader_non.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,7 @@ func (l *nonLoader) GetPageOfJS(s string) (string, error) {
func (l *nonLoader) GetPageOfCSS(s string) (string, error) {
return "", fmt.Errorf("not support")
}

func (l *nonLoader) GetPageOfStatic(s string) (string, error) {
return "", fmt.Errorf("not support")
}
10 changes: 10 additions & 0 deletions pkg/testing/remote/grpc_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,16 @@ func (g *gRPCLoader) GetPageOfCSS(name string) (result string, err error) {
return
}

func (g *gRPCLoader) GetPageOfStatic(name string) (result string, err error) {
var data *server.CommonResult
if data, err = g.client.GetPageOfStatic(g.ctx, &server.SimpleName{
Name: name,
}); err == nil && data != nil {
result = data.Message
}
return
}

func (g *gRPCLoader) Parse() (server *mock.Server, err error) {
return
}
Expand Down
Loading
Loading