Repository: neuvector/scanner
Pull request #230: NV9905: add k8s workload scans
Author: login: jayhuang-suse
Created: 2025-04-25T16:39:40Z | Merged: 2025-05-28T21:13:09Z

(1) sync with enforcer, targeted on 5.4.4 release
(2) sync with neuvector packages.


Diff:
diff --git a/cvetools/apps.go b/cvetools/apps.go
index 77ba3ac5..c554b2b2 100644
--- a/cvetools/apps.go
+++ b/cvetools/apps.go
@@ -2,6 +2,7 @@ package cvetools
 
 import (
 	"fmt"
+	"regexp"
 	"strings"
 
 	log "github.com/sirupsen/logrus"
@@ -60,9 +61,27 @@ func (cv *ScanTools) DetectAppVul(path string, apps []detectors.AppFeatureVersio
 	return vuls
 }
 
+var prefixVersionRegexp = regexp.MustCompile(`^(\d+\.\d+\.\d+)`)
+
+// a proper version is like:  1.0.0 (Major: 1, Minor: 0, Patch: 0)
+func getPrefixVersion(version string) string {
+	match := prefixVersionRegexp.FindStringSubmatch(version)
+	if len(match) > 1 {
+		return match[1]
+	}
+	return version // not a prpoer version
+}
+
 func checkForVulns(app detectors.AppFeatureVersion, appIndex int, apps []detectors.AppFeatureVersion, mv []common.AppModuleVul) []vulFullReport {
 	vuls := make([]vulFullReport, 0)
 	for _, v := range mv {
+		// should be good for all apps but focus on the golang now
+		if strings.HasPrefix(app.ModuleName, "go:") {
+			for i := range v.AffectedVer {
+				v.AffectedVer[i].Version = getPrefixVersion(v.AffectedVer[i].Version)
+			}
+		}
+
 		if common.Debugs.Enabled {
 			if common.Debugs.CVEs.Contains(v.VulName) && common.Debugs.Features.Contains(app.AppName) {
 				log.WithFields(log.Fields{
diff --git a/cvetools/cvesearch.go b/cvetools/cvesearch.go
index 63a1ab4d..a7f17f9f 100644
--- a/cvetools/cvesearch.go
+++ b/cvetools/cvesearch.go
@@ -102,6 +102,10 @@ var aliasMap map[string]string = map[string]string{
 	"docker-ce": "docker",
 }
 
+var matchKubenetesModeules map[string]bool = map[string]bool{ // should expand into a broader range later
+	"ingress-nginx": true,
+}
+
 func NewScanTools(rtSock string, sys *system.SystemTools, layerCacher *ImageLayerCacher, mFile string) *ScanTools {
 	cveDB := common.NewCveDB()
 	return &ScanTools{
@@ -148,12 +152,41 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		log.WithFields(log.Fields{"k8sAppRes": k8sAppString}).Debug()
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(strings.TrimPrefix(k8sAppString[:i], "rke2-"), "rke-")
+			if ok := matchKubenetesModeules[name]; ok {
+				name = fmt.Sprintf("go:k8s.io/%s", name)
+				version := strings.TrimPrefix(k8sAppString[i+1:], "v")
+				k8sAppRes := scan.AppPackage{
+					AppName:    "golang",        // belong to golang type
+					ModuleName: name,            // image name
+					Version:    version,         // tag
+					FileName:   scan.Kubernetes, // high-level k8s scan
+				}
+				appPkgs = append(appPkgs, k8sAppRes)
+			}
+		} else {
+			log.WithFields(log.Fields{"k8sAppString": k8sAppString}).Error("Invalid string")
+		}
+	}
 	// convert AppPackage to AppFeatureVersion
 	afvs := make([]detectors.AppFeatureVersion, len(appPkgs))
 	for i, a := range appPkgs {


Reviews:
user: login: pohanhuangtw
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-05T03:01:55Z
user: login: pohanhuangtw
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-05T03:04:03Z
user: login: pohanhuangtw
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-05T03:07:23Z
user: login: pohanhuangtw
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-05T03:20:15Z
user: login: jayhuang-suse
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T15:53:01Z
user: login: jayhuang-suse
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T15:53:14Z
user: login: jayhuang-suse
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T15:54:24Z
user: login: jayhuang-suse
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T15:56:12Z
user: login: holyspectral
body: I've left my comments:

- I think most of @pohanhuangtw 's comments make sense.  
- I'm not sure if I understand it correctly, but it looks like we are adding `k8s.io` prefix to all workload, which doesn't seem right.  Please make sure it works as expected. 
- Please separate dependency update into another commit.  (go.mod, go.sum and vendor/)

Could you also resolve the conflict with main branch, so this can be merged? 
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T16:53:28Z
user: login: jayhuang-suse
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T22:18:36Z
user: login: jayhuang-suse
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T22:19:05Z
user: login: jayhuang-suse
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T22:27:42Z
user: login: jayhuang-suse
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T22:33:49Z
user: login: holyspectral
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T23:09:20Z
user: login: holyspectral
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T18:04:54Z
user: login: holyspectral
body: LGTM.  Just a reminder that you can request re-review by clicking the button when you're done.  
![image](https://github.com/user-attachments/assets/60889b2c-f17d-4b15-ba37-e98078640c49)
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-28T21:12:53Z

Review comments:
diff_hunk: @@ -60,9 +61,27 @@ func (cv *ScanTools) DetectAppVul(path string, apps []detectors.AppFeatureVersio
 	return vuls
 }
 
+var properVersionRegexp = regexp.MustCompile(`^(\d+\.\d+\.\d+)`)
path: cvetools/apps.go
user: login: pohanhuangtw
body: Nit: naming improve
```go
var semverPrefixRegexp = regexp.MustCompile(`^(\d+\.\d+\.\d+)`)

// extractSemverPrefix extracts a valid semantic version prefix (e.g., "1.2.3") from the input.
func extractSemverPrefix(input string) string {
	match := semverPrefixRegexp.FindStringSubmatch(input)
	if len(match) > 1 {
		return match[1]
	}
	return input // fallback: not a proper semver prefix
}
```
created_at: 2025-05-05T03:01:54Z
author_association: NONE
diff_hunk: @@ -60,9 +61,27 @@ func (cv *ScanTools) DetectAppVul(path string, apps []detectors.AppFeatureVersio
 	return vuls
 }
 
+var properVersionRegexp = regexp.MustCompile(`^(\d+\.\d+\.\d+)`)
+
+// a proper version is like:  1.0.0 (Major: 1, Minor: 0, Patch: 0)
+func keepProperVersion(version string) string {
+	match := properVersionRegexp.FindStringSubmatch(version)
+	if len(match) > 1 {
+		return match[1]
+	}
+	return version // not a prpoer version
+}
+
 func checkForVulns(app detectors.AppFeatureVersion, appIndex int, apps []detectors.AppFeatureVersion, mv []common.AppModuleVul) []vulFullReport {
 	vuls := make([]vulFullReport, 0)
 	for _, v := range mv {
+		// should be good for all apps but focus on the golang now
+		if strings.HasPrefix(app.ModuleName, "go:") {
path: cvetools/apps.go
user: login: pohanhuangtw
body: Is there any specific reason that we grep this prefix only?
created_at: 2025-05-05T03:04:03Z
author_association: NONE
diff_hunk: @@ -148,12 +148,38 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
+			name = strings.TrimPrefix(name, "rke-")
+			name = fmt.Sprintf("go:k8s.io/%s", name)
+			version := strings.TrimPrefix(k8sAppString[i+1:], "v")
path: cvetools/cvesearch.go
user: login: pohanhuangtw
body: Does this version related to the version above?
created_at: 2025-05-05T03:07:23Z
author_association: NONE
diff_hunk: @@ -148,12 +148,38 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
+			name = strings.TrimPrefix(name, "rke-")
path: cvetools/cvesearch.go
user: login: pohanhuangtw
body: base := strings.TrimPrefix(strings.TrimPrefix(parts[0], "rke2-"), "rke-")
modName := "go:k8s.io/" + base
created_at: 2025-05-05T03:20:15Z
author_association: NONE
diff_hunk: @@ -60,9 +61,27 @@ func (cv *ScanTools) DetectAppVul(path string, apps []detectors.AppFeatureVersio
 	return vuls
 }
 
+var properVersionRegexp = regexp.MustCompile(`^(\d+\.\d+\.\d+)`)
+
+// a proper version is like:  1.0.0 (Major: 1, Minor: 0, Patch: 0)
+func keepProperVersion(version string) string {
+	match := properVersionRegexp.FindStringSubmatch(version)
+	if len(match) > 1 {
+		return match[1]
+	}
+	return version // not a prpoer version
+}
+
 func checkForVulns(app detectors.AppFeatureVersion, appIndex int, apps []detectors.AppFeatureVersion, mv []common.AppModuleVul) []vulFullReport {
 	vuls := make([]vulFullReport, 0)
 	for _, v := range mv {
+		// should be good for all apps but focus on the golang now
+		if strings.HasPrefix(app.ModuleName, "go:") {
path: cvetools/apps.go
user: login: jayhuang-suse
body: It was a case only found at the golang package now. I have not learned other packages has this issue.
created_at: 2025-05-05T15:53:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,9 +61,27 @@ func (cv *ScanTools) DetectAppVul(path string, apps []detectors.AppFeatureVersio
 	return vuls
 }
 
+var properVersionRegexp = regexp.MustCompile(`^(\d+\.\d+\.\d+)`)
path: cvetools/apps.go
user: login: jayhuang-suse
body: Suggestion?
created_at: 2025-05-05T15:53:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,12 +148,38 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
+			name = strings.TrimPrefix(name, "rke-")
+			name = fmt.Sprintf("go:k8s.io/%s", name)
+			version := strings.TrimPrefix(k8sAppString[i+1:], "v")
path: cvetools/cvesearch.go
user: login: jayhuang-suse
body: Please elaborate more on your question.
created_at: 2025-05-05T15:54:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,12 +148,38 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
+			name = strings.TrimPrefix(name, "rke-")
path: cvetools/cvesearch.go
user: login: jayhuang-suse
body: The suggestive code is hard to read. and trace
created_at: 2025-05-05T15:56:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,9 +61,27 @@ func (cv *ScanTools) DetectAppVul(path string, apps []detectors.AppFeatureVersio
 	return vuls
 }
 
+var properVersionRegexp = regexp.MustCompile(`^(\d+\.\d+\.\d+)`)
path: cvetools/apps.go
user: login: holyspectral
body: @pohanhuangtw you would have to select the section of code that you want to replace with your suggestion before you click the button to add a suggestion.  In this case you would have to select Ln64-Ln73, so GitHub knows where to replace.  

That said, I agree what @pohanhuangtw pointed out.  `proper` is a subjective word and we should use a more objective name for these variables.  Calling it `semver` is much better.  Alternatively [Masterminds/semver](https://pkg.go.dev/github.com/Masterminds/semver/v3#NewVersion) can be used to parse semver.  
created_at: 2025-05-06T16:16:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,12 +148,38 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
+			name = strings.TrimPrefix(name, "rke-")
path: cvetools/cvesearch.go
user: login: holyspectral
body: I don't think the version @pohanhuangtw provided is hard to read/trace, but generally you can just provide an example in comments so readers can know what they should expect.  
created_at: 2025-05-06T16:26:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,12 +148,38 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
+			name = strings.TrimPrefix(name, "rke-")
+			name = fmt.Sprintf("go:k8s.io/%s", name)
+			version := strings.TrimPrefix(k8sAppString[i+1:], "v")
+			k8sAppRes := scan.AppPackage{
+				AppName:    "golang",        // belong to golang type
+				ModuleName: name,            // image name
+				Version:    version,         // tag
+				FileName:   scan.Kubernetes, // high-level k8s scan
+			}
+			log.WithFields(log.Fields{"k8sAppRes": k8sAppRes}).Debug()
+			appPkgs = append(appPkgs, k8sAppRes)
+		}
path: cvetools/cvesearch.go
user: login: holyspectral
body: We should not eat the error if the `k8sAppString` is not in the right format.  
created_at: 2025-05-06T16:35:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,12 +148,38 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
+			name = strings.TrimPrefix(name, "rke-")
+			name = fmt.Sprintf("go:k8s.io/%s", name)
path: cvetools/cvesearch.go
user: login: holyspectral
body: I'm not sure adding `go:k8s.io` prefix here is correct.  In https://github.com/neuvector/neuvector/pull/1937/ you return the pod label from all pods instead of only kubernetes pods.  Have you tried workloads not provided by k8s? Can you apply this only to k8s workload? 
created_at: 2025-05-06T16:40:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,12 +148,38 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
+			name = strings.TrimPrefix(name, "rke-")
+			name = fmt.Sprintf("go:k8s.io/%s", name)
+			version := strings.TrimPrefix(k8sAppString[i+1:], "v")
+			k8sAppRes := scan.AppPackage{
+				AppName:    "golang",        // belong to golang type
+				ModuleName: name,            // image name
+				Version:    version,         // tag
+				FileName:   scan.Kubernetes, // high-level k8s scan
+			}
+			log.WithFields(log.Fields{"k8sAppRes": k8sAppRes}).Debug()
+			appPkgs = append(appPkgs, k8sAppRes)
+		}
path: cvetools/cvesearch.go
user: login: jayhuang-suse
body: If it is reasonable, please provide an error.
created_at: 2025-05-06T22:18:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,12 +148,38 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
+			name = strings.TrimPrefix(name, "rke-")
+			name = fmt.Sprintf("go:k8s.io/%s", name)
path: cvetools/cvesearch.go
user: login: jayhuang-suse
body: It is based on the cve database.
created_at: 2025-05-06T22:19:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,12 +148,38 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
+			name = strings.TrimPrefix(name, "rke-")
path: cvetools/cvesearch.go
user: login: jayhuang-suse
body: In fact, the LINE 170 will be removed since the rke has been EOL soon after July, 2025.
created_at: 2025-05-06T22:27:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,9 +61,27 @@ func (cv *ScanTools) DetectAppVul(path string, apps []detectors.AppFeatureVersio
 	return vuls
 }
 
+var properVersionRegexp = regexp.MustCompile(`^(\d+\.\d+\.\d+)`)
path: cvetools/apps.go
user: login: jayhuang-suse
body: It is okay to change the name. Versioning is highly opinionated. There aren't that many standards, but there are some conventions. In the CVE database, the versioning does not follow up the SemVer, that is why it is called "proper". 
created_at: 2025-05-06T22:33:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,9 +61,27 @@ func (cv *ScanTools) DetectAppVul(path string, apps []detectors.AppFeatureVersio
 	return vuls
 }
 
+var properVersionRegexp = regexp.MustCompile(`^(\d+\.\d+\.\d+)`)
path: cvetools/apps.go
user: login: holyspectral
body: I don't think our CVE database doesn't follow semver, because that's all the community and upstream CVE databases label their software.  If it doesn't follow semver, we should have seen a lot of issues.

If you don't want to call it semver, calling it `prefixRegexp` works too.  
created_at: 2025-05-06T23:05:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,12 +148,38 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
+			name = strings.TrimPrefix(name, "rke-")
+			name = fmt.Sprintf("go:k8s.io/%s", name)
path: cvetools/cvesearch.go
user: login: holyspectral
body: Could you please make sure that you don't send workloads not provided by k8s to match with CVE database?  Because that would lead to false positive.  
created_at: 2025-05-06T23:07:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,12 +148,38 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 		return result, err
 	}
 
+	var k8sAppString string
 	files := make(map[string]*detectors.FeatureFile)
 	for name, data := range pkgs {
 		files[name] = &detectors.FeatureFile{Data: data}
+		if name == scan.Kubernetes {
+			var kubeResource scan.KubernetesResource
+			if err := json.Unmarshal(data, &kubeResource); err != nil {
+				log.WithFields(log.Fields{"name": name, "data": string(data)}).Error()
+			}
+			k8sAppString = kubeResource.Name
+		}
 	}
-	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 
+	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
+	if k8sAppString != "" {
+		// append k8s repo into the appPkgs
+		if i := strings.Index(k8sAppString, ":"); i > 0 {
+			// remove the RKE variants
+			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
+			name = strings.TrimPrefix(name, "rke-")
+			name = fmt.Sprintf("go:k8s.io/%s", name)
+			version := strings.TrimPrefix(k8sAppString[i+1:], "v")
+			k8sAppRes := scan.AppPackage{
+				AppName:    "golang",        // belong to golang type
+				ModuleName: name,            // image name
+				Version:    version,         // tag
+				FileName:   scan.Kubernetes, // high-level k8s scan
+			}
+			log.WithFields(log.Fields{"k8sAppRes": k8sAppRes}).Debug()
+			appPkgs = append(appPkgs, k8sAppRes)
+		}
path: cvetools/cvesearch.go
user: login: holyspectral
body: ```
errors.New("invalid k8sAppString")
```
created_at: 2025-05-06T23:08:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -163,21 +163,22 @@ func (cv *ScanTools) ScanImageData(data *share.ScanData) (*share.ScanResult, err
 
 	appPkgs := scan.NewScanApps(true).DerivePkg(pkgs)
 	if k8sAppString != "" {
+		log.WithFields(log.Fields{"k8sAppRes": k8sAppString}).Debug()
 		// append k8s repo into the appPkgs
 		if i := strings.Index(k8sAppString, ":"); i > 0 {
 			// remove the RKE variants
-			name := strings.TrimPrefix(k8sAppString[:i], "rke2-")
-			name = strings.TrimPrefix(name, "rke-")
-			name = fmt.Sprintf("go:k8s.io/%s", name)
-			version := strings.TrimPrefix(k8sAppString[i+1:], "v")
-			k8sAppRes := scan.AppPackage{
-				AppName:    "golang",        // belong to golang type
-				ModuleName: name,            // image name
-				Version:    version,         // tag
-				FileName:   scan.Kubernetes, // high-level k8s scan
+			name := strings.TrimPrefix(strings.TrimPrefix(k8sAppString[:i], "rke2-"), "rke-")
+			if name == "ingress-nginx" { // should expand into a broader range later
+				name = fmt.Sprintf("go:k8s.io/%s", name)
+				version := strings.TrimPrefix(k8sAppString[i+1:], "v")
+				k8sAppRes := scan.AppPackage{
+					AppName:    "golang",        // belong to golang type
+					ModuleName: name,            // image name
+					Version:    version,         // tag
+					FileName:   scan.Kubernetes, // high-level k8s scan
+				}
+				appPkgs = append(appPkgs, k8sAppRes)
path: cvetools/cvesearch.go
user: login: holyspectral
body: Could you create a `map[string]bool` containing one element, so we can use `somemap[name]` to determine if it's in the list?  For example, https://go.dev/play/p/pxdDr5zptHM
created_at: 2025-05-22T18:04:35Z
author_association: CONTRIBUTOR

PR comments:
user: login: holyspectral
created_at: 2025-05-06T16:04:28Z
body: I'm not saying that we should skip code review or something, but this PR is blocking [the dependency update PR from renovate](https://github.com/neuvector/scanner/pull/232) right now because the interface change.  

![image](https://github.com/user-attachments/assets/8d069f66-aa8f-4358-8179-0976092e22f4)


Those PRs for dependency updates have to be created separately, like https://github.com/neuvector/scanner/pull/238 

author_association: CONTRIBUTOR
user: login: jayhuang-suse
created_at: 2025-05-06T16:20:59Z
body: @pohanhuangtw please resolve the latest "neuvector" package update and I can follow up to this PR's changes. 
author_association: CONTRIBUTOR
user: login: jayhuang-suse
created_at: 2025-05-06T22:21:24Z
body: > I've left my comments:
> 
> * I think most of @pohanhuangtw 's comments make sense.
> * I'm not sure if I understand it correctly, but it looks like we are adding `k8s.io` prefix to all workload, which doesn't seem right.  Please make sure it works as expected.
==> Please read the JIRA cases and the results are shown there.

> * Please separate dependency update into another commit.  (go.mod, go.sum and vendor/) 
> 
> Could you also resolve the conflict with main branch, so this can be merged?

==> Has asked @pohanhuangtw to make the porting since I am not the maintainer,


author_association: CONTRIBUTOR
user: login: pohanhuangtw
created_at: 2025-05-12T05:50:50Z
body: I have a similar question to what @holyspectral raised:

- Where does the go:k8s.io prefix come from? Is it hard-coded into our build logic
- Do we only consider packages that start with go:k8s.io for every Kubernetes application?
author_association: NONE
user: login: jayhuang-suse
created_at: 2025-05-14T15:41:37Z
body: > I have a similar question to what @holyspectral raised:
> 
> * Where does the go:k8s.io prefix come from? Is it hard-coded into our build logic
> * Do we only consider packages that start with go:k8s.io for every Kubernetes application?

It depends on the CVE database. Overall speaking, the K8s CVEs are not from their images issue but found by the k8s combination structures. Thus, the CVE needs to prefixed with the specific string to detect the workload issues.  Comparing to modify the database source, it is safer to adjust the names at the front-end. 
author_association: CONTRIBUTOR
user: login: jayhuang-suse
created_at: 2025-05-14T15:42:50Z
body: @holyspectral please update your blocking issues here. 
author_association: CONTRIBUTOR
user: login: pohanhuangtw
created_at: 2025-05-19T04:18:46Z
body: Then I have no issue, here.
Thanks

author_association: NONE
user: login: jayhuang-suse
created_at: 2025-05-19T17:08:39Z
body: @holyspectral please specify the remaining concerns. thanks.
author_association: CONTRIBUTOR
user: login: holyspectral
created_at: 2025-05-20T14:37:05Z
body: Thanks for tagging me.  I'm still confused by the change but feel free to correct me if I'm wrong.

My understanding is that we get the package and version via the change in https://github.com/neuvector/neuvector/pull/1937/files by combining `app.kubernetes.io/name` and `app.kubernetes.io/version` labels to form a `<name>:<version>` k8sString.

After that, in this PR, a `go:k8s.io/` prefix is added to ALL the `k8sString` retrieved and the full path is sent to match with CVE DB.

My point is that the `app.kubernetes.io/name` and `app.kubernetes.io/version` labels can be added to any workload in the system, and they're actually the recommendation from k8s, which means that we will see a lot of these labels.   For example, a container with `app.kubernetes.io/name: net` and `app.kubernetes.io/version: 1.0.0` labels will be sent to match with `go:k8s.io/net:1.0.0` and that's what I referred as a potential for a false positive.  

I understand that supporting this kind of scenario is difficult, but I'd expect that we maintain a list in scanner side to only allow some of packages retrieved this way to be sent to match CVE DB, so that we don't have a huge test scope like this.  


author_association: CONTRIBUTOR
user: login: jayhuang-suse
created_at: 2025-05-20T16:56:18Z
body: Good point. It is necessary to put the restrictions on the namespace "kube-system" (k8s reserved system pods) at the enforcer. This PR might be delayed until the next release.  

It is not good to restrict the matching at the scanner eventually.
author_association: CONTRIBUTOR
user: login: jayhuang-suse
created_at: 2025-05-20T17:08:42Z
body: @holyspectral and @pohanhuangtw would you agree that we can add "kube-system" criteria at enforcer side to solve the false-positive cases?
author_association: CONTRIBUTOR
user: login: holyspectral
created_at: 2025-05-20T20:20:06Z
body: Making sure that packages coming from `kube-system` would work.   I'd also vote for just sending k8s namespace from enforcer and leave the decision for scanner.  This will make sure that we can change the filter any time we want.  

Note that in some cases the package of interest may not be installed in the `kube-system` namespace, so please make sure that it's configurable.  
author_association: CONTRIBUTOR
user: login: jayhuang-suse
created_at: 2025-05-20T22:47:26Z
body: According to the k8s note, the criteria for the "ingress-nginx" is regradless of its namespace.

https://kubernetes.io/blog/2025/03/24/ingress-nginx-cve-2025-1974/

`kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx`

Thus, we can add a filter at scanner to report only the "ingress-nginx" (all namespaces) is and it should take effectively with 5.4.4 release. Later on, we can add the namespace parameter in future neuvector releases.
author_association: CONTRIBUTOR
user: login: holyspectral
created_at: 2025-05-21T19:50:00Z
body: > Thus, we can add a filter at scanner to report only the "ingress-nginx" (all namespaces) is and it should take effectively with 5.4.4 release. Later on, we can add the namespace parameter in future neuvector releases.

Sounds like a plan!  It doesn't harm if we can provide the namespace from enforcer though, so it will be there when we need it.
author_association: CONTRIBUTOR