VYPR
Moderate severityOSV Advisory· Published Jan 29, 2026· Updated Jan 29, 2026

malcontent's OCI image scanning could expose registry credentials

CVE-2026-24845

Description

malcontent discovers supply-chain compromises through. context, differential analysis, and YARA. Starting in version 0.10.0 and prior to version 1.20.3, malcontent could be made to expose Docker registry credentials if it scanned a specially crafted OCI image reference. malcontent uses google/go-containerregistry for OCI image pulls, which by default uses the Docker credential keychain. A malicious registry could return a WWW-Authenticate header redirecting token authentication to an attacker-controlled endpoint, causing credentials to be sent to that endpoint. Version 1.20.3 fixes the issue by defaulting to anonymous auth for OCI pulls.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/chainguard-dev/malcontentGo
>= 0.10.0, < 1.20.31.20.3

Affected products

1

Patches

1
538ed00cdc63

Merge commit from fork

https://github.com/chainguard-dev/malcontentEvan GiblerJan 27, 2026via ghsa
6 files changed · +38 10
  • cmd/mal/mal.go+9 0 modified
    @@ -64,6 +64,7 @@ var (
     	minFileRiskFlag           string
     	minLevelFlag              int
     	minRiskFlag               string
    +	ociAuthFlag               bool
     	ociFlag                   bool
     	outputFlag                string
     	profileFlag               bool
    @@ -259,6 +260,7 @@ func main() {
     				IncludeDataFiles:      includeDataFiles,
     				MinFileRisk:           minFileRisk,
     				MinRisk:               minRisk,
    +				OCIAuth:               ociAuthFlag,
     				OCI:                   ociFlag,
     				QuantityIncreasesRisk: quantityIncreasesRiskFlag,
     				Renderer:              renderer,
    @@ -367,6 +369,13 @@ func main() {
     				Destination: &minRiskFlag,
     				Local:       false,
     			},
    +			&cli.BoolFlag{
    +				Name:        "oci-auth",
    +				Value:       false,
    +				Usage:       "Use Docker Keychain authentication to pull images (warning: may leak credentials to malicious registries!)",
    +				Destination: &ociAuthFlag,
    +				Local:       false,
    +			},
     			&cli.StringFlag{
     				Name:        "output",
     				Aliases:     []string{"o"},
    
  • pkg/action/diff.go+2 2 modified
    @@ -221,11 +221,11 @@ func Diff(ctx context.Context, c malcontent.Config, _ *clog.Logger) (*malcontent
     	)
     
     	if c.OCI {
    -		srcPath, err = archive.OCI(ctx, srcPath)
    +		srcPath, err = archive.OCI(ctx, srcPath, c.OCIAuth)
     		if err != nil {
     			return nil, fmt.Errorf("failed to prepare scan path: %w", err)
     		}
    -		destPath, err = archive.OCI(ctx, destPath)
    +		destPath, err = archive.OCI(ctx, destPath, c.OCIAuth)
     		if err != nil {
     			return nil, fmt.Errorf("failed to prepare scan path: %w", err)
     		}
    
  • pkg/action/scan.go+3 3 modified
    @@ -347,7 +347,7 @@ func handleScanPath(ctx context.Context, scanPath string, c malcontent.Config, r
     		c.Renderer.Scanning(ctx, scanPath)
     	}
     
    -	scanInfo, err := prepareScanPath(ctx, scanPath, c.OCI, logger)
    +	scanInfo, err := prepareScanPath(ctx, scanPath, c.OCI, c.OCIAuth, logger)
     	if err != nil {
     		return fmt.Errorf("failed to prepare scan path: %w", err)
     	}
    @@ -368,7 +368,7 @@ func handleScanPath(ctx context.Context, scanPath string, c malcontent.Config, r
     	return processPaths(ctx, paths, scanInfo, c, r, matchChan, matchOnce, logger)
     }
     
    -func prepareScanPath(ctx context.Context, scanPath string, isOCI bool, logger *clog.Logger) (scanPathInfo, error) {
    +func prepareScanPath(ctx context.Context, scanPath string, isOCI, useAuth bool, logger *clog.Logger) (scanPathInfo, error) {
     	if ctx.Err() != nil {
     		return scanPathInfo{}, ctx.Err()
     	}
    @@ -383,7 +383,7 @@ func prepareScanPath(ctx context.Context, scanPath string, isOCI bool, logger *c
     	}
     
     	info.imageURI = scanPath
    -	ociPath, err := archive.OCI(ctx, info.imageURI)
    +	ociPath, err := archive.OCI(ctx, info.imageURI, useAuth)
     	if err != nil {
     		return info, fmt.Errorf("failed to prepare OCI image for scanning: %w", err)
     	}
    
  • pkg/archive/oci.go+16 5 modified
    @@ -8,11 +8,12 @@ import (
     	"path/filepath"
     
     	"github.com/chainguard-dev/clog"
    +	"github.com/google/go-containerregistry/pkg/authn"
     	"github.com/google/go-containerregistry/pkg/crane"
     	v1 "github.com/google/go-containerregistry/pkg/v1"
     )
     
    -func prepareImage(ctx context.Context, d string) (string, *os.File, error) {
    +func prepareImage(ctx context.Context, d string, useAuth bool) (string, *os.File, error) {
     	if ctx.Err() != nil {
     		return "", nil, ctx.Err()
     	}
    @@ -29,8 +30,18 @@ func prepareImage(ctx context.Context, d string) (string, *os.File, error) {
     		return "", nil, fmt.Errorf("failed to create temp file: %w", err)
     	}
     
    +	// Use anonymous auth by default to avoid credential leakage.
    +	// This is an upstream implementation detail in the Docker registry auth spec,
    +	// but it's safer to default to anonymous auth by default.
    +	opts := []crane.Option{crane.WithContext(ctx)}
    +	if useAuth {
    +		opts = append(opts, crane.WithAuthFromKeychain(authn.DefaultKeychain))
    +	} else {
    +		opts = append(opts, crane.WithAuth(authn.Anonymous))
    +	}
    +
     	var image v1.Image
    -	if image, err = crane.Pull(d, crane.WithContext(ctx)); err != nil {
    +	if image, err = crane.Pull(d, opts...); err != nil {
     		return "", nil, fmt.Errorf("failed to pull image: %w", err)
     	}
     	if err := crane.Export(image, tmpFile); err != nil {
    @@ -43,9 +54,9 @@ func prepareImage(ctx context.Context, d string) (string, *os.File, error) {
     	return tmpDir, tmpFile, nil
     }
     
    -// return a directory with the extracted image directories/files in it.
    -func OCI(ctx context.Context, path string) (string, error) {
    -	tmpDir, tmpFile, err := prepareImage(ctx, path)
    +// OCI returns a directory with the extracted image directories/files in it.
    +func OCI(ctx context.Context, path string, useAuth bool) (string, error) {
    +	tmpDir, tmpFile, err := prepareImage(ctx, path, useAuth)
     	if err != nil {
     		return "", fmt.Errorf("failed to prepare image: %w", err)
     	}
    
  • pkg/malcontent/malcontent.go+1 0 modified
    @@ -34,6 +34,7 @@ type Config struct {
     	MinFileRisk           int
     	MinRisk               int
     	OCI                   bool
    +	OCIAuth               bool
     	Output                io.Writer
     	Processes             bool
     	QuantityIncreasesRisk bool
    
  • README.md+7 0 modified
    @@ -68,14 +68,21 @@ GLOBAL OPTIONS:
        --min-file-risk string      Only show results for files which meet the given risk level (any, low, medium, high, critical) (default: "low")
        --min-level int             Obsoleted by --min-risk (default: -1)
        --min-risk string           Only show results which meet the given risk level (any, low, medium, high, critical) (default: "low")
    +   --oci-auth                  Use Docker Keychain authentication to pull images (warning: may leak credentials to malicious registries!)
        --output string, -o string  Write output to specified file instead of stdout
        --profile, -p               Generate profile and trace files
        --quantity-increases-risk   Increase file risk score based on behavior quantity
        --stats, -s                 Show scan statistics
        --third-party               Include third-party rules which may have licensing restrictions
        --verbose                   Emit verbose logging messages to stderr
    +   --help, -h                  show help
    +   --version, -v               print the version
     ```
     
    +> Using `--oci-auth` leverages the Docker Keychain to authenticate image pulls.  
    +> This option may expose a malicious registry to sensitve auth tokens but is not materially different from other image pull mechanisms (e.g., Docker or `google/go-containerregistry` which malcontent leverages via the `crane` package).   
    +> Malcontent defaults to anonymous pulls and authentication is opt-in when needing to scan OCI images from private, trusted registries.
    +
     ## Modes
     
     ### Analyze
    

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

4

News mentions

0

No linked articles in our index yet.