Merge pull request #1926 from kubescape/copilot/fix-kubescan-interface-error

Fix panic on unsafe interface{} to string type assertions
This commit is contained in:
Matthias Bertschy
2026-01-22 19:43:25 +00:00
committed by GitHub
4 changed files with 117 additions and 9 deletions

View File

@@ -294,11 +294,19 @@ func (ksServer *KubescapeMcpserver) CallTool(name string, arguments map[string]i
if !ok {
namespace = "kubescape"
}
namespaceStr, ok := namespace.(string)
if !ok {
return nil, fmt.Errorf("namespace must be a string")
}
manifestName, ok := arguments["manifest_name"]
if !ok {
return nil, fmt.Errorf("manifest_name is required")
}
manifest, err := ksServer.ksClient.VulnerabilityManifests(namespace.(string)).Get(context.Background(), manifestName.(string), metav1.GetOptions{})
manifestNameStr, ok := manifestName.(string)
if !ok {
return nil, fmt.Errorf("manifest_name must be a string")
}
manifest, err := ksServer.ksClient.VulnerabilityManifests(namespaceStr).Get(context.Background(), manifestNameStr, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get vulnerability manifest: %s", err)
}
@@ -323,21 +331,33 @@ func (ksServer *KubescapeMcpserver) CallTool(name string, arguments map[string]i
if !ok {
namespace = "kubescape"
}
namespaceStr, ok := namespace.(string)
if !ok {
return nil, fmt.Errorf("namespace must be a string")
}
manifestName, ok := arguments["manifest_name"]
if !ok {
return nil, fmt.Errorf("manifest_name is required")
}
manifestNameStr, ok := manifestName.(string)
if !ok {
return nil, fmt.Errorf("manifest_name must be a string")
}
cveID, ok := arguments["cve_id"]
if !ok {
return nil, fmt.Errorf("cve_id is required")
}
manifest, err := ksServer.ksClient.VulnerabilityManifests(namespace.(string)).Get(context.Background(), manifestName.(string), metav1.GetOptions{})
cveIDStr, ok := cveID.(string)
if !ok {
return nil, fmt.Errorf("cve_id must be a string")
}
manifest, err := ksServer.ksClient.VulnerabilityManifests(namespaceStr).Get(context.Background(), manifestNameStr, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get vulnerability manifest: %s", err)
}
var match []v1beta1.Match
for _, m := range manifest.Spec.Payload.Matches {
if m.Vulnerability.ID == cveID.(string) {
if m.Vulnerability.ID == cveIDStr {
match = append(match, m)
}
}
@@ -358,7 +378,11 @@ func (ksServer *KubescapeMcpserver) CallTool(name string, arguments map[string]i
if !ok {
namespace = "kubescape"
}
manifests, err := ksServer.ksClient.WorkloadConfigurationScans(namespace.(string)).List(context.Background(), metav1.ListOptions{})
namespaceStr, ok := namespace.(string)
if !ok {
return nil, fmt.Errorf("namespace must be a string")
}
manifests, err := ksServer.ksClient.WorkloadConfigurationScans(namespaceStr).List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, err
}
@@ -394,11 +418,19 @@ func (ksServer *KubescapeMcpserver) CallTool(name string, arguments map[string]i
if !ok {
namespace = "kubescape"
}
namespaceStr, ok := namespace.(string)
if !ok {
return nil, fmt.Errorf("namespace must be a string")
}
manifestName, ok := arguments["manifest_name"]
if !ok {
return nil, fmt.Errorf("manifest_name is required")
}
manifest, err := ksServer.ksClient.WorkloadConfigurationScans(namespace.(string)).Get(context.Background(), manifestName.(string), metav1.GetOptions{})
manifestNameStr, ok := manifestName.(string)
if !ok {
return nil, fmt.Errorf("manifest_name must be a string")
}
manifest, err := ksServer.ksClient.WorkloadConfigurationScans(namespaceStr).Get(context.Background(), manifestNameStr, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get configuration manifest: %s", err)
}

View File

@@ -144,14 +144,23 @@ func CheckShortTerminalWidth(rows []table.Row, headers table.Row) bool {
for _, row := range rows {
rowWidth := 0
for idx, cell := range row {
cellLen := len(cell.(string))
cellStr, ok := cell.(string)
if !ok {
// If cell is not a string, skip this calculation
continue
}
cellLen := len(cellStr)
if cellLen > 50 { // Take only 50 characters of each sentence for counting size
cellLen = 50
}
if cellLen > len(headers[idx].(string)) {
headerStr, ok := headers[idx].(string)
if !ok {
// If header is not a string, use cell length
rowWidth += cellLen
} else if cellLen > len(headerStr) {
rowWidth += cellLen
} else {
rowWidth += len(headers[idx].(string))
rowWidth += len(headerStr)
}
rowWidth += 2
}

View File

@@ -5,6 +5,7 @@ import (
"os"
"testing"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jwalton/gchalk"
"github.com/kubescape/opa-utils/reporthandling/apis"
"github.com/stretchr/testify/assert"
@@ -334,3 +335,69 @@ func TestGetColorForVulnerabilitySeverity(t *testing.T) {
})
}
}
func TestCheckShortTerminalWidth(t *testing.T) {
tests := []struct {
name string
rows []table.Row
headers table.Row
// We can't predict the exact result since it depends on terminal size
// but we can test it doesn't panic with various inputs
shouldNotPanic bool
}{
{
name: "Normal string rows",
rows: []table.Row{
{"cell1", "cell2", "cell3"},
{"longer cell 1", "longer cell 2", "longer cell 3"},
},
headers: table.Row{"Header1", "Header2", "Header3"},
shouldNotPanic: true,
},
{
name: "Rows with non-string values (map)",
rows: []table.Row{
{"cell1", map[string]interface{}{"key": "value"}, "cell3"},
{"cell4", "cell5", "cell6"},
},
headers: table.Row{"Header1", "Header2", "Header3"},
shouldNotPanic: true,
},
{
name: "Headers with non-string values",
rows: []table.Row{
{"cell1", "cell2", "cell3"},
},
headers: table.Row{"Header1", map[string]interface{}{"key": "value"}, "Header3"},
shouldNotPanic: true,
},
{
name: "Both rows and headers with non-string values",
rows: []table.Row{
{map[string]interface{}{"key": "value"}, "cell2", 123},
},
headers: table.Row{[]string{"a", "b"}, "Header2", true},
shouldNotPanic: true,
},
{
name: "Empty rows",
rows: []table.Row{},
headers: table.Row{"Header1", "Header2"},
shouldNotPanic: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if tt.shouldNotPanic {
t.Errorf("CheckShortTerminalWidth() panicked when it shouldn't: %v", r)
}
}
}()
// Call the function - we just want to ensure it doesn't panic
_ = CheckShortTerminalWidth(tt.rows, tt.headers)
})
}
}

2
go.mod
View File

@@ -56,7 +56,6 @@ require (
go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/metric v1.39.0
golang.org/x/mod v0.31.0
golang.org/x/sync v0.19.0
golang.org/x/term v0.38.0
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.1
@@ -557,6 +556,7 @@ require (
golang.org/x/image v0.25.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.14.0 // indirect