Skip to content
Open
39 changes: 4 additions & 35 deletions binary-releases/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,9 @@
## [1.1304.0](https://github.com/snyk/snyk/compare/v1.1303.2...v1.1304.0) (2026-04-09)
## [1.1304.1](https://github.com/snyk/snyk/compare/v1.1304.0...v1.1304.1) (2026-04-27)

The Snyk CLI is being deployed to different deployment channels, users can select the stability level according to their needs. For details please see [this documentation](https://docs.snyk.io/snyk-cli/releases-and-channels-for-the-snyk-cli)

### Features

* **aibom**: Introduces the `snyk aibom test` command. ([2978044](https://github.com/snyk/snyk/commit/297804447be12f47b33f3ed9630a1db0a6994d70))
* **test, monitor, sbom**: Introduce `--maven-skip-wrapper` flag to force the use of a globally installed `mvn` command. ([0ee90ca](https://github.com/snyk/snyk/commit/0ee90caeec58d9a04843dfa9f64b35bc10f543f5), [ff31066](https://github.com/snyk/snyk/commit/ff31066d7f327f4ea25b758108bb62abed3b286a))
* **general**: Introduce explicit configuration for network retry `max-attempts`. ([1fbdf38](https://github.com/snyk/snyk/commit/1fbdf38647c30e29bfc6d6c3612241be9c5b90ca))
* **container**: Add deprecation warnings for `-shaded-jars-depth` and non-numeric values for `--nested-jars-depth`. ([321b6f5](https://github.com/snyk/snyk/commit/321b6f5516ae4f41dfd184911c3bd117c595ea68))
* **container**: Extend support for java runtime binary scanning ([b60473a](https://github.com/snyk/snyk/commit/b60473a20ffa55ea82c558c88783d4bcbc2dc54d))
* **mcp**: Improves auto-enable behavior for Snyk Code, promotes package health checks to stable. ([5f5898f](https://github.com/snyk/snyk/commit/5f5898f531ac84c7ee63feb7ab150fa1949373e7))
* **redteam**: Adds a vulnerability summary to scanned output. ([52eaf5a](https://github.com/snyk/snyk/commit/52eaf5afdc93c0d86324972387df8cecba0c6d48))
* **redteam**: Add `--json` flag support for list commands, `exhaustive` and `eager` modes. ([e962c4d](https://github.com/snyk/snyk/commit/e962c4d71cf4e077c7b262c91d69bcf40022d003))

The Snyk CLI is being deployed to different deployment channels, users can select the stability level according to their needs. For details please see [this documentation](https://docs.snyk.io/snyk-cli/releases-and-channels-for-the-snyk-cli)

### Bug Fixes

* **general**: Fix printing JSON output on stdout when only `--json-file-output` is specified. ([32f65f0](https://github.com/snyk/snyk/commit/32f65f099b716a216cbe0ad2ce9d8fef4be81704))
* **test**: Fixes an issue where no files were uploaded when using `--skip-unresolved`. ([71ca761](https://github.com/snyk/snyk/commit/71ca761b28ce3b4122c6778791433e46f82dcd29))
* **test**: Prevents scan failures when Maven builds succeed with non-fatal errors. ([b30db97](https://github.com/snyk/snyk/commit/b30db97978ab74e4bb66c1a6095019fae9259939))
* **test**: Fixes Go PackageURL generation and import path normalization for projects using `replace` directives. ([7c7a366](https://github.com/snyk/snyk/commit/7c7a3661a72b6e210aac0c4bd9d6acb2bd7bbd62), [ee7d72b](https://github.com/snyk/snyk/commit/ee7d72bb2c317dc571498e91239c8efb50257744))
* **test**: Improves SDK detection when host and SDK versions differ. ([96d0817](https://github.com/snyk/snyk/commit/96d0817068472ee4a8bb916fb54dd3296f384d7a))
* **test**: Ensures project names are populated when scanning NuGet projects from repository root. ([c043553](https://github.com/snyk/snyk/commit/c0435535a03fc7fcb3b35b671eda54c1f313593f))
* **container**: Snyk Container scans of tar files on Windows should now report vulnerabilities for Python application package files. ([9b86790](https://github.com/snyk/snyk/commit/9b867908c4a89046ee475be26a058dff2301f40d))
* **container**: Override packages with inaccurate pom.properties files ([b60473a](https://github.com/snyk/snyk/commit/b60473a20ffa55ea82c558c88783d4bcbc2dc54d))
* **test**: Ensure Yarn workspace pacakges matches are actual members defined in the root `package.json`. ([0dd6581](https://github.com/snyk/snyk/commit/0dd6581046148aceb4adedd6d0e70838970c9c84))
* **test**: Fix increased scan times when testing Golang projects. ([f2f5ba2](https://github.com/snyk/snyk/commit/f2f5ba2811d49156016c0ce6ac7d180d40ef1efc))
* **code**: Snyk Code scans now return clearer error message and exit codes when testing unsupported projects ([6f5b4e3](https://github.com/snyk/snyk/commit/6f5b4e3de3e1c2b7359215c7dde24af447a8b3df))
* **test**: Fix a bug where aliased packages were being resolved with the target name insted of the alias for yarn projects. ([dcbec6f](https://github.com/snyk/snyk/commit/dcbec6fb44397bbb1849ecafd0810d1f6b22fcb0))
* **test**: Fix a bug where Python packages with `.` characters in their name were incorrectly parsed to include `-` characters. ([9a2a36e](https://github.com/snyk/snyk/commit/9a2a36e90cd14139735c18b696bd9de50774f0fc))
* **deps**: Updates dependencies to fix vulnerabilities:
- CVE-2026-26996 ([8e7873f](https://github.com/snyk/snyk/commit/8e7873ff6e9f852ad57bac4c0024231892754e05))
- CVE-2026-29786 ([1a08533](https://github.com/snyk/snyk/commit/1a085335b0f3252818865d3df13e8abba815ffb2))
- CVE-2026-31802 ([1321575](https://github.com/snyk/snyk/commit/132157557160f9d60cba22e073925bdcc1b53656))
- CVE-2025-69873 ([8ff6aad](https://github.com/snyk/snyk/commit/8ff6aad6423d92cf39aad1de76fedb2cc10b5ef0))
- CVE-2026-33186 ([e98d9ef](https://github.com/snyk/snyk/commit/e98d9ef256c6ed21dc452bde7fed03581cc0a6db))
- CVE-2026-32283 ([d26e83f](https://github.com/snyk/cli/commit/d26e83fa349a5e21cfa907ee6a0d1cc29b7fe8c1))
- CVE-2026-29181 ([f5418b6](https://github.com/snyk/cli/commit/f5418b676671eb4e8f5268af2df7218f439e9261))

* **general**: Improved error handling to prioritize and surface the most relevant error and derive the correct exit code when multiple errors occur during CLI execution. ([b505a96](https://github.com/snyk/snyk/commit/b505a962897f67fafa1626be70d56ee7918c095b))
* **deps**: Updates dependencies to fix vulnerabilities for CVE-2026-4660 and CVE-2026-39883 ([2a95d85](https://github.com/snyk/snyk/commit/2a95d85aff26f562f9cf1400d106d9ed12e3beca))
5 changes: 2 additions & 3 deletions cliv2/cmd/cliv2/errorhandling.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ func decorateError(err error) error {

var errorCatalogError snyk_errors.Error
if !errors.As(err, &errorCatalogError) {
genericError := cli.NewGeneralCLIFailureError(err.Error())
genericError.StatusCode = 0
err = errors.Join(err, genericError)
genericError := cli.NewGeneralCLIFailureError(err.Error(), snyk_errors.WithCause(err))
return genericError
}
return err
}
Expand Down
44 changes: 22 additions & 22 deletions cliv2/cmd/cliv2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,9 +541,9 @@ func initExtensions(engine workflow.Engine, config configuration.Configuration)
engine.AddExtensionInitializer(workflows.InitConnectivityCheckWorkflow)
engine.AddExtensionInitializer(ignoreworkflow.InitIgnoreWorkflows)
engine.AddExtensionInitializer(agentscan.Init)
engine.AddExtensionInitializer(secrets.Init)

if config.GetBool(configuration.PREVIEW_FEATURES_ENABLED) {
engine.AddExtensionInitializer(secrets.Init)
config.Set("INTERNAL_USE_UFM_PRESENTER", true)
}
}
Expand Down Expand Up @@ -681,20 +681,23 @@ func MainWithErrorCode() int {
// ignore
}

outputError := err
allErrors := errorList

if err != nil {
errorList, err = processError(err, errorList)
allErrors, outputError = processError(err, errorList)

for _, tempError := range errorList {
for _, tempError := range allErrors {
if tempError != nil {
cliAnalytics.AddError(tempError)
}
}
}

displayError(err, globalEngine.GetUserInterface(), globalConfiguration, ctx)
displayError(outputError, globalEngine.GetUserInterface(), globalConfiguration, ctx)

exitCode := cliv2.DeriveExitCode(err)
globalLogger.Printf("Deriving Exit Code %d (cause: %v)", exitCode, err)
exitCode := cliv2.DeriveExitCode(outputError)
globalLogger.Printf("Deriving Exit Code %d (cause: %v)", exitCode, outputError)

updateInstrumentationDataBeforeSending(cliAnalytics, startTime, ua, exitCode)

Expand All @@ -711,38 +714,35 @@ func MainWithErrorCode() int {
}

if debugEnabled {
writeLogFooter(exitCode, errorList, globalConfiguration, networkAccess)
writeLogFooter(exitCode, allErrors, globalConfiguration, networkAccess)
}

return exitCode
}

func processError(err error, errorList []error) ([]error, error) {
// ensure to use generic fallback error catalog error if no other is available
err = decorateError(err)
resultError := decorateError(err)
resultErrorList := errorList

// filter legacycli terminate errors since it is only used for internal purposes
if exitErr, isExitError := err.(*exec.ExitError); isExitError && exitErr.ExitCode() == constants.SNYK_EXIT_CODE_TS_CLI_TERMINATED {
err = nil
if exitErr, isExitError := resultError.(*exec.ExitError); isExitError && exitErr.ExitCode() == constants.SNYK_EXIT_CODE_TS_CLI_TERMINATED {
resultError = nil
}

// add all errors to analytics
if err != nil {
errorList = append([]error{err}, errorList...)
// ensure to have all errors in the list for analytics, determining the most relevant error to surface to the user ...
if resultError != nil {
resultErrorList = append([]error{resultError}, resultErrorList...)
}

// create a single error from all errors
if len(errorList) == 1 {
err = errorList[0]
} else if len(errorList) > 1 {
err = errors.Join(errorList...)
}
// determine the most relevant error to surface to the user and derive the exit code from
resultError = cli_errors.FindMostRelevantError(resultErrorList)

// ensure to apply exit code mapping based on errors
if exitCode := mapErrorToExitCode(err); exitCode != unsetExitCode {
err = createErrorWithExitCode(exitCode, err)
if exitCode := mapErrorToExitCode(resultError); exitCode != unsetExitCode {
resultError = createErrorWithExitCode(exitCode, resultError)
}
return errorList, err
return resultErrorList, resultError
}

func setTimeout(config configuration.Configuration, onTimeout func()) {
Expand Down
124 changes: 124 additions & 0 deletions cliv2/cmd/cliv2/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,130 @@ type wrErr struct{ wraps error }
func (e *wrErr) Error() string { return "something went wrong" }
func (e *wrErr) Unwrap() error { return e.wraps }

func Test_processError(t *testing.T) {
t.Run("nil error returns nil", func(t *testing.T) {
errorList, err := processError(nil, nil)
assert.Nil(t, err)
assert.Empty(t, errorList)
})

t.Run("ExitError with exit code 1 preserves exit code", func(t *testing.T) {
// Create a real exec.ExitError by running a command that fails
cmd := exec.Command("sh", "-c", "exit 1")
exitErr := cmd.Run()
require.Error(t, exitErr)

errorList, err := processError(exitErr, nil)
assert.NotNil(t, err)
assert.Len(t, errorList, 1)

// The exit code should be preserved through DeriveExitCode
exitCode := cliv2.DeriveExitCode(err)
assert.Equal(t, 1, exitCode)
})

t.Run("ExitError with TS_CLI_TERMINATED is filtered out", func(t *testing.T) {
// Create a real exec.ExitError with the terminate code
cmd := exec.Command("sh", "-c", fmt.Sprintf("exit %d", constants.SNYK_EXIT_CODE_TS_CLI_TERMINATED))
exitErr := cmd.Run()
require.Error(t, exitErr)

errorList, err := processError(exitErr, nil)
assert.Nil(t, err)
assert.Empty(t, errorList)
})

t.Run("ErrorWithExitCode is preserved", func(t *testing.T) {
inputErr := &clierrors.ErrorWithExitCode{ExitCode: 1}
errorList, err := processError(inputErr, nil)

assert.NotNil(t, err)
assert.Len(t, errorList, 1)

var resultExitCode *clierrors.ErrorWithExitCode
assert.True(t, errors.As(err, &resultExitCode))
assert.Equal(t, 1, resultExitCode.ExitCode)

exitCode := cliv2.DeriveExitCode(err)
assert.Equal(t, 1, exitCode)
})

t.Run("multiple errors are joined and exit code is preserved", func(t *testing.T) {
// Create a real exec.ExitError
cmd := exec.Command("sh", "-c", "exit 1")
exitErr := cmd.Run()
require.Error(t, exitErr)

otherErr := fmt.Errorf("some other error")
errorList := []error{otherErr}

resultList, err := processError(exitErr, errorList)
assert.NotNil(t, err)
assert.Len(t, resultList, 2)

// The exit code should still be derivable
exitCode := cliv2.DeriveExitCode(err)
assert.Equal(t, 1, exitCode)
})

t.Run("snyk_errors.Error without special mapping gets exit code 2", func(t *testing.T) {
// A snyk_errors.Error that's not in the exit code mapping
snykErr := snyk_errors.Error{
Title: "Some Error",
ErrorCode: "SNYK-9999",
Level: "error",
}

errorList, err := processError(snykErr, nil)
assert.NotNil(t, err)
assert.Len(t, errorList, 1)

// This should result in exit code 2 since it's not mapped
exitCode := cliv2.DeriveExitCode(err)
assert.Equal(t, constants.SNYK_EXIT_CODE_ERROR, exitCode)
})

t.Run("maintenance error gets mapped to EX_TEMPFAIL", func(t *testing.T) {
maintenanceErr := snyk_errors.Error{
Title: "Maintenance",
ErrorCode: "SNYK-0099",
Level: "error",
}

errorList, err := processError(maintenanceErr, nil)
assert.NotNil(t, err)
assert.Len(t, errorList, 1)

// Should be mapped to EX_TEMPFAIL (75)
exitCode := cliv2.DeriveExitCode(err)
assert.Equal(t, constants.SNYK_EXIT_CODE_EX_TEMPFAIL, exitCode)
})

t.Run("maintenance error in error list takes priority", func(t *testing.T) {
// Create a real exec.ExitError with exit code 1
cmd := exec.Command("sh", "-c", "exit 1")
exitErr := cmd.Run()
require.Error(t, exitErr)

maintenanceErr := snyk_errors.Error{
Title: "Maintenance",
ErrorCode: "SNYK-0099",
Level: "error",
}

// Pass exitErr as the main error, maintenance error in the list
errorList := []error{maintenanceErr}
resultList, err := processError(exitErr, errorList)

assert.NotNil(t, err)
assert.Len(t, resultList, 2)

// Maintenance error should take priority, resulting in EX_TEMPFAIL
exitCode := cliv2.DeriveExitCode(err)
assert.Equal(t, constants.SNYK_EXIT_CODE_EX_TEMPFAIL, exitCode)
})
}

func loadJsonFile(t *testing.T, filename string) []byte {
t.Helper()

Expand Down
Loading