VYPR
High severityOSV Advisory· Published Nov 25, 2025· Updated Apr 15, 2026

CVE-2025-65965

CVE-2025-65965

Description

Grype is a vulnerability scanner for container images and filesystems. A credential disclosure vulnerability was found in Grype, affecting versions 0.68.0 through 0.104.0. If registry credentials are defined and the output of grype is written using the --file or --output json=<file> option, the registry credentials will be included unsanitized in the output file. This issue has been patched in version 0.104.1. Users running affected versions of grype can work around this vulnerability by redirecting stdout to a file instead of using the --file or --output options.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/anchore/grypeGo
>= 0.68.0, < 0.104.10.104.1

Affected products

1

Patches

2
39f7fa17af27

fix: redact during file output (#3068)

https://github.com/anchore/grypeKeith ZantowNov 24, 2025via ghsa
4 files changed · +109 5
  • cmd/grype/cli/commands/root.go+3 0 modified
    @@ -236,6 +236,9 @@ func runGrype(app clio.Application, opts *options.Grype, userInput string) (errs
     	log.WithFields("time", time.Since(startTime)).Info("found vulnerability matches")
     	startTime = time.Now()
     
    +	// clear out the registry auth information to avoid including possibly sensitive information in the report
    +	opts.Registry.Auth = nil
    +
     	model, err := models.NewDocument(app.ID(), packages, pkgContext, *remainingMatches, ignoredMatches, vp, opts, dbInfo(status, vp), models.SortStrategy(opts.SortBy.Criteria), opts.Timestamp)
     	if err != nil {
     		return fmt.Errorf("failed to create document: %w", err)
    
  • cmd/grype/cli/options/registry.go+4 4 modified
    @@ -21,7 +21,7 @@ type RegistryCredentials struct {
     type registry struct {
     	InsecureSkipTLSVerify bool                  `yaml:"insecure-skip-tls-verify" json:"insecure-skip-tls-verify" mapstructure:"insecure-skip-tls-verify"`
     	InsecureUseHTTP       bool                  `yaml:"insecure-use-http" json:"insecure-use-http" mapstructure:"insecure-use-http"`
    -	Auth                  []RegistryCredentials `yaml:"auth" json:"auth" mapstructure:"auth"`
    +	Auth                  []RegistryCredentials `yaml:"auth" json:"auth,omitempty" mapstructure:"auth"`
     	CACert                string                `yaml:"ca-cert" json:"ca-cert" mapstructure:"ca-cert"`
     }
     
    @@ -82,9 +82,9 @@ func (cfg *registry) ToOptions() *image.RegistryOptions {
     	for i, a := range cfg.Auth {
     		auth[i] = image.RegistryCredentials{
     			Authority:  a.Authority,
    -			Username:   a.Username.String(),
    -			Password:   a.Password.String(),
    -			Token:      a.Token.String(),
    +			Username:   string(a.Username),
    +			Password:   string(a.Password),
    +			Token:      string(a.Token),
     			ClientCert: a.TLSCert,
     			ClientKey:  a.TLSKey,
     		}
    
  • cmd/grype/cli/options/secret.go+9 1 modified
    @@ -21,5 +21,13 @@ func (r *secret) PostLoad() error {
     }
     
     func (r secret) String() string {
    -	return string(r)
    +	if r == "" {
    +		return ""
    +	}
    +	// match the redactor's behavior, replacing with 7 asterisks
    +	return "*******"
    +}
    +
    +func (r secret) MarshalText() ([]byte, error) {
    +	return []byte(r.String()), nil
     }
    
  • test/cli/registry_auth_test.go+93 0 modified
    @@ -1,8 +1,12 @@
     package cli
     
     import (
    +	"os"
    +	"path/filepath"
     	"strings"
     	"testing"
    +
    +	"github.com/stretchr/testify/require"
     )
     
     func TestRegistryAuth(t *testing.T) {
    @@ -97,3 +101,92 @@ func TestRegistryAuth(t *testing.T) {
     		})
     	}
     }
    +
    +func TestRegistryAuthRedactions(t *testing.T) {
    +	tmp := filepath.Join(t.TempDir(), "output.json")
    +
    +	assertNotInFile := func(text string) traitAssertion {
    +		return func(tb testing.TB, stdout, stderr string, rc int) {
    +			contents, err := os.ReadFile(tmp)
    +			require.NoError(tb, err)
    +			require.NotEmpty(tb, contents)
    +			require.NotContains(tb, string(contents), text)
    +		}
    +	}
    +
    +	tests := []struct {
    +		name       string
    +		args       []string
    +		env        map[string]string
    +		assertions []traitAssertion
    +	}{
    +		{
    +			name: "use creds",
    +			args: []string{"-vv", "sbom:test-fixtures/sbom-grype-source.json", "-o", "json"},
    +			env: map[string]string{
    +				"GRYPE_REGISTRY_AUTH_USERNAME": "foobar-username",
    +				"GRYPE_REGISTRY_AUTH_PASSWORD": "foobar-password",
    +			},
    +			assertions: []traitAssertion{
    +				assertSucceedingReturnCode,
    +				assertNotInOutput("foobar-username"),
    +				assertNotInOutput("foobar-password"),
    +			},
    +		},
    +		{
    +			name: "use token",
    +			args: []string{"-vv", "sbom:test-fixtures/sbom-grype-source.json", "-o", "json"},
    +			env: map[string]string{
    +				"GRYPE_REGISTRY_AUTH_TOKEN": "foobar-token",
    +			},
    +			assertions: []traitAssertion{
    +				assertSucceedingReturnCode,
    +				assertNotInOutput("foobar-token"),
    +			},
    +		},
    +		{
    +			name: "use creds file",
    +			args: []string{"-vv", "sbom:test-fixtures/sbom-grype-source.json", "-o", "json", "--file", tmp},
    +			env: map[string]string{
    +				"GRYPE_REGISTRY_AUTH_USERNAME": "foobar-username",
    +				"GRYPE_REGISTRY_AUTH_PASSWORD": "foobar-password",
    +			},
    +			assertions: []traitAssertion{
    +				assertSucceedingReturnCode,
    +				assertNotInFile("foobar-username"),
    +				assertNotInFile("foobar-password"),
    +				assertNotInOutput("foobar-username"),
    +				assertNotInOutput("foobar-password"),
    +			},
    +		},
    +		{
    +			name: "use token file",
    +			args: []string{"-vv", "sbom:test-fixtures/sbom-grype-source.json", "-o", "json", "--file", tmp},
    +			env: map[string]string{
    +				"GRYPE_REGISTRY_AUTH_TOKEN": "foobar-token",
    +			},
    +			assertions: []traitAssertion{
    +				assertSucceedingReturnCode,
    +				assertNotInFile("foobar-token"),
    +				assertNotInOutput("foobar-token"),
    +			},
    +		},
    +	}
    +
    +	for _, test := range tests {
    +		t.Run(test.name, func(t *testing.T) {
    +			_ = os.Remove(tmp) // ok to fail
    +			cmd, stdout, stderr := runGrype(t, test.env, test.args...)
    +			for _, traitAssertionFn := range test.assertions {
    +				traitAssertionFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
    +			}
    +			if t.Failed() {
    +				fileContents, _ := os.ReadFile(tmp)
    +				t.Log("FILE:\n", string(fileContents))
    +				t.Log("STDOUT:\n", stdout)
    +				t.Log("STDERR:\n", stderr)
    +				t.Log("COMMAND:", strings.Join(cmd.Args, " "))
    +			}
    +		})
    +	}
    +}
    
c99f79de49a5

fix: redact during file output

https://github.com/anchore/grypeKeith ZantowNov 24, 2025via ghsa
4 files changed · +109 5
  • cmd/grype/cli/commands/root.go+3 0 modified
    @@ -236,6 +236,9 @@ func runGrype(app clio.Application, opts *options.Grype, userInput string) (errs
     	log.WithFields("time", time.Since(startTime)).Info("found vulnerability matches")
     	startTime = time.Now()
     
    +	// clear out the registry auth information to avoid including possibly sensitive information in the report
    +	opts.Registry.Auth = nil
    +
     	model, err := models.NewDocument(app.ID(), packages, pkgContext, *remainingMatches, ignoredMatches, vp, opts, dbInfo(status, vp), models.SortStrategy(opts.SortBy.Criteria), opts.Timestamp)
     	if err != nil {
     		return fmt.Errorf("failed to create document: %w", err)
    
  • cmd/grype/cli/options/registry.go+4 4 modified
    @@ -21,7 +21,7 @@ type RegistryCredentials struct {
     type registry struct {
     	InsecureSkipTLSVerify bool                  `yaml:"insecure-skip-tls-verify" json:"insecure-skip-tls-verify" mapstructure:"insecure-skip-tls-verify"`
     	InsecureUseHTTP       bool                  `yaml:"insecure-use-http" json:"insecure-use-http" mapstructure:"insecure-use-http"`
    -	Auth                  []RegistryCredentials `yaml:"auth" json:"auth" mapstructure:"auth"`
    +	Auth                  []RegistryCredentials `yaml:"auth" json:"auth,omitempty" mapstructure:"auth"`
     	CACert                string                `yaml:"ca-cert" json:"ca-cert" mapstructure:"ca-cert"`
     }
     
    @@ -82,9 +82,9 @@ func (cfg *registry) ToOptions() *image.RegistryOptions {
     	for i, a := range cfg.Auth {
     		auth[i] = image.RegistryCredentials{
     			Authority:  a.Authority,
    -			Username:   a.Username.String(),
    -			Password:   a.Password.String(),
    -			Token:      a.Token.String(),
    +			Username:   string(a.Username),
    +			Password:   string(a.Password),
    +			Token:      string(a.Token),
     			ClientCert: a.TLSCert,
     			ClientKey:  a.TLSKey,
     		}
    
  • cmd/grype/cli/options/secret.go+9 1 modified
    @@ -21,5 +21,13 @@ func (r *secret) PostLoad() error {
     }
     
     func (r secret) String() string {
    -	return string(r)
    +	if r == "" {
    +		return ""
    +	}
    +	// match the redactor's behavior, replacing with 7 asterisks
    +	return "*******"
    +}
    +
    +func (r secret) MarshalText() ([]byte, error) {
    +	return []byte(r.String()), nil
     }
    
  • test/cli/registry_auth_test.go+93 0 modified
    @@ -1,8 +1,12 @@
     package cli
     
     import (
    +	"os"
    +	"path/filepath"
     	"strings"
     	"testing"
    +
    +	"github.com/stretchr/testify/require"
     )
     
     func TestRegistryAuth(t *testing.T) {
    @@ -97,3 +101,92 @@ func TestRegistryAuth(t *testing.T) {
     		})
     	}
     }
    +
    +func TestRegistryAuthRedactions(t *testing.T) {
    +	tmp := filepath.Join(t.TempDir(), "output.json")
    +
    +	assertNotInFile := func(text string) traitAssertion {
    +		return func(tb testing.TB, stdout, stderr string, rc int) {
    +			contents, err := os.ReadFile(tmp)
    +			require.NoError(tb, err)
    +			require.NotEmpty(tb, contents)
    +			require.NotContains(tb, string(contents), text)
    +		}
    +	}
    +
    +	tests := []struct {
    +		name       string
    +		args       []string
    +		env        map[string]string
    +		assertions []traitAssertion
    +	}{
    +		{
    +			name: "use creds",
    +			args: []string{"-vv", "sbom:test-fixtures/sbom-grype-source.json", "-o", "json"},
    +			env: map[string]string{
    +				"GRYPE_REGISTRY_AUTH_USERNAME": "foobar-username",
    +				"GRYPE_REGISTRY_AUTH_PASSWORD": "foobar-password",
    +			},
    +			assertions: []traitAssertion{
    +				assertSucceedingReturnCode,
    +				assertNotInOutput("foobar-username"),
    +				assertNotInOutput("foobar-password"),
    +			},
    +		},
    +		{
    +			name: "use token",
    +			args: []string{"-vv", "sbom:test-fixtures/sbom-grype-source.json", "-o", "json"},
    +			env: map[string]string{
    +				"GRYPE_REGISTRY_AUTH_TOKEN": "foobar-token",
    +			},
    +			assertions: []traitAssertion{
    +				assertSucceedingReturnCode,
    +				assertNotInOutput("foobar-token"),
    +			},
    +		},
    +		{
    +			name: "use creds file",
    +			args: []string{"-vv", "sbom:test-fixtures/sbom-grype-source.json", "-o", "json", "--file", tmp},
    +			env: map[string]string{
    +				"GRYPE_REGISTRY_AUTH_USERNAME": "foobar-username",
    +				"GRYPE_REGISTRY_AUTH_PASSWORD": "foobar-password",
    +			},
    +			assertions: []traitAssertion{
    +				assertSucceedingReturnCode,
    +				assertNotInFile("foobar-username"),
    +				assertNotInFile("foobar-password"),
    +				assertNotInOutput("foobar-username"),
    +				assertNotInOutput("foobar-password"),
    +			},
    +		},
    +		{
    +			name: "use token file",
    +			args: []string{"-vv", "sbom:test-fixtures/sbom-grype-source.json", "-o", "json", "--file", tmp},
    +			env: map[string]string{
    +				"GRYPE_REGISTRY_AUTH_TOKEN": "foobar-token",
    +			},
    +			assertions: []traitAssertion{
    +				assertSucceedingReturnCode,
    +				assertNotInFile("foobar-token"),
    +				assertNotInOutput("foobar-token"),
    +			},
    +		},
    +	}
    +
    +	for _, test := range tests {
    +		t.Run(test.name, func(t *testing.T) {
    +			_ = os.Remove(tmp) // ok to fail
    +			cmd, stdout, stderr := runGrype(t, test.env, test.args...)
    +			for _, traitAssertionFn := range test.assertions {
    +				traitAssertionFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
    +			}
    +			if t.Failed() {
    +				fileContents, _ := os.ReadFile(tmp)
    +				t.Log("FILE:\n", string(fileContents))
    +				t.Log("STDOUT:\n", stdout)
    +				t.Log("STDERR:\n", stderr)
    +				t.Log("COMMAND:", strings.Join(cmd.Args, " "))
    +			}
    +		})
    +	}
    +}
    

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

6

News mentions

0

No linked articles in our index yet.