22package cmd
33
44import (
5+ "bytes"
56 "fmt"
7+ "io"
68 "os"
79 "os/exec"
810 "regexp"
@@ -28,19 +30,30 @@ var exitFunc = os.Exit
2830// CommandRunner interface for running external commands (allows mocking in tests)
2931type CommandRunner interface {
3032 Run (name string , args []string ) error
33+ // RunWithStderr runs the command; if stderr is not nil, Trivy stderr is written to both os.Stderr and stderr.
34+ RunWithStderr (name string , args []string , stderr io.Writer ) error
3135}
3236
3337// ExecCommandRunner runs commands using exec.Command
3438type ExecCommandRunner struct {}
3539
3640// Run executes a command and returns its exit error
3741func (r * ExecCommandRunner ) Run (name string , args []string ) error {
42+ return r .RunWithStderr (name , args , nil )
43+ }
44+
45+ // RunWithStderr runs the command; if stderr is not nil, command stderr is written to both os.Stderr and stderr.
46+ func (r * ExecCommandRunner ) RunWithStderr (name string , args []string , stderr io.Writer ) error {
3847 // #nosec G204 -- name comes from config (codacy-installed Trivy path),
3948 // and args are validated by validateImageName() which checks for shell metacharacters.
4049 // exec.Command passes arguments directly without shell interpretation.
4150 cmd := exec .Command (name , args ... )
4251 cmd .Stdout = os .Stdout
43- cmd .Stderr = os .Stderr
52+ if stderr != nil {
53+ cmd .Stderr = io .MultiWriter (os .Stderr , stderr )
54+ } else {
55+ cmd .Stderr = os .Stderr
56+ }
4457 return cmd .Run ()
4558}
4659
@@ -162,7 +175,6 @@ func handleTrivyNotFound(err error) {
162175 logger .Error ("Trivy not found" , logrus.Fields {"error" : err .Error ()})
163176 color .Red ("❌ Error: Trivy could not be installed or found" )
164177 fmt .Println ("Run 'codacy-cli init' if you have no project yet, then try container-scan again so Trivy can be installed automatically." )
165- fmt .Println ("exit-code 2" )
166178 exitFunc (2 )
167179}
168180
@@ -177,7 +189,6 @@ func executeContainerScan(imageName string) int {
177189 if err := validateImageName (imageName ); err != nil {
178190 logger .Error ("Invalid image name" , logrus.Fields {"image" : imageName , "error" : err .Error ()})
179191 color .Red ("❌ Error: %v" , err )
180- fmt .Println ("exit-code 2" )
181192 return 2
182193 }
183194 logger .Info ("Starting container scan" , logrus.Fields {"image" : imageName })
@@ -195,20 +206,36 @@ func executeContainerScan(imageName string) int {
195206 return printScanSummary (hasVulnerabilities == 1 )
196207}
197208
209+ // isScanFailure returns true if Trivy stderr indicates the scan failed (e.g. image not found, no runtime)
210+ // rather than a successful scan that found vulnerabilities. Trivy uses exit code 1 for both cases.
211+ func isScanFailure (stderr []byte ) bool {
212+ s := string (stderr )
213+ return strings .Contains (s , "FATAL" ) ||
214+ strings .Contains (s , "run error" ) ||
215+ strings .Contains (s , "image scan error" ) ||
216+ strings .Contains (s , "unable to find the specified image" )
217+ }
218+
198219// scanImage scans the image and returns: 0=no vulns, 1=vulns found, -1=error
199220func scanImage (imageName , trivyPath string ) int {
200221 fmt .Printf ("🔍 Scanning container image: %s\n \n " , imageName )
201222 args := buildTrivyArgs (imageName )
202223 logger .Info ("Running Trivy container scan" , logrus.Fields {"command" : fmt .Sprintf ("%s %v" , trivyPath , args )})
203224
204- if err := commandRunner .Run (trivyPath , args ); err != nil {
205- if getExitCode (err ) == 1 {
225+ var stderrBuf bytes.Buffer
226+ if err := commandRunner .RunWithStderr (trivyPath , args , & stderrBuf ); err != nil {
227+ code := getExitCode (err )
228+ if code == 1 && isScanFailure (stderrBuf .Bytes ()) {
229+ logger .Error ("Scan failed (e.g. image not found or no container runtime)" , logrus.Fields {"image" : imageName , "error" : err .Error ()})
230+ color .Red ("❌ Scanning failed: unable to scan the container image (e.g. image not found or no container runtime)" )
231+ return - 1
232+ }
233+ if code == 1 {
206234 logger .Warn ("Vulnerabilities found in image" , logrus.Fields {"image" : imageName })
207235 return 1
208236 }
209237 logger .Error ("Failed to run Trivy" , logrus.Fields {"error" : err .Error (), "image" : imageName })
210238 color .Red ("❌ Error: Failed to run Trivy for %s: %v" , imageName , err )
211- fmt .Println ("exit-code 2" )
212239 return - 1
213240 }
214241 logger .Info ("No vulnerabilities found in image" , logrus.Fields {"image" : imageName })
@@ -220,7 +247,6 @@ func printScanSummary(hasVulnerabilities bool) int {
220247 if hasVulnerabilities {
221248 logger .Warn ("Container scan completed with vulnerabilities" , logrus.Fields {})
222249 color .Red ("❌ Scanning failed: vulnerabilities found in the container image" )
223- fmt .Println ("exit-code 1" )
224250 return 1
225251 }
226252 logger .Info ("Container scan completed successfully" , logrus.Fields {})
0 commit comments