The code execution in this vulnerability does not happen on a quantum computer. It happens on the ordinary machine (a laptop, a notebook server, or a CI runner) that downloads and reads a quantum job's results. The "quantum" part is only how the data arrives.

CVECVE-2026-9291
ComponentAmazon Braket Python SDK (amazon-braket-sdk)
Affected< 1.117.0
Fixed in1.117.0
WeaknessCWE-502, Deserialization of Untrusted Data
CVSS 3.17.1 (High), AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H
CVSS 4.07.5 (High), AV:N/AC:H/AT:P/PR:L/UI:P/VC:H/VI:H/VA:H
PublishedMay 22, 2026
AdvisoriesAWS Security Bulletin 2026-036, GHSA-g697-2xrc-gc46
📺 Animated walkthrough. Use Play, or the arrow keys, to step through the attack.

What Amazon Braket is

Amazon Braket is AWS's managed quantum computing service. You don't own a quantum computer; you run circuits on AWS's simulators or on real quantum hardware from vendors such as IonQ, Rigetti, and QuEra. The Braket Python SDK is the interface developers use to build circuits and run jobs.

Braket also runs Hybrid Jobs: workloads that combine classical Python with quantum steps. AWS executes the job, and the job writes its output to an S3 bucket. Users of Braket include university researchers, financial firms, pharmaceutical and materials companies, and quantum software teams. They run the SDK on normal computers that hold AWS credentials.

The normal flow

When a Hybrid Job finishes, you retrieve its output with one call:

from braket.aws import AwsQuantumJob

job = AwsQuantumJob("arn:aws:braket:...:job/portfolio-opt")
results = job.result()

job.result() reads a file named results.json from the job's S3 output bucket and returns the data. It looks like a read operation. As shown above, it is not always one.

A quick primer: serialization and deserialization

A program holds data in memory as objects: numbers, lists, arrays, and so on. To save that data to a file or send it over a network, the program must turn it into a flat stream of bytes or text. That conversion is serialization. Turning the bytes back into in-memory objects on the other end is deserialization.

JSON is a familiar serialization format. The risk depends on the format. Reading JSON only ever produces plain data: strings, numbers, lists, and dictionaries. Some formats do more. Python's pickle format can describe not just data but instructions for rebuilding objects, and rebuilding an object can run code. So deserializing pickle is not the same as reading JSON, even though the pickle here is wrapped inside a JSON file.

CWE-502, "Deserialization of Untrusted Data," is the weakness class for exactly this: feeding data you do not control into a deserializer that can be made to run code.

The mechanism: pickle stored inside JSON

Some quantum results, such as a numpy array of measurement probabilities, do not serialize to plain text. Braket supports a PICKLED result format for these. A pickled value is Python pickle data, base64-encoded so it fits inside JSON.

A results.json with pickled data looks like this:

{
  "dataDictionary": { "probabilities": "gASV...(base64 pickle)..." },
  "dataFormat": "pickled_v4"
}

The SDK writes pickled values like this:

codecs.encode(pickle.dumps(v, protocol=4), "base64").decode()

And reads them back like this:

pickle.loads(codecs.decode(v.encode(), "base64"))

In versions before 1.117.0, the SDK ran that pickle.loads() call whenever the format was pickled_v4. It did not check the source of the data or require the caller to opt in.

The key point: pickle.loads() runs code

Python's pickle format is not just data. An object can define a __reduce__ method that tells Python which function to call, and with which arguments, when the object is unpickled. An attacker can set that function to os.system and the argument to a shell command.

So calling pickle.loads() on data you do not control runs whatever code the author of that data chose. Reading the file executes it.

Applied to the normal flow:

results = job.result()   # runs the code stored in results.json, with your privileges

Where it bites: who can write to the bucket

To exploit this, an attacker must place a malicious results.json in the job's S3 output bucket. The single requirement is:

Write access (s3:PutObject) to the job's S3 output bucket.

That is a common permission. It is granted by shared team or CI buckets, by IAM roles with broad s3:PutObject rights, and by any compromised principal, such as a leaked CI token, that can write to the bucket.

The attack proceeds in three steps:

  1. The attacker writes a results.json with dataFormat: pickled_v4 and a malicious pickle payload into the output bucket.
  2. A user or an automated pipeline calls job.result().
  3. pickle.loads() runs the payload on that machine, with that user's operating-system privileges and AWS credentials.

Impact

The code runs on a machine that has AWS credentials. With code execution there, an attacker can:

  • Read AWS credentials from environment variables, ~/.aws/credentials, or the instance metadata service (IMDS) role. These credentials grant access to the AWS account.
  • Read and exfiltrate data the machine can access, including quantum algorithms, financial models, and simulation results.
  • Access other AWS resources using the stolen credentials.
  • Overwrite more results files to run the payload again the next time someone reads them.

The quantum computer is not involved. The impact is on the classical machine and the AWS account it connects to.

Why the score is High and not Critical

The CVSS 3.1 base score is 7.1. Two factors keep it below Critical: the attacker needs write access to the output bucket (PR:L), and a user or process must read the results (UI:R). It is not exploitable by an unauthenticated remote attacker. In environments that share output buckets or read results automatically, both conditions are met in normal operation.

The fix

Version 1.117.0 makes pickle deserialization opt-in and refuses it by default. The deserializer takes an allow_pickle argument that defaults to False, and raises an error when pickled data is read without it:

if not allow_pickle:
    raise RuntimeError(
        "Pickle deserialization can execute arbitrary code and is "
        "unsafe on untrusted data."
    )

This is the standard fix for CWE-502: do not automatically deserialize untrusted data with a format that can execute code, and require an explicit decision if it is needed.

What to do

pip install --upgrade "amazon-braket-sdk>=1.117.0"
  1. Upgrade everywhere, including requirements.txt, lockfiles, and container images.
  2. Restrict write access to output buckets. Scope s3:PutObject narrowly, avoid shared write access, enable bucket versioning and object-level logging, and use per-job prefixes.
  3. Set allow_pickle=True only for data you produced and control, never for buckets that other principals can write to.
  4. Review logs if you ran a vulnerable SDK against loosely-permissioned buckets: check CloudTrail and S3 access logs for unexpected PutObject calls on results.json, and inspect machines that ran job.result().

Summary

  • pickle.loads() on untrusted data is arbitrary code execution. The same applies to yaml.load, marshal, and similar deserializers.
  • A read operation is not automatically safe. This bug runs code while loading a results file.
  • Cloud IAM is part of the attack surface. The flaw was in the SDK, but the trigger was an S3 write permission. Patching and least-privilege bucket policies are both required.

References