> ## Content Index
> Fetch the complete content index at: https://blog.elefunc.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Post-Quantum Web Crypto: ML-KEM and ML-DSA on RTEdge.net, and When Browsers Follow
- URL: https://blog.elefunc.com/post-quantum-web-crypto/
- Published: 2026-10-01T09:04:46.000Z
- Updated: 2026-10-01T10:53:35.000Z
- Description: Cloudflare Workers can now run ML-KEM and ML-DSA through Web Crypto behind a compatibility flag, so I tested them with one probe in the browser and on the edge, turned the flag on for every RTEdge.net deployment, and mapped when Chrome, Firefox and Safari follow.
- Author: Cetin
- Tags: Security, Engineering

On October 1, Cloudflare [added ML-KEM and ML-DSA to the Workers Web Crypto API](https://developers.cloudflare.com/changelog/post/2026-09-28-webcrypto-modern-algorithms/?ref=blog.elefunc.com). ML-KEM ([FIPS 203](https://csrc.nist.gov/pubs/fips/203/final?ref=blog.elefunc.com)) establishes shared secrets, and ML-DSA ([FIPS 204](https://csrc.nist.gov/pubs/fips/204/final?ref=blog.elefunc.com)) signs and verifies. They are two of NIST's post-quantum standards, and in Workers they now sit behind the same `crypto.subtle` API that browsers expose, together with four encapsulation and decapsulation methods, `getPublicKey()`, a static `SubtleCrypto.supports()`, and JSON Web Keys of type `AKP`.

The reason to care is the usual one. Traffic encrypted today can be recorded today and decrypted later, once a large enough quantum computer exists. Moving key agreement and signatures to quantum-resistant algorithms is cheaper before that day than after it, and having them as platform primitives means a web app will not have to ship its own lattice arithmetic in WebAssembly once every engine has them.

Cloudflare made the feature opt-in, behind the `webcrypto_modern_algorithms` compatibility flag. I wanted to know two things: does my browser have it, and does my edge?

## One Probe, Two Runtimes

In RTCode.io, our real-time HTML playground, a `<script type=worker edge>` element runs in two places. In the playground it is a service worker inside your own browser. Deployed to RTEdge.net, our hosting on Cloudflare Workers for Platforms, the same code runs at the edge. One probe therefore answers both questions.

The probe runs 40 checks. Apart from six that only confirm a method exists, none of them stop at "did it throw":

- ML-KEM: generate a key pair, encapsulate, decapsulate, and compare the two shared secrets
- ML-DSA: sign, verify, and confirm that a tampered message is rejected
- The changelog's extras: a shared AES-GCM key through `encapsulateKey()` and `decapsulateKey()`, `getPublicKey()` for both algorithms and an `AKP` JSON Web Key round trip, plus the draft's `raw-seed`, `spki` and `pkcs8` key formats and ML-DSA's `context` parameter
- The rest of the [WICG draft](https://wicg.github.io/webcrypto-modern-algos/?ref=blog.elefunc.com): ML-KEM-512, the hybrid KEMs including X-Wing, SLH-DSA, ChaCha20-Poly1305, AES-OCB, SHA-3, cSHAKE, TurboSHAKE, KangarooTwelve, KMAC and Argon2, with every hash and Argon2 result compared against published test vectors

Most rows also ask `SubtleCrypto.supports()` the same question and record its answer, so a runtime whose claims disagree with its behavior would stand out. Before trusting a column of failures, I ran the same function under Node.js 26 and Deno 2.9, which implement most of the draft. They passed 35 and 36 of the 40 checks, and every known-answer test matched.

## The First Answer Was Zero

My browser is Microsoft Edge 153\. In the playground, the page and its service worker both scored 0 of 40, because Chromium 153 predates the release that ships these algorithms.

The deployed worker also scored 0 of 40\. It reported `Cloudflare.compatibilityFlags.webcrypto_modern_algorithms` as `false`, and the runtime said the same thing in its own words:

> Key format "raw-secret" requires the webcrypto\_modern\_algorithms flag.

RTEdge.net sets each upload's compatibility date to the current day, so behavior that Cloudflare turns on by default arrives without any action from us. This flag has no default-on date, so no compatibility date turns it on; it has to be listed explicitly.

## One Line on RTEdge.net

Every RTEdge.net deployment is uploaded with the same compatibility settings, so enabling the flag took one line:

```json
{
  "compatibility_date": "2026-10-01",
  "compatibility_flags": [
    "no_nodejs_compat",
    "no_nodejs_compat_v2",
    "webcrypto_modern_algorithms",
    "enable_request_signal",
    "disallow_importable_env"
  ]
}

```

Flags apply at upload, so I redeployed the probe. The edge then passed 18 of 40: exactly the 18 checks that cover ML-KEM, ML-DSA and the methods Cloudflare announced, and nothing else. ML-KEM-768 and ML-KEM-1024 agree on 32-byte shared secrets; ML-DSA-44, ML-DSA-65 and ML-DSA-87 sign and verify; and the AES-GCM, JWK, seed, `spki`, `pkcs8` and `context` round trips all work. ML-KEM-512, the hybrid KEMs including X-Wing, SLH-DSA, ChaCha20-Poly1305, AES-OCB, the hashes, KMAC and Argon2 fail with `NotSupportedError`, as documented, and `SubtleCrypto.supports()` agreed with the actual result on every row it answered.

Every RTEdge.net worker deployed from now on has the API, and a worker can check for itself:

```js
const enabled = globalThis.Cloudflare?.compatibilityFlags?.webcrypto_modern_algorithms === true;

```

## When Browsers Follow

The edge is ahead of most browsers today. Here is where each engine stands.

Post-quantum Web Crypto in Chrome, Firefox and Safari

ML-KEM, ML-DSA and the new SubtleCrypto methods, from the first intents to October 1, 2026\. Hover, focus or tap a mark for details.

- Behind a flag or Nightly only
- Origin trial
- On by default
- Intent or standards position
**The timeline as a table** 

**Chrome** is first. After a developer trial behind `chrome://flags/#webcrypto-pqc` in Chrome 150 and an origin trial from Chrome 151 to 154, [Chrome 155](https://developer.chrome.com/blog/chrome-155-beta?ref=blog.elefunc.com) turns on ML-KEM-768, ML-KEM-1024, ML-DSA-44, ML-DSA-65, ML-DSA-87, X-Wing and ChaCha20-Poly1305 by default, together with all four encapsulation methods, `getPublicKey()` and `supports()`. It reached a small share of desktop users as an early stable release on September 23, and the stable release starts rolling out on October 6\. Microsoft Edge follows Chromium, with Edge 155 due the week of October 8\. Chrome and Cloudflare Workers overlap on ML-KEM and ML-DSA. X-Wing and ChaCha20-Poly1305 are in Chrome but not yet in Workers, and neither offers ML-KEM-512.

**Firefox** landed ML-KEM-512, ML-KEM-768 and ML-KEM-1024 with the four encapsulation methods in [Nightly 157 on September 9](https://bugzilla.mozilla.org/show%5Fbug.cgi?id=1943614&ref=blog.elefunc.com), where it is on by default. Firefox 157 shipped on September 29 with that code switched off; setting `dom.webcrypto.encapsulation.enabled` in `about:config` turns it on. ML-DSA, X-Wing, `getPublicKey()` and `supports()` are not there yet, and [Mozilla's position](https://github.com/mozilla/standards-positions/issues/1282?ref=blog.elefunc.com) on the proposal is neutral.

**Safari** has no implementation and no flag yet. WebKit's [position on the proposal](https://github.com/WebKit/standards-positions/issues/641?ref=blog.elefunc.com) is neutral, with Apple engineers looking at ML-KEM, ML-DSA and SHA-3, which Apple platforms already support in CryptoKit. Its [position on hybrid KEMs](https://github.com/WebKit/standards-positions/issues/704?ref=blog.elefunc.com) is support, with Apple looking at implementing X-Wing. Safari 27, released on September 14, does not include them.

Server runtimes got there earlier. Node.js 24.7 added ML-KEM and ML-DSA to Web Crypto in August 2025, and Deno followed in June 2026, which is why both could double-check the probe.

## Check Your Browser

The card below runs the same 40 checks in this page, next to the edge results recorded on October 1\. A check mark means a method exists, a real round trip succeeded or a known-answer test matched; nothing here leaves your browser.

Post-quantum Web Crypto, checked live in your browser

Running 40 checks in this page…

## Feature-Detect, Do Not Sniff

Support will arrive at different times in different engines, so detect it instead of reading version numbers:

```js
const subtle = crypto.subtle;
const SubtleCryptoClass = subtle.constructor;
const mlKem = typeof SubtleCryptoClass.supports === 'function'
  ? SubtleCryptoClass.supports('encapsulateBits', 'ML-KEM-768')
  : await subtle.generateKey('ML-KEM-768', false, ['encapsulateBits', 'decapsulateBits']).then(() => true, () => false);

if (mlKem) {
  const { publicKey, privateKey } = await subtle.generateKey('ML-KEM-768', false, ['encapsulateBits', 'decapsulateBits']);
  const { sharedKey, ciphertext } = await subtle.encapsulateBits('ML-KEM-768', publicKey);
  const recovered = await subtle.decapsulateBits('ML-KEM-768', privateKey, ciphertext);
  // sharedKey and recovered hold the same 32 bytes
}

```

Firefox has ML-KEM but no `supports()` yet, so the snippet falls back to trying `generateKey()`. Two more details are easy to miss. Call `supports()` on the class, as `SubtleCrypto.supports(...)` or through `crypto.subtle.constructor`, because Node.js rejects a detached call. And pass complete algorithm parameters: under the draft, and in Node.js, `supports('digest', 'cSHAKE128')` is `false` until you include `outputLength`, and `supports('generateKey', 'AES-OCB')` is `false` until you include `length`; Deno answers `true` either way, so do not rely on the short form.

Where it exists, prefer the hybrid X-Wing, registered as `MLKEM768-X25519`. It combines ML-KEM-768 with X25519, so the shared secret stays safe as long as either one holds.

## Post-Quantum on Both Ends

RTCode.io is a real-time playground where every keystroke updates the live output, and RTEdge.net deploys what you build there to Cloudflare's edge. As of this week, the edge end of that path speaks post-quantum Web Crypto on every RTEdge.net worker, and the browser end follows as each engine ships it. See what else we build at [elefunc.com](https://elefunc.com/?ref=blog.elefunc.com).