-
Notifications
You must be signed in to change notification settings - Fork 10
feature: container-scan command to trivy scan containers #191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
c8c5182
25dcb9b
a7ac145
844da96
41cedbc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,181 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| package cmd | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||||||||||||||
| "os/exec" | ||||||||||||||||||||||||||||||||||||||||||||||
| "regexp" | ||||||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| "codacy/cli-v2/utils/logger" | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/fatih/color" | ||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/sirupsen/logrus" | ||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/spf13/cobra" | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // validImageNamePattern validates Docker image references | ||||||||||||||||||||||||||||||||||||||||||||||
| // Allows: registry/namespace/image:tag or image@sha256:digest | ||||||||||||||||||||||||||||||||||||||||||||||
| // Based on Docker image reference specification | ||||||||||||||||||||||||||||||||||||||||||||||
| var validImageNamePattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._\-/:@]*$`) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Flag variables for container-scan command | ||||||||||||||||||||||||||||||||||||||||||||||
| var ( | ||||||||||||||||||||||||||||||||||||||||||||||
| severityFlag string | ||||||||||||||||||||||||||||||||||||||||||||||
| pkgTypesFlag string | ||||||||||||||||||||||||||||||||||||||||||||||
| ignoreUnfixedFlag bool | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| func init() { | ||||||||||||||||||||||||||||||||||||||||||||||
| containerScanCmd.Flags().StringVar(&severityFlag, "severity", "", "Comma-separated list of severities to scan for (default: HIGH,CRITICAL)") | ||||||||||||||||||||||||||||||||||||||||||||||
| containerScanCmd.Flags().StringVar(&pkgTypesFlag, "pkg-types", "", "Comma-separated list of package types to scan (default: os)") | ||||||||||||||||||||||||||||||||||||||||||||||
| containerScanCmd.Flags().BoolVar(&ignoreUnfixedFlag, "ignore-unfixed", true, "Ignore unfixed vulnerabilities") | ||||||||||||||||||||||||||||||||||||||||||||||
| rootCmd.AddCommand(containerScanCmd) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| var containerScanCmd = &cobra.Command{ | ||||||||||||||||||||||||||||||||||||||||||||||
| Use: "container-scan [FLAGS] <IMAGE_NAME>", | ||||||||||||||||||||||||||||||||||||||||||||||
| Short: "Scan container images for vulnerabilities using Trivy", | ||||||||||||||||||||||||||||||||||||||||||||||
| Long: `Scan container images for vulnerabilities using Trivy. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| By default, scans for HIGH and CRITICAL vulnerabilities in OS packages, | ||||||||||||||||||||||||||||||||||||||||||||||
| ignoring unfixed issues. Use flags to override these defaults. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| The --exit-code 1 flag is always applied (not user-configurable) to ensure | ||||||||||||||||||||||||||||||||||||||||||||||
| the command fails when vulnerabilities are found.`, | ||||||||||||||||||||||||||||||||||||||||||||||
| Example: ` # Default behavior (HIGH,CRITICAL severity, os packages only) | ||||||||||||||||||||||||||||||||||||||||||||||
| codacy-cli container-scan myapp:latest | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Scan only for CRITICAL vulnerabilities | ||||||||||||||||||||||||||||||||||||||||||||||
| codacy-cli container-scan --severity CRITICAL myapp:latest | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Scan all severities and package types | ||||||||||||||||||||||||||||||||||||||||||||||
| codacy-cli container-scan --severity LOW,MEDIUM,HIGH,CRITICAL --pkg-types os,library myapp:latest | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| # Include unfixed vulnerabilities | ||||||||||||||||||||||||||||||||||||||||||||||
| codacy-cli container-scan --ignore-unfixed=false myapp:latest`, | ||||||||||||||||||||||||||||||||||||||||||||||
| Args: cobra.ExactArgs(1), | ||||||||||||||||||||||||||||||||||||||||||||||
| Run: runContainerScan, | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // validateImageName checks if the image name is a valid Docker image reference | ||||||||||||||||||||||||||||||||||||||||||||||
| // and doesn't contain shell metacharacters that could be used for command injection | ||||||||||||||||||||||||||||||||||||||||||||||
| func validateImageName(imageName string) error { | ||||||||||||||||||||||||||||||||||||||||||||||
| if imageName == "" { | ||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("image name cannot be empty") | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Check for maximum length (Docker has a practical limit) | ||||||||||||||||||||||||||||||||||||||||||||||
| if len(imageName) > 256 { | ||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("image name is too long (max 256 characters)") | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Validate against allowed pattern | ||||||||||||||||||||||||||||||||||||||||||||||
| if !validImageNamePattern.MatchString(imageName) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("invalid image name format: contains disallowed characters") | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Additional check for dangerous shell metacharacters | ||||||||||||||||||||||||||||||||||||||||||||||
| dangerousChars := []string{";", "&", "|", "$", "`", "(", ")", "{", "}", "<", ">", "!", "\\", "\n", "\r", "'", "\""} | ||||||||||||||||||||||||||||||||||||||||||||||
| for _, char := range dangerousChars { | ||||||||||||||||||||||||||||||||||||||||||||||
| if strings.Contains(imageName, char) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("invalid image name: contains disallowed character '%s'", char) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // getTrivyPath returns the path to the Trivy binary or exits if not found | ||||||||||||||||||||||||||||||||||||||||||||||
| func getTrivyPath() string { | ||||||||||||||||||||||||||||||||||||||||||||||
| trivyPath, err := exec.LookPath("trivy") | ||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| logger.Error("Trivy not found", logrus.Fields{"error": err.Error()}) | ||||||||||||||||||||||||||||||||||||||||||||||
| color.Red("❌ Error: Trivy is not installed or not found in PATH") | ||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Println("Please install Trivy to use container scanning.") | ||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Println("Visit: https://trivy.dev/latest/getting-started/installation/") | ||||||||||||||||||||||||||||||||||||||||||||||
| os.Exit(1) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| logger.Info("Found Trivy", logrus.Fields{"path": trivyPath}) | ||||||||||||||||||||||||||||||||||||||||||||||
| return trivyPath | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // handleTrivyResult processes the Trivy command result and exits appropriately | ||||||||||||||||||||||||||||||||||||||||||||||
| func handleTrivyResult(err error, imageName string) { | ||||||||||||||||||||||||||||||||||||||||||||||
| if err == nil { | ||||||||||||||||||||||||||||||||||||||||||||||
| logger.Info("Container scan completed successfully", logrus.Fields{"image": imageName}) | ||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Println() | ||||||||||||||||||||||||||||||||||||||||||||||
| color.Green("✅ Success: No vulnerabilities found matching the specified criteria") | ||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() == 1 { | ||||||||||||||||||||||||||||||||||||||||||||||
| logger.Warn("Container scan completed with vulnerabilities", logrus.Fields{ | ||||||||||||||||||||||||||||||||||||||||||||||
| "image": imageName, "exit_code": 1, | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Println() | ||||||||||||||||||||||||||||||||||||||||||||||
| color.Red("❌ Scanning failed: vulnerabilities found in the container image") | ||||||||||||||||||||||||||||||||||||||||||||||
| os.Exit(1) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| logger.Error("Failed to run Trivy", logrus.Fields{"error": err.Error()}) | ||||||||||||||||||||||||||||||||||||||||||||||
| color.Red("❌ Error: Failed to run Trivy: %v", err) | ||||||||||||||||||||||||||||||||||||||||||||||
| os.Exit(1) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+126
to
+148
|
||||||||||||||||||||||||||||||||||||||||||||||
| // handleTrivyResult processes the Trivy command result and exits appropriately | |
| func handleTrivyResult(err error, imageName string) { | |
| if err == nil { | |
| logger.Info("Container scan completed successfully", logrus.Fields{"image": imageName}) | |
| fmt.Println() | |
| color.Green("✅ Success: No vulnerabilities found matching the specified criteria") | |
| return | |
| } | |
| if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() == 1 { | |
| logger.Warn("Container scan completed with vulnerabilities", logrus.Fields{ | |
| "image": imageName, "exit_code": 1, | |
| }) | |
| fmt.Println() | |
| color.Red("❌ Scanning failed: vulnerabilities found in the container image") | |
| os.Exit(1) | |
| } | |
| logger.Error("Failed to run Trivy", logrus.Fields{"error": err.Error()}) | |
| color.Red("❌ Error: Failed to run Trivy: %v", err) | |
| os.Exit(1) | |
| } |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The color.Red function is being called with a format string and argument, but color.Red doesn't support format strings like Printf. This will print the literal format string with "%v" in it, not the actual error message. Use fmt.Fprintf with color.Output or color.New(color.FgRed).Printf instead.
Check failure on line 138 in cmd/container_scan.go
Codacy Production / Codacy Static Code Analysis
cmd/container_scan.go#L138
Detected non-static command inside Command.
Check failure on line 138 in cmd/container_scan.go
Codacy Production / Codacy Static Code Analysis
cmd/container_scan.go#L138
OS command injection is a critical vulnerability that can lead to a full system compromise as it may allow an adversary to pass in arbitrary commands or arguments to be executed.
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The severityFlag and pkgTypesFlag values are passed directly to Trivy without validation. While exec.Command properly separates arguments and prevents shell injection, malicious or malformed values could still cause unexpected Trivy behavior. Consider adding validation to ensure these flags contain only expected characters (e.g., alphanumeric, commas for severity levels).
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new command lacks test coverage. Similar commands in this repository have corresponding test files (e.g., analyze_test.go, config_test.go, init_test.go, upload_test.go). Consider adding a container_scan_test.go file to test the buildTrivyArgs function and the error handling logic in runContainerScan.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The color.Red function is being called with a format string and argument, but color.Red doesn't support format strings like Printf. This will print the literal format string with "%v" in it, not the actual error message. Use fmt.Fprintf with color.Output or color.New(color.FgRed).Printf instead.