VYPR
Low severity3.1NVD Advisory· Published Apr 21, 2026· Updated May 1, 2026

CVE-2026-39396

CVE-2026-39396

Description

OpenBao is an open source identity-based secrets management system. Prior to version 2.5.3, ExtractPluginFromImage() in OpenBao's OCI plugin downloader extracts a plugin binary from a container image by streaming decompressed tar data via io.Copy with no upper bound on the number of bytes written. An attacker who controls or compromises the OCI registry referenced in the victim's configuration can serve a crafted image containing a decompression bomb that decompresses to an arbitrarily large file. The SHA256 integrity check occurs after the full file is written to disk, meaning the hash mismatch is detected only after the damage (disk exhaustion) has already occurred. This allow the attacker to replace legit plugin image with no need to change its signature. Version 2.5.3 contains a patch.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/openbao/openbaoGo
< 0.0.0-20260420180337-2b2a901aa9f70.0.0-20260420180337-2b2a901aa9f7

Affected products

1

Patches

1
af576af5322c

Validate downloaded plugin binary size (#2941) (#2944)

https://github.com/openbao/openbaoJonas KöhnenApr 20, 2026via ghsa
4 files changed · +57 2
  • changelog/2941.txt+3 0 added
    @@ -0,0 +1,3 @@
    +```release-note:security
    +core/plugins: Validate and restrict downloaded plugin binary size from OCI images; set `plugin_download_max_size` to limit the size (defaults to 512MB). GHSA-r65v-xgwc-g56j / CVE-2026-39396.
    +```
    
  • command/server/config.go+17 0 modified
    @@ -87,6 +87,9 @@ type Config struct {
     	PluginAutoRegister     bool            `hcl:"-"`
     	PluginAutoRegisterRaw  interface{}     `hcl:"plugin_auto_register"`
     
    +	PluginDownloadMaxSize    int64       `hcl:"-"`
    +	PluginDownloadMaxSizeRaw interface{} `hcl:"plugin_download_max_size"`
    +
     	EnableIntrospectionEndpoint    bool        `hcl:"-"`
     	EnableIntrospectionEndpointRaw interface{} `hcl:"introspection_endpoint,alias:EnableIntrospectionEndpoint"`
     
    @@ -699,6 +702,12 @@ func (c *Config) Merge(c2 *Config) *Config {
     		result.PluginAutoRegisterRaw = c2.PluginAutoRegisterRaw
     	}
     
    +	result.PluginDownloadMaxSize = c.PluginDownloadMaxSize
    +	if c2.PluginAutoDownloadRaw != nil {
    +		result.PluginDownloadMaxSize = c2.PluginDownloadMaxSize
    +		result.PluginDownloadMaxSizeRaw = c2.PluginDownloadMaxSizeRaw
    +	}
    +
     	return result
     }
     
    @@ -1042,6 +1051,14 @@ func ParseConfig(d, source string) (*Config, error) {
     		result.PluginAutoRegister = autoRegister
     	}
     
    +	if result.PluginDownloadMaxSizeRaw != nil {
    +		maxSize, err := parseutil.ParseInt(result.PluginDownloadMaxSizeRaw)
    +		if err != nil {
    +			return nil, err
    +		}
    +		result.PluginDownloadMaxSize = maxSize
    +	}
    +
     	// Remove all unused keys from Config that were satisfied by SharedConfig.
     	result.UnusedKeys = configutil.UnusedFieldDifference(result.UnusedKeys, nil, append(result.FoundKeys, sharedConfig.FoundKeys...))
     	// Assign file info
    
  • helper/pluginutil/oci/downloader.go+33 2 modified
    @@ -23,10 +23,12 @@ import (
     	"github.com/hashicorp/go-hclog"
     	"github.com/openbao/openbao/command/server"
     	"github.com/openbao/openbao/helper/osutil"
    +	"github.com/shirou/gopsutil/v4/disk"
     )
     
     const (
    -	PluginCacheDir = ".oci-cache"
    +	PluginCacheDir     = ".oci-cache"
    +	PluginMaxSizeBytes = 512 * 1024 * 1024 // 512 MB
     )
     
     // PluginDownloader handles downloading and managing OCI-based plugins
    @@ -108,6 +110,15 @@ func (d *PluginDownloader) shouldFailOnPluginError() bool {
     	return behavior == server.PluginDownloadFail
     }
     
    +// maxPluginSize returns the maximum allowed plugin binary size
    +func (d *PluginDownloader) maxPluginSize() int64 {
    +	if d.config.PluginDownloadMaxSize > 0 {
    +		return d.config.PluginDownloadMaxSize
    +	}
    +
    +	return PluginMaxSizeBytes
    +}
    +
     // IsPluginCacheValid checks if the plugin already exists in the plugin directory
     // and matches the expected SHA256 hash (fast path)
     func (d *PluginDownloader) IsPluginCacheValid(config *server.PluginConfig) bool {
    @@ -290,6 +301,19 @@ func (d *PluginDownloader) ExtractPluginFromImage(img v1.Image, targetPath strin
     				continue
     			}
     
    +			if header.Size > d.maxPluginSize() {
    +				return fmt.Errorf("plugin binary size of %d MiB exceeds allowed size of %d MiB", header.Size/1024/1024, d.maxPluginSize()/1024/1024)
    +			}
    +
    +			diskUsage, err := disk.Usage(filepath.Dir(targetPath))
    +			if err != nil {
    +				return fmt.Errorf("failed to get disk usage: %w", err)
    +			}
    +
    +			if diskUsage.Free < uint64(header.Size) {
    +				return fmt.Errorf("not enough space left on disk to download plugin")
    +			}
    +
     			logger.Info("found plugin binary in OCI image", "entry", header.Name, "size", header.Size)
     
     			// Create the output file
    @@ -298,9 +322,16 @@ func (d *PluginDownloader) ExtractPluginFromImage(img v1.Image, targetPath strin
     				return fmt.Errorf("failed to create output file: %w", err)
     			}
     
    -			if _, copyErr := io.Copy(outFile, tarReader); copyErr != nil {
    +			// wrap tarReader in an io.LimitReader to protect against malicious Tar Headers that report a wrong file size
    +			limitReader := io.LimitReader(tarReader, header.Size+1)
    +
    +			n, copyErr := io.Copy(outFile, limitReader)
    +			if copyErr != nil {
     				err = fmt.Errorf("failed to extract binary data: %w", copyErr)
     			}
    +			if n != header.Size {
    +				err = fmt.Errorf("file size was different than reported in tar header")
    +			}
     
     			if closeErr := outFile.Close(); closeErr != nil {
     				err = errors.Join(err, fmt.Errorf("failed to close %s: %w", targetPath, closeErr))
    
  • website/content/docs/configuration/plugins.mdx+4 0 modified
    @@ -163,6 +163,10 @@ plugin_auto_register = true
     - `true` - server startup and `SIGHUP` triggers registering of plugins; can still be manually registered with `bao plugin register`.
     - `false` - only `bao plugin register` will manually register plugins.
     
    +## `plugin_download_max_size`
    +
    +A number defining the maximum allowed plugin binary size in bytes when extracted from an OCI image. Defaults to `536870912` (512 MiB) if not set.
    +
     ## Authentication
     
     When downloading plugin images from a private registry, OpenBao will use
    

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.