malcontent's OCI image scanning could expose registry credentials
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.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/chainguard-dev/malcontentGo | >= 0.10.0, < 1.20.3 | 1.20.3 |
Affected products
1- Range: v0.10.0, v0.11.0, v0.12.0, …
Patches
1538ed00cdc63Merge commit from fork
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- github.com/advisories/GHSA-9m43-p3cx-w8j5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-24845ghsaADVISORY
- github.com/chainguard-dev/malcontent/commit/538ed00cdc639d687a4bd1e843a2be0428a3b3e7ghsax_refsource_MISCWEB
- github.com/chainguard-dev/malcontent/security/advisories/GHSA-9m43-p3cx-w8j5ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.