VYPR
High severity7.2NVD Advisory· Published Jun 26, 2025· Updated Apr 29, 2026

CVE-2025-6624

CVE-2025-6624

Description

Versions of the package snyk before 1.1297.3 are vulnerable to Insertion of Sensitive Information into Log File through local Snyk CLI debug logs. Container Registry credentials provided via environment variables or command line arguments can be exposed when executing Snyk CLI in DEBUG or DEBUG/TRACE mode. The issue affects the following Snyk commands: 1. When snyk container test or snyk container monitor commands are run against a container registry, with debug mode enabled, the container registry credentials may be written into the local Snyk CLI debug log. This only happens with credentials specified in environment variables (SNYK_REGISTRY_USERNAME and SNYK_REGISTRY_PASSWORD), or in the CLI (--password/-p and --username/-u). 2. When snyk auth command is executed with debug mode enabled AND the log level is set to TRACE, the Snyk access / refresh credential tokens used to connect the CLI to Snyk may be written into the local CLI debug logs. 3. When snyk iac test is executed with a Remote IAC Custom rules bundle, debug mode enabled, AND the log level is set to TRACE, the docker registry token may be written into the local CLI debug logs.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
snyknpm
< 1.1297.31.1297.3
github.com/snyk/go-application-frameworkGo
>= 0

Affected products

1
  • cpe:2.3:a:snyk:snyk_cli:*:*:*:*:*:*:*:*
    Range: <1.1297.3

Patches

2
ca7ba7d72e68

fix: improve the sanitization of credentials in local debug logs (#376)

https://github.com/snyk/go-application-frameworkKaspar LyngsieJun 23, 2025via ghsa
6 files changed · +262 89
  • pkg/analytics/analytics.go+5 17 modified
    @@ -2,6 +2,8 @@ package analytics
     
     import (
     	"bytes"
    +	"github.com/snyk/go-application-framework/pkg/logging"
    +
     	//nolint:gosec // insecure sha1 used for legacy identifier
     	"crypto/sha1"
     	"encoding/json"
    @@ -98,22 +100,8 @@ type dataOutput struct {
     	Data analyticsOutput `json:"data"`
     }
     
    -var (
    -	// sensitiveFieldNames is a list of field names that should be sanitized.
    -	// data sanitization is used to prevent sensitive data from being sent to the analytics server.
    -	sensitiveFieldNames = []string{
    -		"headers",
    -		"user",
    -		"passw",
    -		"token",
    -		"key",
    -		"secret",
    -	}
    -)
    -
     const (
    -	sanitizeReplacementString string = "REDACTED"
    -	apiEndpoint               string = "/v1/analytics/cli"
    +	apiEndpoint string = "/v1/analytics/cli"
     )
     
     // New creates a new Analytics instance.
    @@ -243,7 +231,7 @@ func (a *AnalyticsImpl) GetRequest() (*http.Request, error) {
     		return nil, err
     	}
     
    -	outputJson, err = SanitizeValuesByKey(sensitiveFieldNames, sanitizeReplacementString, outputJson)
    +	outputJson, err = SanitizeValuesByKey(logging.SENSITIVE_FIELD_NAMES, logging.SANITIZE_REPLACEMENT_STRING, outputJson)
     	if err != nil {
     		return nil, err
     	}
    @@ -252,7 +240,7 @@ func (a *AnalyticsImpl) GetRequest() (*http.Request, error) {
     	if err != nil {
     		return nil, err
     	}
    -	outputJson, err = SanitizeUsername(user.Username, user.HomeDir, sanitizeReplacementString, outputJson)
    +	outputJson, err = SanitizeUsername(user.Username, user.HomeDir, logging.SANITIZE_REPLACEMENT_STRING, outputJson)
     	if err != nil {
     		return nil, err
     	}
    
  • pkg/analytics/analytics_test.go+5 4 modified
    @@ -3,6 +3,7 @@ package analytics
     import (
     	"encoding/json"
     	"fmt"
    +	"github.com/snyk/go-application-framework/pkg/logging"
     	"io"
     	"net/http"
     	"os/user"
    @@ -74,7 +75,7 @@ func Test_Basic(t *testing.T) {
     			body, err := io.ReadAll(request.Body)
     			assert.Nil(t, err)
     			// expect no CLI args to be sent to analytics (CLI-586)
    -			assert.Equal(t, 0, strings.Count(string(body), sanitizeReplacementString))
    +			assert.Equal(t, 0, strings.Count(string(body), logging.SANITIZE_REPLACEMENT_STRING))
     
     			var requestBody dataOutput
     			err = json.Unmarshal(body, &requestBody)
    @@ -118,11 +119,11 @@ func Test_SanitizeValuesByKey(t *testing.T) {
     	}
     
     	// test input
    -	filter := sensitiveFieldNames
    +	filter := logging.SENSITIVE_FIELD_NAMES
     	input, err := json.Marshal(inputStruct)
     	assert.NoError(t, err)
     
    -	replacement := sanitizeReplacementString
    +	replacement := logging.SANITIZE_REPLACEMENT_STRING
     
     	fmt.Println("Before: " + string(input))
     
    @@ -171,7 +172,7 @@ func Test_SanitizeUsername(t *testing.T) {
     	// 2. with domain name
     	// 3. user name and path are different
     	// 4. current OS values
    -	replacement := "REDACTED"
    +	replacement := "***"
     	inputData := []input{
     		{
     			userName:     "some.user",
    
  • pkg/analytics/instrumentation_collector.go+3 2 modified
    @@ -4,6 +4,7 @@ import (
     	"encoding/json"
     	"errors"
     	"fmt"
    +	"github.com/snyk/go-application-framework/pkg/logging"
     	"os/user"
     	"time"
     
    @@ -180,7 +181,7 @@ func (ic *instrumentationCollectorImpl) sanitizeExtensionData(logger *zerolog.Lo
     	}
     
     	var sanitized []byte
    -	sanitized, err = SanitizeValuesByKey(sensitiveFieldNames, sanitizeReplacementString, extension)
    +	sanitized, err = SanitizeValuesByKey(logging.SENSITIVE_FIELD_NAMES, logging.SANITIZE_REPLACEMENT_STRING, extension)
     	if err != nil {
     		logger.Printf("failed to sanitize extension, removing object from analytics payload as sanitzation was not possible: %v", err)
     		return result
    @@ -192,7 +193,7 @@ func (ic *instrumentationCollectorImpl) sanitizeExtensionData(logger *zerolog.Lo
     		return result
     	}
     
    -	sanitized, err = SanitizeUsername(u.Username, u.HomeDir, sanitizeReplacementString, sanitized)
    +	sanitized, err = SanitizeUsername(u.Username, u.HomeDir, logging.SANITIZE_REPLACEMENT_STRING, sanitized)
     	if err != nil {
     		logger.Printf("failed to sanitize user information in extension payload, removing object from analytics payload as sanitzation was not possible: %v", err)
     		return result
    
  • pkg/analytics/instrumentation_collector_test.go+1 1 modified
    @@ -214,7 +214,7 @@ func Test_InstrumentationCollector(t *testing.T) {
     
     		mockExtension := map[string]interface{}{
     			"strings":  "hello world",
    -			"password": "REDACTED",
    +			"password": "***",
     		}
     
     		expectedV2InstrumentationObject.Data.Attributes.Interaction.Extension = &mockExtension
    
  • pkg/logging/scrubbingLogWriter.go+85 12 modified
    @@ -21,6 +21,7 @@ import (
     	"io"
     	"os/user"
     	"regexp"
    +	"sort"
     	"strings"
     	"sync"
     	"time"
    @@ -31,8 +32,18 @@ import (
     	"github.com/snyk/go-application-framework/pkg/configuration"
     )
     
    -const redactMask string = "***"
     const MAX_WRITE_RETRIES = 10
    +const SANITIZE_REPLACEMENT_STRING string = "***"
    +
    +// SENSITIVE_FIELD_NAMES is a list of field names that should be sanitized.
    +var SENSITIVE_FIELD_NAMES = []string{
    +	"headers",
    +	"user",
    +	"passw",
    +	"token",
    +	"key",
    +	"secret",
    +}
     
     type ScrubbingLogWriter interface {
     	AddTerm(term string, matchGroup int)
    @@ -146,37 +157,44 @@ func addMandatoryMasking(dict ScrubbingDict) ScrubbingDict {
     		groupToRedact: 3,
     		regex:         regexp.MustCompile(s),
     	}
    +
     	s = fmt.Sprintf(`([t|T]oken )(%s)`, charGroup)
     	dict[s] = scrubStruct{
     		groupToRedact: 2,
     		regex:         regexp.MustCompile(s),
     	}
    +
     	s = fmt.Sprintf(`([b|B]earer )(%s)`, charGroup)
     	dict[s] = scrubStruct{
     		groupToRedact: 2,
     		regex:         regexp.MustCompile(s),
     	}
    +
     	s = fmt.Sprintf(`([b|B]asic )(%s)`, charGroup)
     	dict[s] = scrubStruct{
     		groupToRedact: 2,
     		regex:         regexp.MustCompile(s),
     	}
    +
     	s = fmt.Sprintf("(gh[ps])_(%s)", charGroup)
     	dict[s] = scrubStruct{
     		groupToRedact: 2,
     		regex:         regexp.MustCompile(s),
     	}
    +
     	s = fmt.Sprintf("(github_pat_)(%s)", charGroup)
     	dict[s] = scrubStruct{
     		groupToRedact: 2,
     		regex:         regexp.MustCompile(s),
     	}
    +
     	// github
     	s = fmt.Sprintf(`(access_token[\\="\s:]+)(%s)&?`, charGroup)
     	dict[s] = scrubStruct{
     		groupToRedact: 2,
     		regex:         regexp.MustCompile(s),
     	}
    +
     	s = fmt.Sprintf(`(refresh_token[\\="\s:]+)(%s)&?`, charGroup)
     	dict[s] = scrubStruct{
     		groupToRedact: 2,
    @@ -195,23 +213,70 @@ func addMandatoryMasking(dict ScrubbingDict) ScrubbingDict {
     		regex:         regexp.MustCompile(s),
     	}
     
    -	// Scrub all kinds of "username/password" combinations sent to the log in various cases and with or without equal signs, etc.
    -	s = `(?im)(\b\w*username\b|\-u|'u|"u)["'=:\s]+([\S\s]*?)["'\s]`
    +	// Hide whatever is the current username
    +	u, err := user.Current()
    +	if err == nil {
    +		s = fmt.Sprintf(`\b%s\b`, regexp.QuoteMeta(u.Username))
    +		addTermToDict(s, 0, dict)
    +	}
    +
    +	// The legacy CLI's snyk-config package prints the entire configuration in debug mode.
    +	// It begins with some pseudo-JSON structure, which we can redact.
    +	s = `(?s)_:\s*\[(?<everything_inside_hard_brackets>.*)\]`
    +	dict[s] = scrubStruct{
    +		groupToRedact: 1,
    +		regex:         regexp.MustCompile(s),
    +	}
    +
    +	// JSON-formatted data, in general
    +	kws := strings.Join(SENSITIVE_FIELD_NAMES, "|")
    +	s = fmt.Sprintf(`(?i)"[^"]*(?<json_key>%s)[^"]*"\s*:\s*"(?<json_value>[^"]*)"`, kws)
     	dict[s] = scrubStruct{
     		groupToRedact: 2,
     		regex:         regexp.MustCompile(s),
     	}
     
    -	s = `(?im)(\b\w*password\b|\-p|'p|"p)["'=:\s]+([\S\s]*?)["'\s]`
    +	// CLI argument mapping from the snyk-config debug logging
    +	// I.e., if --argument=value is passed, it will be logged as { 'argument=value': true }
    +	s = fmt.Sprintf(`(?im)(%s)[^=]*=(?P<value>.*)['"]`, kws)
     	dict[s] = scrubStruct{
     		groupToRedact: 2,
     		regex:         regexp.MustCompile(s),
     	}
     
    -	u, err := user.Current()
    -	if err == nil {
    -		s = fmt.Sprintf(`\b%s\b`, regexp.QuoteMeta(u.Username))
    -		addTermToDict(s, 0, dict)
    +	// Same as above, only with short form
    +	shorts := []string{"p", "u"}
    +	shortForm := strings.Join(shorts, "")
    +	s = fmt.Sprintf(`(?im)'[%s]=(?<value>.*)'`, shortForm)
    +	dict[s] = scrubStruct{
    +		groupToRedact: 2,
    +		regex:         regexp.MustCompile(s),
    +	}
    +
    +	// Specific short-form scrubbing of the JSON-ish log structures
    +	// Appear in the snyk-config debug logging as various constellations of { 'u': 'john.doe', } with or without quotes,
    +	// and values can contain spaces, double and/or single quotes.
    +
    +	s = fmt.Sprintf(`(?i)(?<short_form_key>\b[%s]\b)[,'":]+\s*(?:['"](?<short_form_value>.*)['"]|([^,'"\s]+))[,}]?`, shortForm)
    +	dict[s] = scrubStruct{
    +		groupToRedact: 2,
    +		regex:         regexp.MustCompile(s),
    +	}
    +
    +	// CLI argument-style-specific scrubbing
    +	// Many cases are already covered by the JSON scrubbing above, thus this might seem incomplete.
    +	// Refer to the unit tests for the full set of covered cases.
    +	s = fmt.Sprintf(`(?im)\-[%s][\s=](?<short_form_value>\S*)`, shortForm)
    +	dict[s] = scrubStruct{
    +		groupToRedact: 1,
    +		regex:         regexp.MustCompile(s),
    +	}
    +
    +	// Long-form, rest is covered by the JSON scrubbing above
    +	s = fmt.Sprintf(`(?im)--(?<argument_key>[^=\s]*(?:%s)[^=\s]*)[\s=]['"]?(?<argument_value>\S*)['"]?`, kws)
    +	dict[s] = scrubStruct{
    +		groupToRedact: 2,
    +		regex:         regexp.MustCompile(s),
     	}
     
     	return dict
    @@ -226,14 +291,22 @@ func (w *scrubbingLevelWriter) Write(p []byte) (int, error) {
     
     func scrub(p []byte, scrubDict ScrubbingDict) []byte {
     	s := string(p)
    -	for _, entry := range scrubDict {
    +
    +	// The dictionary order is important here, as we want potentially overlapping regexes to be applied
    +	// in a specific order every time. Since dictionaries are unordered, we sort the keys here.
    +	keys := make([]string, 0, len(scrubDict))
    +	for k := range scrubDict {
    +		keys = append(keys, k)
    +	}
    +	sort.Strings(keys)
    +	for _, key := range keys {
    +		entry := scrubDict[key]
     		matches := entry.regex.FindAllStringSubmatch(s, -1)
     		for _, match := range matches {
    -			if match[entry.groupToRedact] == "" {
    +			if entry.groupToRedact >= len(match) || match[entry.groupToRedact] == "" {
     				continue
     			}
    -
    -			s = strings.Replace(s, match[entry.groupToRedact], redactMask, -1)
    +			s = strings.Replace(s, match[entry.groupToRedact], SANITIZE_REPLACEMENT_STRING, -1)
     		}
     	}
     	return []byte(s)
    
  • pkg/logging/scrubbingLogWriter_test.go+163 53 modified
    @@ -19,16 +19,15 @@ package logging
     import (
     	"bytes"
     	"fmt"
    +	"github.com/snyk/go-application-framework/pkg/auth"
    +	"github.com/snyk/go-application-framework/pkg/configuration"
    +	"github.com/stretchr/testify/require"
     	"os/user"
     	"regexp"
     	"testing"
     
     	"github.com/rs/zerolog"
     	"github.com/stretchr/testify/assert"
    -	"github.com/stretchr/testify/require"
    -
    -	"github.com/snyk/go-application-framework/pkg/auth"
    -	"github.com/snyk/go-application-framework/pkg/configuration"
     )
     
     type mockWriter struct {
    @@ -96,7 +95,7 @@ func TestScrubbingIoWriter(t *testing.T) {
     
     	pattern := "%s for my account, including my %s"
     	patternWithSecret := fmt.Sprintf(pattern, "secret", "token")
    -	patternWithMaskedSecret := fmt.Sprintf(pattern, redactMask, redactMask)
    +	patternWithMaskedSecret := fmt.Sprintf(pattern, SANITIZE_REPLACEMENT_STRING, SANITIZE_REPLACEMENT_STRING)
     
     	bufioWriter := bytes.NewBufferString("")
     	writer := NewScrubbingIoWriter(bufioWriter, scrubDict)
    @@ -255,9 +254,24 @@ func TestAddDefaults(t *testing.T) {
     			expected: "refresh_token=***&expire",
     		},
     		{
    -			name:     "token in json",
    -			input:    `"token":"alittlesecret"`,
    -			expected: `"token":"***"`,
    +			name:     "access token in json",
    +			input:    `{"access_token":"secret_access_token"}`,
    +			expected: `{"access_token":"***"}`,
    +		},
    +		{
    +			name:     "access token in json with multiple fields",
    +			input:    `{"unrelated":"foobar", "access_token":"secret_access_token","expires_in":300,"issued_at":"2025-06-20T15:32:38.38731422Z"}`,
    +			expected: `{"unrelated":"foobar", "access_token":"***","expires_in":300,"issued_at":"2025-06-20T15:32:38.38731422Z"}`,
    +		},
    +		{
    +			name:     "any type of token in json",
    +			input:    `{"something_token":"secret_access_token"}`,
    +			expected: `{"something_token":"***"}`,
    +		},
    +		{
    +			name:     "any type of token in json with postfix",
    +			input:    `{"something_token_and_a_postfix":"secret_access_token"}`,
    +			expected: `{"something_token_and_a_postfix":"***"}`,
     		},
     		{
     			name:     "SNYK_TOKEN",
    @@ -267,68 +281,164 @@ func TestAddDefaults(t *testing.T) {
     		{
     			name:     "username",
     			input:    fmt.Sprintf("User %s.%s is repeatedly mentioned, but not partially.", u.Username, u.Username),
    -			expected: fmt.Sprintf("User %s.%s is repeatedly mentioned, but not partially.", redactMask, redactMask),
    +			expected: fmt.Sprintf("User %s.%s is repeatedly mentioned, but not partially.", SANITIZE_REPLACEMENT_STRING, SANITIZE_REPLACEMENT_STRING),
    +		},
    +		{
    +			name: "JSON-ish argument structure with verbatim output from snyk-config",
    +			input: `_: [
    +						'gcr.io/distroless/nodejs:latest',
    +						'john.doe',
    +						'hunter2',
    +						[other things]
    +				  	],`,
    +			expected: `_: [***],`,
     		},
     		{
    -			name: "username/password passed as an argument somewhere in the log",
    -			input: `
    -			{
    -				_: [ 'test' ],
    +			name: "cli argument mapping from snyk-config debug logging",
    +			input: `{
    +				username: 'username-set',
    +				'username=john.doe': true,
     				password: 'password-set',
     				'password=foobar': true,
    -				'-u user',
    -				'-p 1234',
    -				u: true,
    +				'u=foobar': true,
    +				'password-with-double-quotes=foo"bar': true,
    +				"password-with-single-quotes=foo'bar": true,
    +				'password-with-comma=foo,bar': true,
    +				'my-password-with-spaces=foo bar': true,
    +				'my-password-thats-solid=solid': true,
    +				'p=foobar': true,
     				debug: true,
    -				'log-level': 'trace',
    -				"REGISTRY_USERNAME": "user",
    -				"REGISTRY_PASSWORD": "foobar",
    -				"API": "https://api.snyk.io",
    -				"INTEGRATION_NAME": "CLI_V1_PLUGIN"
    +				'log-level': 'trace'
     			}`,
    -			expected: `
    -			{
    -				_: [ 'test' ],
    -				password: '***',
    +			expected: `{
    +				username: 'username-set',
    +				'username=***': true,
    +				password: 'password-set',
     				'password=***': true,
    -				'-u ***',
    -				'-p ***',
    -				u: true,
    +				'u=***': true,
    +				'password-with-double-quotes=***': true,
    +				"password-with-single-quotes=***": true,
    +				'password-with-comma=***': true,
    +				'my-password-with-spaces=***': true,
    +				'my-password-thats-***=***': true,
    +				'p=***': true,
     				debug: true,
    -				'log-level': 'trace',
    +				'log-level': 'trace'
    +			}`,
    +		},
    +		{
    +			name: "username and password constellations passed in a JSON-ish structure with verbatim output from snyk-config",
    +			input: `{
    +				unrelated: dont-scrub,
    +				"unrelated": "dont-scrub",
    +				'unrelated': 'dont-scrub',
    +				unrelated: dont-scrub,
    +				'-p': 'hunter2',
    +				'u': 'john.doe',
    +				'p': 'hunter2',
    +				'p': 'hun"ter2',
    +				'p': 'hun ter2',
    +				'p': 'hun,ter2',
    +				"u": "john.doe",
    +				"u": "john'doe",
    +				"u": "john,doe",
    +				"u": "john doe",
    +				"p": "hunter2",
    +				u: 'john.doe',
    +				p: 'hunter2',
    +				"REGISTRY_USERNAME": "user",
    +				"REGISTRY_PASSWORD": "foobar",
    +				"MORE_UNRELATED": "DONT_SCRUB"
    +			}`,
    +			expected: `{
    +				unrelated: dont-scrub,
    +				"unrelated": "dont-scrub",
    +				'unrelated': 'dont-scrub',
    +				unrelated: dont-scrub,
    +				'-p': '***',
    +				'u': '***',
    +				'p': '***',
    +				'p': '***',
    +				'p': '***',
    +				'p': '***',
    +				"u": "***",
    +				"u": "***",
    +				"u": "***",
    +				"u": "***",
    +				"p": "***",
    +				u: '***',
    +				p: '***',
     				"REGISTRY_USERNAME": "***",
     				"REGISTRY_PASSWORD": "***",
    -				"API": "https://api.snyk.io",
    -				"INTEGRATION_NAME": "CLI_V1_PLUGIN"
    +				"MORE_UNRELATED": "DONT_SCRUB"
     			}`,
     		},
     		{
    -			name: "Various authentication request/response combinations",
    -			input: `
    +			name: "CLI arguments logged to the debug logs (multi-line)",
    +			input: `Arguments:[
    +			container test gcr.io/distroless/nodejs:latest
    +			--platform=linux/arm64
    +			--unrelated-argument
    +			--unrelated-argument-with-value "value"
    +			--unrelated-argument-with-equals-sign="value"
    +			-u john.doe
    +			-p hunter2
    +			--username john.doe
    +			--password hunter2
    +			--a-password-with-secret-in-the-value 'super-secret-password'
    +			--a-password-with-an-equals-sign='hunter2'
    +			--a-password-with-spaces='hun ter2'
    +			--a-password-with-double-quotes='hun"ter2'
    +			--a-password-with-single-quotes="hun'ter2"
    +			--token "token"
    +			--password-with-no-value
    +			--another-unrelated-at-the-end
    +			--log-level=trace
    +			-d
    +			--debug
    +		]`,
    +			expected: `Arguments:[
    +			container test gcr.io/distroless/nodejs:latest
    +			--platform=linux/arm64
    +			--unrelated-argument
    +			--unrelated-argument-with-value "value"
    +			--unrelated-argument-with-equals-sign="value"
    +			-u ***
    +			-p ***
    +			--username ***
    +			--password ***
    +			--a-password-with-secret-in-the-value '***
    +			--a-password-with-an-equals-sign=***
    +			--a-password-with-spaces=***
    +			--a-password-with-double-quotes=***
    +			--a-password-with-single-quotes=***
    +			--token "***
    +			--password-with-no-value
    +			--another-unrelated-at-the-end
    +			--log-level=trace
    +			-d
    +			--debug
    +		]`,
    +		},
     		{
    -			"token": "super_secret_token",
    -			"access_token": "super_secret_token",
    -			"expires_in": 3599,
    -			"refresh_expires_in": 15552000,
    -			"refresh_token": "super_secret_refresh_token",
    -			"scope": "org.read",
    -			"token_type": "bearer"
    -		}`,
    -			expected: `
    +			name:     "CLI arguments logged to the debug logs (same line, short-form, no equals signs)",
    +			input:    `container test gcr.io/distroless/nodejs:latest --platform=linux/arm64 --unrelated-argument --unrelated-argument-with-value "value" --unrelated-argument-with-equals-sign="value" -u john.doe -p hunter2 --log-level=trace`,
    +			expected: `container test gcr.io/distroless/nodejs:latest --platform=linux/arm64 --unrelated-argument --unrelated-argument-with-value "value" --unrelated-argument-with-equals-sign="value" -u *** -p *** --log-level=trace`,
    +		},
    +		{
    +			name:     "CLI arguments logged to the debug logs (same line, short-form, with equals signs)",
    +			input:    `container test gcr.io/distroless/nodejs:latest --platform=linux/arm64 --unrelated-argument --unrelated-argument-with-value "value" --unrelated-argument-with-equals-sign="value" -u=john.doe -p=hunter2 --log-level=trace`,
    +			expected: `container test gcr.io/distroless/nodejs:latest --platform=linux/arm64 --unrelated-argument --unrelated-argument-with-value "value" --unrelated-argument-with-equals-sign="value" -u=*** -p=*** --log-level=trace`,
    +		},
     		{
    -			"token": "***",
    -			"access_token": "***",
    -			"expires_in": 3599,
    -			"refresh_expires_in": 15552000,
    -			"refresh_token": "***",
    -			"scope": "org.read",
    -			"token_type": "bearer"
    -		}`,
    +			name:     "CLI arguments logged to the debug logs (same line, long-form, no equals signs)",
    +			input:    `container test ubuntu:latest --username john.doe --password solidpassword -d`,
    +			expected: `container test ubuntu:latest --username *** --password *** -d`,
     		},
     		{
    -			name:     "Various passed arguments",
    -			input:    "Arguments:[container test gcr.io/distroless/nodejs:latest --platform=linux/arm64 --u=johndoe --p=hunter2 --log-level=trace -d]",
    -			expected: "Arguments:[container test gcr.io/distroless/nodejs:latest --platform=linux/arm64 --u=*** --p=*** --log-level=trace -d]",
    +			name:     "CLI arguments logged to the debug logs (same line, long-form, with equals signs)",
    +			input:    `container test ubuntu:latest --username=john.doe --password=solidpassword -d`,
    +			expected: `container test ubuntu:latest --username=*** --password=*** -d`,
     		},
     	}
     	for _, test := range tests {
    
38322f377da7

fix(logging): Improve the sanitization of credentials in local debug logs

https://github.com/snyk/cliLucian RJun 19, 2025via ghsa
8 files changed · +118 25
  • binary-releases/RELEASE_NOTES.md+2 5 modified
    @@ -1,10 +1,7 @@
    -## [1.1297.2](https://github.com/snyk/snyk/compare/v1.1297.1...1.1297.2) (2025-06-16)
    +## [1.1297.3](https://github.com/snyk/snyk/compare/v1.1297.2...1.1297.3) (2025-06-23)
     
     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
     
    -* **logging:** Improves the sanitization of credentials in local debug logs. ([e054455](https://github.com/snyk/snyk/commit/e054455eab8e686f19c165a8bad86259103a5f5d))
    -* **language-server:** IDE Connectivity for Proxy Users: Fixes an issue where IDE plugins could fail to connect when operating behind an NTLM proxy.
    -* **language-server:** Snyk Code Local Engine Fix: Addresses a regression that prevented the Snyk Code Local Engine (SCLE) from functioning correctly within the IDEs.
    -
    +* **logging:** Improves the sanitization of credentials in local debug logs. ([e50fb22](https://github.com/snyk/snyk/commit/e50fb22cde76b3ca01e6a21d5495534c06586df9))
    \ No newline at end of file
    
  • cliv2/go.mod+1 1 modified
    @@ -16,7 +16,7 @@ require (
     	github.com/snyk/cli-extension-sbom v0.0.0-20250422133603-a5ae6fdf0934
     	github.com/snyk/container-cli v0.0.0-20250321132345-1e2e01681dd7
     	github.com/snyk/error-catalog-golang-public v0.0.0-20250429130542-564b0605020e
    -	github.com/snyk/go-application-framework v0.0.0-20250612130357-31093e6eb8ad
    +	github.com/snyk/go-application-framework v0.0.0-20250623124518-ca7ba7d72e68
     	github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65
     	github.com/snyk/snyk-iac-capture v0.6.5
     	github.com/snyk/snyk-ls v0.0.0-20250613113919-2b232b9d448d
    
  • cliv2/go.sum+2 2 modified
    @@ -808,8 +808,8 @@ github.com/snyk/container-cli v0.0.0-20250321132345-1e2e01681dd7 h1:/2+2piwQtB9f
     github.com/snyk/container-cli v0.0.0-20250321132345-1e2e01681dd7/go.mod h1:38w+dcAQp9eG3P5t2eNS9eG0reut10AeJjLv5lJ5lpM=
     github.com/snyk/error-catalog-golang-public v0.0.0-20250429130542-564b0605020e h1:XFGkHDWA8JTPLr82QzoKVqGytofEYBf68VqoUq8yvXk=
     github.com/snyk/error-catalog-golang-public v0.0.0-20250429130542-564b0605020e/go.mod h1:Ytttq7Pw4vOCu9NtRQaOeDU2dhBYUyNBe6kX4+nIIQ4=
    -github.com/snyk/go-application-framework v0.0.0-20250612130357-31093e6eb8ad h1:RpUp1oayxILiWL6jGnXgAYiz7E44minwFEeDXJU3Xc0=
    -github.com/snyk/go-application-framework v0.0.0-20250612130357-31093e6eb8ad/go.mod h1:Hy8dugDhTPRPe99Bf4mG7zeh7+OobdWfX5dzhbeQQsU=
    +github.com/snyk/go-application-framework v0.0.0-20250623124518-ca7ba7d72e68 h1:eTR15PecAEX3F9HvptgPWI1Z936qirRoIGCiK3kALfU=
    +github.com/snyk/go-application-framework v0.0.0-20250623124518-ca7ba7d72e68/go.mod h1:Hy8dugDhTPRPe99Bf4mG7zeh7+OobdWfX5dzhbeQQsU=
     github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65 h1:CEQuYv0Go6MEyRCD3YjLYM2u3Oxkx8GpCpFBd4rUTUk=
     github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65/go.mod h1:88KbbvGYlmLgee4OcQ19yr0bNpXpOr2kciOthaSzCAg=
     github.com/snyk/policy-engine v0.33.2 h1:ZxD6/RQ4vqUAXa64V72SsGjZ8vmnBgZNGYQxMIqctYo=
    
  • cliv2/internal/cliv2/cliv2.go+1 1 modified
    @@ -427,7 +427,7 @@ func (c *CLI) executeV1Default(proxyInfo *proxy.ProxyInfo, passThroughArgs []str
     		c.DebugLogger.Println("Launching: ")
     		c.DebugLogger.Println("  ", c.v1BinaryLocation)
     		c.DebugLogger.Println(" With Arguments:")
    -		c.DebugLogger.Println("  ", strings.Join(passThroughArgs, ", "))
    +		c.DebugLogger.Println(" ", strings.Join(passThroughArgs, " "))
     		c.DebugLogger.Println(" With Environment: ")
     
     		variablesMap := utils.ToKeyValueMap(snykCmd.Env, "=")
    
  • cliv2/pkg/basic_workflows/legacycli.go+0 1 modified
    @@ -84,7 +84,6 @@ func legacycliWorkflow(
     	proxyAuthenticationMechanism := httpauth.AuthenticationMechanismFromString(proxyAuthenticationMechanismString)
     	analyticsDisabled := config.GetBool(configuration.ANALYTICS_DISABLED)
     
    -	debugLogger.Print("Arguments:", args)
     	debugLogger.Print("Use StdIO:", useStdIo)
     	debugLogger.Print("Working directory:", workingDirectory)
     
    
  • src/cli/args.ts+0 5 modified
    @@ -8,7 +8,6 @@ import {
       SupportedUserReachableFacingCliArgs,
     } from '../lib/types';
     import { getContainerImageSavePath } from '../lib/container';
    -import { obfuscateArgs } from '../lib/utils';
     
     export declare interface Global extends NodeJS.Global {
       ignoreUnknownCA: boolean;
    @@ -119,8 +118,6 @@ export function args(rawArgv: string[]): Args {
         debugModule.enable(enable);
       }
     
    -  const debug = debugModule('snyk');
    -
       // Late require, see the note re "debug" option above.
       const cli = require('./commands');
     
    @@ -273,8 +270,6 @@ export function args(rawArgv: string[]): Args {
         global.ignoreUnknownCA = true;
       }
     
    -  debug(command, obfuscateArgs(argv));
    -
       return {
         command,
         method,
    
  • test/jest/acceptance/debuglog.spec.ts+111 7 modified
    @@ -4,7 +4,7 @@ import {
       createProjectFromWorkspace,
     } from '../util/createProject';
     
    -jest.setTimeout(1000 * 60);
    +jest.setTimeout(10000 * 60);
     
     describe('debug log', () => {
       it('redacts token from env var', async () => {
    @@ -48,7 +48,85 @@ describe('debug log', () => {
     
       it('redacts basic authentication', async () => {
         const { stderr } = await runSnykCLI(
    -      'container test ubuntu:latest --username=us --password=pw -d',
    +      'container test ubuntu:latest --username=john.doe@domain.org --password=solidpassword -d',
    +      {
    +        env: {
    +          ...process.env,
    +          SNYK_DISABLE_ANALYTICS: '1',
    +        },
    +      },
    +    );
    +
    +    expect(stderr).not.toContain('Basic am9ob');
    +    expect(stderr).toContain('Basic ***');
    +
    +    expect(stderr).not.toContain('john.doe@domain.org');
    +    expect(stderr).not.toContain('solidpassword');
    +  });
    +
    +  it('redacts short-form basic authentication', async () => {
    +    const { stderr } = await runSnykCLI(
    +      'container test ubuntu:latest -u=john.doe@domain.org -p=solidpassword -d',
    +      {
    +        env: {
    +          ...process.env,
    +          SNYK_DISABLE_ANALYTICS: '1',
    +        },
    +      },
    +    );
    +
    +    expect(stderr).not.toContain('Basic am9ob');
    +    expect(stderr).toContain('Basic ***');
    +
    +    expect(stderr).not.toContain('john.doe@domain.org');
    +    expect(stderr).not.toContain('solidpassword');
    +
    +    expect(stderr).toContain('-u=***');
    +    expect(stderr).toContain('-p=***');
    +  });
    +
    +  it('redacts ENV-driven basic authentication', async () => {
    +    const { stderr } = await runSnykCLI('container test ubuntu:latest -d', {
    +      env: {
    +        ...process.env,
    +        SNYK_DISABLE_ANALYTICS: '1',
    +        SNYK_REGISTRY_USERNAME: 'john.doe@domain.org',
    +        SNYK_REGISTRY_PASSWORD: 'solidpassword',
    +      },
    +    });
    +
    +    expect(stderr).not.toContain('Basic am9ob');
    +    expect(stderr).toContain('Basic ***');
    +
    +    expect(stderr).not.toContain('john.doe@domain.org');
    +    expect(stderr).not.toContain('solidpassword');
    +  });
    +
    +  it('redacts basic authentication with trace log level', async () => {
    +    const { stderr } = await runSnykCLI(
    +      'container test ubuntu:latest --username=john.doe@domain.org --password=solidpassword -d',
    +      {
    +        env: {
    +          ...process.env,
    +          SNYK_DISABLE_ANALYTICS: '1',
    +          SNYK_LOG_LEVEL: 'trace',
    +        },
    +      },
    +    );
    +
    +    expect(stderr).not.toContain('Basic am9ob');
    +    expect(stderr).toContain('Basic ***');
    +
    +    expect(stderr).not.toContain('john.doe@domain.org');
    +    expect(stderr).not.toContain('solidpassword');
    +
    +    expect(stderr).toContain('"username": "***"');
    +    expect(stderr).toContain('"password": "***"');
    +  });
    +
    +  it('redacts short-form basic authentication with trace log level', async () => {
    +    const { stderr } = await runSnykCLI(
    +      'container test ubuntu:latest -u=john.doe@domain.org -p=solidpassword -d',
           {
             env: {
               ...process.env,
    @@ -58,11 +136,37 @@ describe('debug log', () => {
           },
         );
     
    -    // this test only makes sense when Basic auth would be expected, otherwise the checks below
    -    if (stderr.includes('Basic ')) {
    -      expect(stderr).not.toContain('Basic dXM6cHc=');
    -      expect(stderr).toContain('Basic ***');
    -    }
    +    expect(stderr).not.toContain('Basic am9ob');
    +    expect(stderr).toContain('Basic ***');
    +
    +    expect(stderr).not.toContain('john.doe@domain.org');
    +    expect(stderr).not.toContain('solidpassword');
    +
    +    expect(stderr).toContain('-u=***');
    +    expect(stderr).toContain('"u": "***"');
    +    expect(stderr).toContain('-p=***');
    +    expect(stderr).toContain('"p": "***"');
    +  });
    +
    +  it('redacts ENV-driven basic authentication with trace log level', async () => {
    +    const { stderr } = await runSnykCLI('container test ubuntu:latest -d', {
    +      env: {
    +        ...process.env,
    +        SNYK_DISABLE_ANALYTICS: '1',
    +        SNYK_REGISTRY_USERNAME: 'john.doe@domain.org',
    +        SNYK_REGISTRY_PASSWORD: 'solidpassword',
    +        SNYK_LOG_LEVEL: 'trace',
    +      },
    +    });
    +
    +    expect(stderr).not.toContain('Basic am9ob');
    +    expect(stderr).toContain('Basic ***');
    +
    +    expect(stderr).not.toContain('john.doe@domain.org');
    +    expect(stderr).not.toContain('solidpassword');
    +
    +    expect(stderr).toContain('"REGISTRY_USERNAME": "***"');
    +    expect(stderr).toContain('"REGISTRY_PASSWORD": "***"');
       });
     
       it('redacts externally injected bearer token', async () => {
    
  • ts-binary-wrapper/src/common.ts+1 3 modified
    @@ -181,9 +181,7 @@ export function runWrapper(executable: string, cliArguments: string[]): number {
       const debug = debugEnabled(cliArguments);
     
       if (debug) {
    -    logErrorWithTimeStamps(
    -      'Executing: ' + executable + ' ' + cliArguments.join(' '),
    -    );
    +    logErrorWithTimeStamps('Executing: ' + executable);
       }
     
       const res = spawnSync(executable, cliArguments, {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.