SAI Security Advisory

Command Injection in Capture Dependency

April 30, 2024

Products Impacted

This vulnerability is present in AWS Sagemaker Python SDK v2.154.0 up to v2.218.0.

CVSS Score: 7.8

AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE Categorization

CWE-502: Deserialization of Untrusted Data

Details

As stated above, the vulnerability exists in the NumpyDeserializer deserialize function:

def deserialize(self, stream, content_type):
        """Deserialize data from an inference endpoint into a NumPy array.

        Args:
            stream (botocore.response.StreamingBody): Data to be deserialized.
            content_type (str): The MIME type of the data.

        Returns:
            numpy.ndarray: The data deserialized into a NumPy array.
        """
        try:
            if content_type == "text/csv":
                return np.genfromtxt(
                    codecs.getreader("utf-8")(stream), delimiter=",", dtype=self.dtype
                )
            if content_type == "application/json":
                return np.array(json.load(codecs.getreader("utf-8")(stream)), dtype=self.dtype)
            if content_type == "application/x-npy":
                return np.load(io.BytesIO(stream.read()), allow_pickle=self.allow_pickle)
            if content_type == "application/x-npz":
                try:
                    return np.load(io.BytesIO(stream.read()), allow_pickle=self.allow_pickle)
                finally:
                    stream.close()
        finally:
            stream.close()

        raise ValueError("%s cannot read content type %s." % (__class__.__name__, content_type))

If the content type is either “application/x-npy” or “application/x-npz” then the stream with the malicious pickle file gets sent to the np.load function, allowing for code execution to occur. The root cause of the vulnerability, however, exists within the class initializer:

    def __init__(self, dtype=None, accept="application/x-npy", allow_pickle=True):
        """Initialize a ``NumpyDeserializer`` instance.

        Args:
            dtype (str): The dtype of the data (default: None).
            accept (union[str, tuple[str]]): The MIME type (or tuple of allowable MIME types) that
                is expected from the inference endpoint (default: "application/x-npy").
            allow_pickle (bool): Allow loading pickled object arrays (default: True).
        """
        super(NumpyDeserializer, self).__init__(accept=accept)
        self.dtype = dtype
        self.allow_pickle = allow_pickle

As mentioned in the summary, by having allow_pickle set to true, the function is unsafe by default. A user would be compromised if their code opens a malicious pickle object and passes the stream to deserialize like the below example:

# Use the NumpyDeserializer
from sagemaker.base_deserializers import NumpyDeserializer

with open("bad.npy", "rb") as f:
    NumpyDeserializer().deserialize(f, "application/x-npy")

with open("bad.npy", "rb") as f:
    NumpyDeserializer().deserialize(f, "application/x-npz")

When the above file is run, we can see that “pwned” is printed out twice:

Related SAI Security Advisory

CVE-2026-87988

September 11, 2026

_READ_ONLY_COMMANDS_POSIX expansion adds 31 commands with no path checking, granting unconditional access to the full host filesystem

Mistral Vibe

Mistral Vibe automatically approves a large set of commands that are not subject to the expected workspace path restrictions, allowing files anywhere on the host to be accessed without user approval.

September 2026
CVE-2026-87987

September 11, 2026

Environment variable prefixes stripped from permission check enable RCE via env injection

Mistral Vibe

Mistral Vibe does not consider environment variable assignments when checking whether a command can run without approval, allowing environment controlled behavior in allowlisted programs such as Git to be abused for arbitrary code execution.

September 2026