Skip to content

Normative: Allow NaN values to be *optionally* canonicalized#758

Open
littledan wants to merge 1 commit into
tc39:mainfrom
littledan:nan-relax
Open

Normative: Allow NaN values to be *optionally* canonicalized#758
littledan wants to merge 1 commit into
tc39:mainfrom
littledan:nan-relax

Conversation

@littledan

Copy link
Copy Markdown
Member

This patch legalizes V8's occasional canonicalization of NaNs
by changing SetValueInBuffer to either a particular value
for the implementation-distinguishable NaN value or an
implementation-defined canonical value.

This semantic change reached consensus at the November 2016
TC39 meeting.

Closes #635

This patch still needs the associated test262 update. The tests that are affected include these, but maybe more:

built-ins/Object/internals/DefineOwnProperty/nan-equivalence
built-ins/TypedArray/prototype/fill/fill-values-conversion-operations-consistent-nan
built-ins/TypedArrays/internals/DefineOwnProperty/conversion-operation-consistent-nan
built-ins/TypedArrays/internals/Set/conversion-operation-consistent-nan

@littledan

Copy link
Copy Markdown
Member Author

cc @erights @allenwb

@bterlson bterlson added needs consensus This needs committee consensus before it can be eligible to be merged. needs test262 tests The proposal should specify how to test an implementation. Ideally via github.com/tc39/test262 labels Dec 29, 2016
@bterlson

Copy link
Copy Markdown
Member

@allenwb does this seem fine to you? (Just checking as I think you were involved in the discussions closely at the last meeting).

@bterlson bterlson removed the needs consensus This needs committee consensus before it can be eligible to be merged. label Dec 29, 2016
@bterlson bterlson requested a review from allenwb January 5, 2017 19:15
@littledan

littledan commented Mar 5, 2018

Copy link
Copy Markdown
Member Author

What do we need from here to decide whether to land this patch?

@jfbastien has clarified that, even if this patch lands, we won't really have a full description of what happens with NaN values in practice. This is because processors sometimes change the NaN bit pattern later, e.g., in MOV instructions, in a way that JS implementations which don't canonicalize are probably not guarding against.

Even as I'd personally prefer that we leave the NaN bit pattern completely unspecified, I think this patch still brings us a little bit closer to reality and would be helpful to land.

Comment thread spec.html Outdated
1. Set _rawBytes_ to a List containing the 4 bytes that are the result of converting _value_ to IEEE 754-2008 binary32 format using “Round to nearest, ties to even” rounding mode. If _isLittleEndian_ is *false*, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If _value_ is *NaN*, _rawValue_ may be set to any implementation chosen IEEE 754-2008 binary32 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable *NaN* value.
1. Else if _type_ is `"Float64"`, then
1. Set _rawBytes_ to a List containing the 8 bytes that are the IEEE 754-2008 binary64 format encoding of _value_. If _isLittleEndian_ is *false*, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If _value_ is *NaN*, _rawValue_ may be set to any implementation chosen IEEE 754-2008 binary64 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable *NaN* value.
1. Set _rawBytes_ to a List containing the 8 bytes that are the IEEE 754-2008 binary64 format encoding of _value_. If _isLittleEndian_ is *false*, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If _value_ is *NaN*, _rawValue_ may be set to any implementation chosen IEEE 754-2008 binary64 format Not-a-Number encoding. An implementation must always choose either the same encoding for each implementation distinguishable *NaN* value, or an implementation-defined canonical value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this change is only being made to the Float64 branch, should it also be applied to the Float32 branch just above?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@allenwb allenwb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A agree with Rick. It should also apply to the Float32 case

This patch legalizes V8's occasional canonicalization of NaNs
by changing SetValueInBuffer to *either* a particular value
for the implementation-distinguishable NaN value *or* an
implementation-defined canonical value.

This semantic change reached consensus at the November 2016
TC39 meeting.

Closes tc39#635
allenwb
allenwb previously approved these changes Mar 8, 2018
@allenwb allenwb dismissed their stale review March 8, 2018 16:24

Becasue I still agree with @rwaldron

@allenwb allenwb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Rick.

In the Float32 case a Number value which may be a NaN is converted to a Float32 so a 32-bit NaN needs to be created from the 64-bing NaN. The bit encoding of the 32-bit NaN is observable in the buffer so we want the choice of encoding to be consistent within an implementaiton, just like for Float32.

Comment thread spec.html
<emu-alg>
1. If _type_ is `"Float32"`, then
1. Let _rawBytes_ be a List containing the 4 bytes that are the result of converting _value_ to IEEE 754-2008 binary32 format using &ldquo;Round to nearest, ties to even&rdquo; rounding mode. If _isLittleEndian_ is *false*, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If _value_ is *NaN*, _rawBytes_ may be set to any implementation chosen IEEE 754-2008 binary32 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable *NaN* value.
1. Let _rawBytes_ be a List containing the 4 bytes that are the result of converting _value_ to IEEE 754-2008 binary32 format using &ldquo;Round to nearest, ties to even&rdquo; rounding mode. If _isLittleEndian_ is *false*, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If _value_ is *NaN*, _rawBytes_ may be set to any implementation chosen IEEE 754-2008 binary32 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable *NaN* value, or an implementation-defined canonical value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@littledan

Copy link
Copy Markdown
Member Author

Thanks for the reviews everyone!

@ljharb

ljharb commented Apr 26, 2019

Copy link
Copy Markdown
Member

@littledan would you mind rebasing this PR, and summarizing the current status? I believe it has consensus, and i'm not sure if it still needs tests.

@rwaldron can you rereview after the PR is rebased?

@ljharb ljharb added the normative change Affects behavior required to correctly evaluate some ECMAScript source text label Apr 26, 2019
@littledan

Copy link
Copy Markdown
Member Author

Well, I would not feel comfortable making the change suggested above, to give more guarantees to Float32, given that this is unlikely to correspond to actual implementations. I would rather move this patch to removing any guarantees about how floats are canonicalized, given the information from @jfbastien, about how this is the implementation reality and is unlikely to change. @waldemarhorwat encouraged this path as well. @erights, would you be OK with this change?

@erights

erights commented Apr 26, 2019

Copy link
Copy Markdown

Yes, this change looks good to me.

@littledan

Copy link
Copy Markdown
Member Author

@erights To be concrete, I'm talking about, not this PR, but instead a PR to say that there's no stability for NaN (since this is the implementation reality).

@erights

erights commented Apr 27, 2019

Copy link
Copy Markdown

@erights To be concrete, I'm talking about, not this PR, but instead a PR to say that there's no stability for NaN (since this is the implementation reality).

Thanks for clarifying. I did not know that. Link to other PR?

@aapoalas

Copy link
Copy Markdown
Contributor

It was suggested in Matrix that the entire language about choosing the same NaN when serialising multiple times should be removed as spec fiction. Perhaps this PR could be repurposed for that?

ptomato pushed a commit to ptomato/ecma262 that referenced this pull request Feb 16, 2026
While hour12 is either true or false, let hourCycle to be either 'h12' or 'h23' but not 'h11' nor 'h24'.

The current logic is not reasonable. We see no region in the CLDR use h11 nor h24 hour cycle as default. 
While we set a hour12: true on a 24 hour system region (which use 0:00 - 23:59), we should set to h12 instead of h11

Do not perform the starnge logic of setting default hourCycle based on both hour12 and the defaultHourCycle while hour12 is true or false. Instead, only depends on hour12 to set it to h12 or h23. 

Address tc39/ecma402#402

Co-authored-by: Shane F. Carr <sffc@google.com>
Co-authored-by: Ujjwal Sharma <ryzokuken@igalia.com>
Co-authored-by: André Bargull <andre.bargull@gmail.com>
@ChALkeR

ChALkeR commented Feb 18, 2026

Copy link
Copy Markdown

in v8, the observable behavior is seemingly random between calls there
in might canonicalize some nans but not the other identical ones in the same code on different calls
if this is what the spec change is meant to signal then perhaps that could be made a bit more clear?

@erights

erights commented Feb 18, 2026

Copy link
Copy Markdown

observable behavior is seemingly random between calls

Why? Such observable dynamic non-determinism opens side-channels even when when observers cannot measure duration.

@ChALkeR

ChALkeR commented Feb 18, 2026

Copy link
Copy Markdown

@erights that's how v8 behaves

And, reading the proposed spec change now, I assume that standarizing that was the intention of this PR
Also the word "occasional" in the PR description
Also #758 (comment)

And I'm not sure that that's worded in the spec clearly enough?

const n = new Float64Array(new Uint8Array(8).fill(0xff).buffer)[0]
;[
  NaN, NaN, NaN, NaN, NaN, NaN,
  n, n, n, n, n, n, n,
].forEach((x, i) => {
  var str = new Uint8Array(new Float64Array([x]).buffer).toString()  
  console.log(i, str)
})

prints:

0 0,0,0,0,0,0,248,127
1 0,0,0,0,0,0,248,127
2 0,0,0,0,0,0,248,127
3 0,0,0,0,0,0,248,127
4 0,0,0,0,0,0,248,127
5 0,0,0,0,0,0,248,127
6 255,255,255,255,255,255,255,255
7 255,255,255,255,255,255,255,255
8 255,255,255,255,255,255,255,255
9 0,0,0,0,0,0,248,127
10 255,255,255,255,255,255,255,255
11 255,255,255,255,255,255,255,255
12 255,255,255,255,255,255,255,255

Note the value on 9

It does return "either a particular value for the implementation-distinguishable NaN value or an implementation-defined canonical value".

Which follows the proposed spec change.

The nuance is that it does that in a non-deterministic way

An implementation must always choose the same encoding for each implementation distinguishable NaN value, or an implementation-defined canonical value.

This can be misinterpreted in a way that one would think that it always chooses the same encoding or always returns an implementation-defined canonical value.

While what this actually means is that behavior can be either of that at any point.

@ljharb

ljharb commented Feb 18, 2026

Copy link
Copy Markdown
Member

The intention of our 2015 Munich plenary discussion, from what I recall, is essentially that "outside of Typed Arrays, there is one distinguishable NaN in the language. Typed Arrays can expose bit patterns in an implementation-defined way - meaning, there is no consistency expected or required"

@ChALkeR

ChALkeR commented Feb 18, 2026

Copy link
Copy Markdown

@ljharb that makes sense, but non-determinism could be indeed a side-channel
was that considered?

e.g. Spectre was announced in 2018, after that discussion 🙃

@erights

erights commented Feb 18, 2026

Copy link
Copy Markdown

@erights that's how v8 behaves

Why does v8 behave that way?

@ChALkeR

ChALkeR commented Feb 18, 2026

Copy link
Copy Markdown

Why does v8 behave that way?

I'm unsure i'm just a passerby here sorry.

What I wanted to bring the attention to is that this spec change explicitly allows that, and that allowing that non-determinism seems like the intention1 of this change (which was reviewed, even though, pre-Spectre).

And that the wording around that is not very obvious

Footnotes

  1. from (1) "legalizes V8's occasional canonicalization" + me checking how V8 behaves, (2) word "optionally" in title, and (3) https://github.com/tc39/ecma262/pull/758#issuecomment-370379042, (4) https://github.com/tc39/ecma262/issues/635#issuecomment-2813725627

@ChALkeR

ChALkeR commented Feb 18, 2026

Copy link
Copy Markdown

Why does v8 behave that way?

Perhaps the answer would be "because that was the consensus of the discussion back in 2015"?

Also "because tc39/test262#1476 landed"?

@erights

erights commented Feb 18, 2026

Copy link
Copy Markdown

What I think I remember from those old times:

Mozilla/FF/SpiderMonkey used NaN-boxing, and so always canonicalized genuine NaNs. I don't know if it was the same canonical NaN on little endian vs big endian architectures.

Google was not (and I assume is not) boxing their NaNs, and was not willing to pay in performance to do so, when the perf cost would be significant (which I believe) and the benefit is so small "No one would care."

I remember resisting a bit but giving up in the face of this genuinely compelling argument from Google.

However, I still tried to find some limitation we could agree on that would limit observable dynamic side channels. What I should have anticipated is that if the difference is too hard to test, no one would test it and implementers would proceed to violate it with no feedback. I do not remember even thinking about how to write test262 tests.

In the era before Spectre and Meltdown, I am sure most of my fears about side channels fell on deaf ears.

Again, my memories are terribly stale. I am often inaccurate of such time gaps.

@erights

erights commented Feb 18, 2026

Copy link
Copy Markdown

Glad to see all that test262 attention to these issues! I did not remember that.

@ChALkeR

ChALkeR commented Feb 18, 2026

Copy link
Copy Markdown

@ljharb

Typed Arrays can expose bit patterns in an implementation-defined way

There is (usually) a difference between implementation-defined and undefined, and what's happening is more like "undefined". v8 does not define a consistent behavior for this.

Upd: I see the definition though:

An implementation-defined facility is one that defers its definition to an external source without further qualification. This specification does not make any recommendations for particular behaviours, and conforming implementations are free to choose any behaviour within the constraints put forth by this specification.

It's unfortunate that the spec doesn't put a difference between consistent and unknown-potentially-non-deterministic non-defined-in-spec behavior (like e.g. C++ spec does for defns.impl.defined / defns.unspecified / defns.undefined), and that "implementation defined" is misleading in that aspect. It's not actually defined anywhere.

@erights

erights commented Feb 18, 2026

Copy link
Copy Markdown

"implementation-defined" means not spec defined.

@michaelficarra

Copy link
Copy Markdown
Member

@ChALkeR

It's unfortunate that the spec doesn't put a difference between consistent and unknown-potentially-non-deterministic non-defined-in-spec behavior (like e.g. C++ spec does for defns.impl.defined / defns.unspecified / defns.undefined), and that "implementation defined" is misleading in that aspect.

That's because we don't need to; there's no undefined behaviour in JS. There's plenty of places where implementations are given the choice to do something or not, or are given a choice of values to produce, or are given the freedom to approximate arithmetic results, but they are never given the opportunity to throw up their hands and do anything they want. If you have a specific suggestion for a place where you think that is unclear, I'd love to hear it.

It's not actually defined anywhere.

It's expected that conformant implementations would provide a description of all the choices their implementation makes in these positions. In practice, most implementations don't. Also in practice, most implementations (and their users) don't actually care about strict conformance.

The exact text in NumericToRawBytes is

If value is NaN, rawBytes may be set to any implementation chosen IEEE 754-2019 binary64 format NaN encoding. An implementation must always choose the same encoding for each implementation distinguishable NaN value.

Based on your observations, Chrome's behaviour does not conform with the latter part of that requirement.

@ChALkeR

ChALkeR commented Feb 18, 2026

Copy link
Copy Markdown

@michaelficarra

The exact text in NumericToRawBytes is

This (#758, where we are now) is a PR aimed to change that exact wording you cited and legalize Chrome behavior in spec.

And that is actually undefined non-deterministic behavior.

That's because we don't need to; there's no undefined behaviour in JS.

What exactly in the spec would forbid non-determinism there once this PR is landed?

It's expected that conformant implementations would provide a description of all the choices their implementation makes in these positions.

Compare ECMAScript implementation-defined to C++ implementation-defined and C++ unspecified.
The difference is requiring that behavior to be documented somewhere.

@o-

o- commented Feb 23, 2026

Copy link
Copy Markdown

V8 does not guarantee a stable NaN pattern. E.g., we use different patterns for different purposes. I checked the latest discussion notes on the topic I don't think that the idea was to request a particular NaN pattern but instead allow any NaN pattern to occur.

Post-spectre we abandoned the idea of in-process read isolation. Hence there is no need to canonicalize from that pov either.

@ChALkeR

ChALkeR commented Feb 23, 2026

Copy link
Copy Markdown

@erights

fears about side channels

Well, it's definitely a side channel in Chrome
Here is a demo: https://junk.rray.org/poc/nani.html

It can track gc and communicate over the side-channel cross-origin (in the same process), bypass iframe storage partitioning from different sites, detect devtools, etc.

@erights

erights commented Feb 24, 2026

Copy link
Copy Markdown

Thanks @ChALkeR , you've done what I never figured out how to do: demonstrate that it is a real side channel that should be concerning. Thanks for pointing out that it is

not using SharedArrayBuffer, timing, or anything outside of ECMAScript spec

After Meltdown and Spectre, it has been easy to raise the alarm about side channels that can be read by those who can somehow measure duration. It has been harder to get people to appreciate two bracketing issues:

  • Hardened JS, and the SES shim of Hardened JS, are both able to deny confined code the ability to measure duration.
  • Any dynamic spec non-determinism might enable side channels even to those that are denied the ability to measure duration. (Static spec non-determinism leads to static side channel concerns about fingerprinting, but lets keep that separate.)

See https://papers.agoric.com/taxonomy-of-security-issues/

GC observability is another timing-independent side channel. This is why we so carefully distinguished WeakMap/WeakSet (spec determinism) from WeakRef/FinalizationRegistry (observable gc decisions). And why we then tried to severely reduce the magnitude of that side channel (by making gc within a turn hard to observe, and by allowing the omission of the WeakRef.prototype.constructor === WeakRef link.) It is also why Hardened JS does not classify WeakRef and FinalizationRegistry as globals that are safe to provide and implicitly share.

It is also why we originally proposed that a WeakRef in realm A holding a target from distinct realm B act as a strong reference. This would severely limit the magnitude of the inter-realm gc-observation side channel. Alas, not all engines already had the bookkeeping needed to distinguish realm-of-origin. Implementors were understandably reluctant to consider pervasive extra bookkeeping for an obscure corner case.

@ChALkeR

ChALkeR commented Feb 24, 2026

Copy link
Copy Markdown

SES

SES directly contradicts both Node.js security model and (as it turns out) V8 security model too
It's in a state that there are existing caveats in Node.js that defeat SES but it's neither a Node.js bug nor SES responsibility - so no one is responsible for that, but the whole thing is very fragile.

I'd be happy to chat about that before posting a public rant about how it doesn't work 🙃 (which is smth I've been postponing for a while now)

I have demos
(As far as I remember - including on just the code from ses readme)

Feel free to DM me in OpenJSF Slack about that.

@ChALkeR

ChALkeR commented Feb 27, 2026

Copy link
Copy Markdown

For context, chromium issue which works as intended: https://issues.chromium.org/issues/485920246

@erights

erights commented Feb 27, 2026

Copy link
Copy Markdown

@ChALkeR , how did you come up with the exploits at https://junk.rray.org/poc/nani.html ? Reliably reproducible demos of NaN side channels! Several of us have looked, and it seems like black magic to discover these. Any advice you can offer for other to learn from?

@erights erights added the needs consensus This needs committee consensus before it can be eligible to be merged. label Feb 27, 2026
@erights

erights commented Feb 27, 2026

Copy link
Copy Markdown

In light of @ChALkeR 's attack, I added back the "needs consensus" label. I understand why Chromium classifies this attack as "works as intended". But I do not think that tc39 should effectively do so itself.

erights added a commit to endojs/endo that referenced this pull request Apr 7, 2026
Closes: #XXXX
Refs: #XXXX

- [x] TODO cite Nikita's NaN side-channel demonstration.

## Description

The JavaScript language can leak the bit encoding of a NaN via shared
TypedArray views of an common ArrayBuffer. Although the JavaScript
language has only one NaN value, the underlying IEEE 754
double-precision floating-point representation has many different bit
patterns that represent NaN. This can be exploited as a side-channel to
leak information. This actually happens on some platforms such as v8.

@ChALkeR explains at
tc39/ecma262#758 (comment) that
the behavior of this side-channel on v8. At
https://junk.rray.org/poc/nani.html he demonstrates it, and it indeed
even worse than I expected.

To plug this side-channel, we make two coordinated changes.
* We stop listing the `Float*Array` constructors as universal globals.
This prevents them from being implicitly endowed to created
compartments, because they are not harmless. However, we still keep them
on the start compartment (the original global), consider them
intrinsics, and still repair and harden them on `lockdown()`. Thus, they
can be explicitly endowed to child compartments at the price of enabling
code in that compartment to read the side-channel.
* On `lockdown()`, we repair the `DataView.prototype.setFloat*` methods
so that they only write canonical NaNs into the underlying ArrayBuffer.

The `@endo.marshal` package's `encodePassable` encodings need to obtain
the bit representation of floating point values. It had used
`Float64Array` for that. However, sometimes the `@endo/marshal` package
is evaluated in a created compartment that would now lack that
constructor. (This reevaluation typically occurs when bundling bundles
in that package.) So instead, `encodePassable` now uses the `DataView`
methods which are now safe.


### Security Considerations

The point. This NaN side-channel in ses had been implicit, easily
forgotten, and hard to plug in user code.

### Scaling Considerations

Not really.

### Documentation Considerations

Somewhere we need to document that the `Float*Array` constructors are
not available in created compartments by default, and why.

### Testing Considerations

New tests included.

One test newly skipped because it always hangs for me locally, which is
annoying. ***Reviewers***, feel free to ask me to unskip it.

### Compatibility Considerations

The absence of the `Float*Array` constructors in created compartments is
potentially a compat break. In fact our need to repair `encodePassable`
demonstrates such a compat break.

### Upgrade Considerations

none.
@gibson042 gibson042 changed the title Normative: Allow NaN values to be *optionally* canoncalized Normative: Allow NaN values to be *optionally* canonicalized Apr 9, 2026
turadg added a commit to endojs/endo that referenced this pull request Apr 16, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to master, this PR
will be updated.


# Releases
## @endo/ocapn@1.0.0

### Major Changes

- [#3183](#3183)
[`279c0c4`](279c0c4)
Thanks [@kumavis](https://github.com/kumavis)! - Initial public release
of `@endo/ocapn`. The package is no longer private and is now published
to npm.

Tested against the python test suite from 2026-01-06
<https://github.com/ocapn/ocapn-test-suite/commits/f0273f21c5ee05a28785b51c231535124f28bca9>

### Minor Changes

- [#3172](#3172)
[`6405b36`](6405b36)
Thanks [@turadg](https://github.com/turadg)! - Parameterize CapTP slot
types and improve TypeScript 6 conformance across the OCapN client
surface. Compile-time type changes only; no runtime behavior changes.

### Patch Changes

- Updated dependencies
\[[`f65b000`](f65b000),
[`d1d9625`](d1d9625),
[`88bc2b9`](88bc2b9),
[`e619205`](e619205),
[`43165e5`](43165e5),
[`6ada52b`](6ada52b)]:
    -   @endo/eventual-send@1.5.0
    -   @endo/promise-kit@1.2.1
    -   @endo/pass-style@1.8.0
    -   @endo/marshal@1.9.1
    -   @endo/harden@1.1.0
    -   @endo/nat@5.2.0

## ses@2.0.0

### Major Changes

- [#3153](#3153)
[`e619205`](e619205)
Thanks [@erights](https://github.com/erights)! - # Plug NaN Side-channel

The JavaScript language can leak the bit encoding of a NaN via shared
TypedArray views of an common ArrayBuffer. Although the JavaScript
language has only one NaN value, the underlying IEEE 754
double-precision floating-point representation has many different bit
patterns that represent NaN. This can be exploited as a side-channel to
leak information. This actually happens on some platforms such as v8.

@ChALkeR explains at
<tc39/ecma262#758 (comment)> that
the behavior of this side-channel on v8. At
<https://junk.rray.org/poc/nani.html> he demonstrates it, and it indeed
even worse than I expected.

    To plug this side-channel, we make two coordinated changes.

- We stop listing the `Float*Array` constructors as universal globals.
This prevents them from being implicitly endowed to created
compartments, because they are not harmless. However, we still keep them
on the start compartment (the original global), consider them
intrinsics, and still repair and harden them on `lockdown()`. Thus, they
can be explicitly endowed to child compartments at the price of enabling
code in that compartment to read the side-channel.
- On `lockdown()`, we repair the `DataView.prototype.setFloat*` methods
so that they only write canonical NaNs into the underlying ArrayBuffer.

The `@endo.marshal` package's `encodePassable` encodings need to obtain
the bit representation of floating point values. It had used
`Float64Array` for that. However, sometimes the `@endo/marshal` package
is evaluated in a created compartment that would now lack that
constructor. (This reevaluation typically occurs when bundling bundles
in that package.) So instead, `encodePassable` now uses the `DataView`
methods which are now safe.

### Minor Changes

- [#3129](#3129)
[`a675d8e`](a675d8e)
Thanks [@erights](https://github.com/erights)! - `overrideTaming:
'moderate'` includes `overrideTaming: 'min'`.

Previously `overrideTaming: 'min'` correctly enabled
`Iterator.prototype.constructor` to be overridden by assignment, but due
to an oversight, `overrideTaming: 'moderate'` did not. Now it does.

To make such mistakes less likely, this PR also adopts a style where all
records within larger enablements triple-dot the corresponding record
from a smaller enablement, if present.

## @endo/bundle-source@4.3.0

### Minor Changes

- [#3180](#3180)
[`7f7ae8e`](7f7ae8e)
Thanks [@turadg](https://github.com/turadg)! - `BundleCache.load()` is
now generic on the `format` option:

- Omitted (default) → `Promise<BundleSourceResult<'endoZipBase64'>>`
    -   Literal format → `Promise<BundleSourceResult<format>>`
- Runtime-typed `ModuleFormat` →
`Promise<BundleSourceResult<ModuleFormat>>`

Previously `load()` returned `Promise<unknown>`, requiring callers to
assert the bundle shape.

### Patch Changes

- Updated dependencies
\[[`154102b`](154102b),
[`2b674ca`](2b674ca),
[`d1d9625`](d1d9625),
[`b4820dc`](b4820dc),
[`acbacba`](acbacba),
[`cdb6eae`](cdb6eae),
[`6ada52b`](6ada52b),
[`6ad084a`](6ad084a),
[`1cd1246`](1cd1246)]:
    -   @endo/compartment-mapper@2.1.0
    -   @endo/promise-kit@1.2.1
    -   @endo/harden@1.1.0

## @endo/common@1.4.0

### Minor Changes

- [#3172](#3172)
[`98c89b7`](98c89b7)
Thanks [@turadg](https://github.com/turadg)! - Add `objectExtendEach`
helper for merging a sequence of objects into an accumulator, with
precise TypeScript inference of the resulting intersection type.

### Patch Changes

- Updated dependencies
\[[`f65b000`](f65b000),
[`d1d9625`](d1d9625)]:
    -   @endo/eventual-send@1.5.0
    -   @endo/promise-kit@1.2.1
    -   @endo/errors@1.3.1
    -   @endo/harden@1.1.0

## @endo/compartment-mapper@2.1.0

### Minor Changes

- [#3132](#3132)
[`b4820dc`](b4820dc)
Thanks [@boneskull](https://github.com/boneskull)! - Expose
`_redundantPreloadHook` option in `captureFromMap()`, which will be
called for each item in the `_preload` array that was already indirectly
loaded via the entry `Compartment`.

Fixes a bug in the type of `_preload` option, which now allows for mixed
arrays.

Fixes a bug in the preloader, which was not exhaustively checking if a
non-entry module was already loaded via the entry `Compartment`.

- [#3048](#3048)
[`6ad084a`](6ad084a)
Thanks [@kriskowal](https://github.com/kriskowal)! - Add support for
Node.js subpath pattern replacement in `package.json` `exports` and
`imports` fields. Patterns like `"./features/*.js":
"./src/features/*.js"` and `"#internal/*.js": "./lib/*.js"` are now
resolved at link time using prefix/suffix string matching with
specificity ordering. Null-target patterns exclude matching specifiers.
Conditional pattern values are resolved through the standard
condition-matching rules. Patterns are expanded to concrete module
entries during archiving.

### Patch Changes

- [#3111](#3111)
[`154102b`](154102b)
Thanks [@boneskull](https://github.com/boneskull)! - Fix type of
`PackageDataHook.packageData` which now correctly allows `$root$` as a
key.

- [#3182](#3182)
[`2b674ca`](2b674ca)
Thanks [@kriskowal](https://github.com/kriskowal)! - Cull
underscore-prefixed internal properties (like `__createdBy`) from
serialized compartment maps in archives. The compartment map validator
    now also ignores underscore-prefixed properties when checking for
    extraneous fields.

- [#3173](#3173)
[`acbacba`](acbacba)
Thanks [@boneskull](https://github.com/boneskull)! - Fixes potential
issue wherein a canonical name may be computed incorrectly. Includes
performance improvements.

- [#3157](#3157)
[`cdb6eae`](cdb6eae)
Thanks [@boneskull](https://github.com/boneskull)! - Dramatically
improve performance of canonical name (shortest path) computation in
`mapNodeModules()`.

- [#3127](#3127)
[`6ada52b`](6ada52b)
Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime
dependencies from package manifests.

- [#3115](#3115)
[`1cd1246`](1cd1246)
Thanks [@boneskull](https://github.com/boneskull)! - Remove unused
"error" `ModuleSourceHookModuleSource` type.

- Updated dependencies
\[[`e619205`](e619205),
[`6ada52b`](6ada52b),
[`a675d8e`](a675d8e)]:
    -   ses@2.0.0
    -   @endo/module-source@1.4.1

## @endo/eventual-send@1.5.0

### Minor Changes

- [#3172](#3172)
[`f65b000`](f65b000)
Thanks [@turadg](https://github.com/turadg)! - Improve `E()` type
inference and publicly export method-projection helpers.

- `RemoteFunctions`, `PickCallable`, and `ECallableOrMethods` now
short-circuit on `any`, preventing `E(anyValue)` from collapsing to an
unusable type.
- `EMethods`, `EGetters`, and related helpers are now part of the public
type surface, so downstream packages can name the projected shapes `E()`
produces.

    Compile-time type changes only; no runtime behavior changes.

### Patch Changes

-   Updated dependencies \[]:
    -   @endo/harden@1.1.0

## @endo/exo@1.7.0

### Minor Changes

- [#3172](#3172)
[`88bc2b9`](88bc2b9)
Thanks [@turadg](https://github.com/turadg)! - Improve TypeScript
inference for patterns, exo, and pass-style. These are compile-time type
changes only; no runtime behavior changes.

- **pass-style**: `CopyArray<T>` is now `readonly T[]` so readonly
tuples (e.g. `readonly ['ibc']`) satisfy `Passable`. Backward-compatible
because `T[]` still extends `readonly T[]`.
- **patterns**: `M.remotable()` defaults to `any` (matching
`M.promise()`), so unparameterized remotables are assignable to concrete
remotable typedefs. The parameterized form `M.remotable<typeof
SomeInterfaceGuard>()` still yields precise inference.
- **patterns**: `TFRemotable` returns `any` (not `Payload`) for
non-`InterfaceGuard` arguments.
- **patterns**: `TFOr` handles array-of-patterns and falls back through
`TFAnd`; `M.undefined()` maps to `void`.
- **patterns**: `TFOptionalTuple` emits truly optional elements;
`M.promise()` maps to `PromiseLike`.
- **patterns**: `TFSplitRecord` handles the empty-rest case correctly.
    -   **patterns**: `TFRestArgs` unwraps array patterns.
- **patterns**: `TypeFromArgGuard` discriminates by `toStringTag`, not
structural shape.
- **patterns**: `MatcherOf` payload is preserved through
`InterfaceGuard`.
- **patterns**: new `CastedPattern<T>` for unchecked type assertions in
pattern position.
- **exo**: `defineExoClass`, `defineExoClassKit`, and `makeExo` no
longer intersect facet constraints with `& Methods`. The previous
constraint collapsed specific facet keys into the `string | number |
symbol` index signature, making `FilteredKeys` return `never` and
erasing facet method inference (`Pick<X, never> = {}`).
- **exo**: `Guarded<M, G>` is now structurally compatible across `G`,
and the kit `F` constraint is widened.
- **exo**: `defineExoClassKit` preserves facet inference when no guard
is supplied.

TypeScript consumers that were working around the previous inference
gaps with casts may be able to remove those casts. Downstream code that
depended on the narrower `CopyArray<T> = T[]` or the previous
`M.remotable()` default may need minor adjustments.

- [#3133](#3133)
[`9111b4e`](9111b4e)
Thanks [@turadg](https://github.com/turadg)! - feat: infer TypeScript
types from pattern guards

- `TypeFromPattern<P>` — infer static types from any pattern matcher
- `TypeFromMethodGuard<G>` — infer function signatures from `M.call()` /
`M.callWhen()` guards
- `TypeFromInterfaceGuard<G>` — infer method records from interface
guard definitions
- `M.remotable<typeof Guard>()` — facet-isolated return types in exo
kits
- `M.infer<typeof pattern>` — namespace shorthand analogous to `z.infer`
- `matches` and `mustMatch` now narrow the specimen type via type
predicates
- `makeExo`, `defineExoClass`, and `defineExoClassKit` enforce method
signatures against guards at compile time

These are compile-time type changes only; there are no runtime
behavioral changes.
Existing TypeScript consumers may see new type errors where method
signatures diverge from their guards.

### Patch Changes

- Updated dependencies
\[[`8195a5a`](8195a5a),
[`98c89b7`](98c89b7),
[`f65b000`](f65b000),
[`88bc2b9`](88bc2b9),
[`9111b4e`](9111b4e),
[`43165e5`](43165e5),
[`df84eea`](df84eea),
[`6ada52b`](6ada52b)]:
    -   @endo/patterns@1.9.0
    -   @endo/common@1.4.0
    -   @endo/eventual-send@1.5.0
    -   @endo/pass-style@1.8.0
    -   @endo/errors@1.3.1
    -   @endo/far@1.1.14
    -   @endo/harden@1.1.0

## @endo/pass-style@1.8.0

### Minor Changes

- [#3172](#3172)
[`88bc2b9`](88bc2b9)
Thanks [@turadg](https://github.com/turadg)! - Improve TypeScript
inference for patterns, exo, and pass-style. These are compile-time type
changes only; no runtime behavior changes.

- **pass-style**: `CopyArray<T>` is now `readonly T[]` so readonly
tuples (e.g. `readonly ['ibc']`) satisfy `Passable`. Backward-compatible
because `T[]` still extends `readonly T[]`.
- **patterns**: `M.remotable()` defaults to `any` (matching
`M.promise()`), so unparameterized remotables are assignable to concrete
remotable typedefs. The parameterized form `M.remotable<typeof
SomeInterfaceGuard>()` still yields precise inference.
- **patterns**: `TFRemotable` returns `any` (not `Payload`) for
non-`InterfaceGuard` arguments.
- **patterns**: `TFOr` handles array-of-patterns and falls back through
`TFAnd`; `M.undefined()` maps to `void`.
- **patterns**: `TFOptionalTuple` emits truly optional elements;
`M.promise()` maps to `PromiseLike`.
- **patterns**: `TFSplitRecord` handles the empty-rest case correctly.
    -   **patterns**: `TFRestArgs` unwraps array patterns.
- **patterns**: `TypeFromArgGuard` discriminates by `toStringTag`, not
structural shape.
- **patterns**: `MatcherOf` payload is preserved through
`InterfaceGuard`.
- **patterns**: new `CastedPattern<T>` for unchecked type assertions in
pattern position.
- **exo**: `defineExoClass`, `defineExoClassKit`, and `makeExo` no
longer intersect facet constraints with `& Methods`. The previous
constraint collapsed specific facet keys into the `string | number |
symbol` index signature, making `FilteredKeys` return `never` and
erasing facet method inference (`Pick<X, never> = {}`).
- **exo**: `Guarded<M, G>` is now structurally compatible across `G`,
and the kit `F` constraint is widened.
- **exo**: `defineExoClassKit` preserves facet inference when no guard
is supplied.

TypeScript consumers that were working around the previous inference
gaps with casts may be able to remove those casts. Downstream code that
depended on the narrower `CopyArray<T> = T[]` or the previous
`M.remotable()` default may need minor adjustments.

- [#3184](#3184)
[`43165e5`](43165e5)
Thanks [@turadg](https://github.com/turadg)! - Unblock TypeScript
declaration emit in downstream packages that structurally expose
`PassStyled`/`Container` types. Compile-time type changes only; no
runtime behavior changes.

- `PASS_STYLE` is now typed as the string-literal `'Symbol(passStyle)'`
rather than `unique symbol`. The runtime value is unchanged (still
`Symbol.for('passStyle')`), and computed-key indexing like
`obj[PASS_STYLE]` continues to work because JS computed keys accept any
value. This removes TS4023 / TS9006 errors in consumers whose inferred
types structurally contain `[PASS_STYLE]` (via `PassStyled`,
`ExtractStyle`, object spread of a `PassStyled`, etc.). A `unique
symbol` is only nameable via its original declaration module, which
consumers have no reason to import; a string-literal type has no such
nameability requirement.
- `CopyArrayInterface`, `CopyRecordInterface`, and `CopyTaggedInterface`
are now exported, so downstream `.d.ts` emit can name them when they
appear through structural expansion of `Passable`/`Container`.
- The `PassStyleOf` array overload is widened from `(p: any[]) =>
'copyArray'` to `(p: readonly any[]) => 'copyArray'`, so `as const`
tuples and `readonly T[]` values classify as `'copyArray'`. This aligns
the classifier with `CopyArray<T>`, which is already `readonly T[]`.
Backward-compatible because `T[]` still extends `readonly T[]`.

Obviates the `@endo/pass-style` patch that agoric-sdk has been carrying
in `.yarn/patches/`.

TypeScript consumers that relied on `typeof PASS_STYLE` being `unique
symbol` (e.g. annotating a value as `symbol` from `PASS_STYLE`) will
need minor adjustments — widen the annotation to `symbol | string`, or
cast via `unknown`.

### Patch Changes

- [#3127](#3127)
[`6ada52b`](6ada52b)
Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime
dependencies from package manifests.

- Updated dependencies
\[[`98c89b7`](98c89b7),
[`f65b000`](f65b000),
[`d1d9625`](d1d9625)]:
    -   @endo/common@1.4.0
    -   @endo/eventual-send@1.5.0
    -   @endo/promise-kit@1.2.1
    -   @endo/errors@1.3.1
    -   @endo/harden@1.1.0

## @endo/patterns@1.9.0

### Minor Changes

- [#3067](#3067)
[`8195a5a`](8195a5a)
Thanks [@gibson042](https://github.com/gibson042)! - - Updates
`containerHasSplit` to consider copyArray elements in forward order,
    better aligning with intuition.

- [#3172](#3172)
[`88bc2b9`](88bc2b9)
Thanks [@turadg](https://github.com/turadg)! - Improve TypeScript
inference for patterns, exo, and pass-style. These are compile-time type
changes only; no runtime behavior changes.

- **pass-style**: `CopyArray<T>` is now `readonly T[]` so readonly
tuples (e.g. `readonly ['ibc']`) satisfy `Passable`. Backward-compatible
because `T[]` still extends `readonly T[]`.
- **patterns**: `M.remotable()` defaults to `any` (matching
`M.promise()`), so unparameterized remotables are assignable to concrete
remotable typedefs. The parameterized form `M.remotable<typeof
SomeInterfaceGuard>()` still yields precise inference.
- **patterns**: `TFRemotable` returns `any` (not `Payload`) for
non-`InterfaceGuard` arguments.
- **patterns**: `TFOr` handles array-of-patterns and falls back through
`TFAnd`; `M.undefined()` maps to `void`.
- **patterns**: `TFOptionalTuple` emits truly optional elements;
`M.promise()` maps to `PromiseLike`.
- **patterns**: `TFSplitRecord` handles the empty-rest case correctly.
    -   **patterns**: `TFRestArgs` unwraps array patterns.
- **patterns**: `TypeFromArgGuard` discriminates by `toStringTag`, not
structural shape.
- **patterns**: `MatcherOf` payload is preserved through
`InterfaceGuard`.
- **patterns**: new `CastedPattern<T>` for unchecked type assertions in
pattern position.
- **exo**: `defineExoClass`, `defineExoClassKit`, and `makeExo` no
longer intersect facet constraints with `& Methods`. The previous
constraint collapsed specific facet keys into the `string | number |
symbol` index signature, making `FilteredKeys` return `never` and
erasing facet method inference (`Pick<X, never> = {}`).
- **exo**: `Guarded<M, G>` is now structurally compatible across `G`,
and the kit `F` constraint is widened.
- **exo**: `defineExoClassKit` preserves facet inference when no guard
is supplied.

TypeScript consumers that were working around the previous inference
gaps with casts may be able to remove those casts. Downstream code that
depended on the narrower `CopyArray<T> = T[]` or the previous
`M.remotable()` default may need minor adjustments.

- [#3133](#3133)
[`9111b4e`](9111b4e)
Thanks [@turadg](https://github.com/turadg)! - feat: infer TypeScript
types from pattern guards

- `TypeFromPattern<P>` — infer static types from any pattern matcher
- `TypeFromMethodGuard<G>` — infer function signatures from `M.call()` /
`M.callWhen()` guards
- `TypeFromInterfaceGuard<G>` — infer method records from interface
guard definitions
- `M.remotable<typeof Guard>()` — facet-isolated return types in exo
kits
- `M.infer<typeof pattern>` — namespace shorthand analogous to `z.infer`
- `matches` and `mustMatch` now narrow the specimen type via type
predicates
- `makeExo`, `defineExoClass`, and `defineExoClassKit` enforce method
signatures against guards at compile time

These are compile-time type changes only; there are no runtime
behavioral changes.
Existing TypeScript consumers may see new type errors where method
signatures diverge from their guards.

- [#3133](#3133)
[`df84eea`](df84eea)
Thanks [@turadg](https://github.com/turadg)! - Add optional `label`
parameter to `M.promise()`, aligning its signature
    with `M.remotable(label?)`. When a label is provided, runtime error
messages include it for diagnostics (e.g., "Must be a promise Foo, not
    remotable").

### Patch Changes

- [#3127](#3127)
[`6ada52b`](6ada52b)
Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime
dependencies from package manifests.

- Updated dependencies
\[[`98c89b7`](98c89b7),
[`f65b000`](f65b000),
[`88bc2b9`](88bc2b9),
[`e619205`](e619205),
[`43165e5`](43165e5),
[`6ada52b`](6ada52b)]:
    -   @endo/common@1.4.0
    -   @endo/eventual-send@1.5.0
    -   @endo/pass-style@1.8.0
    -   @endo/marshal@1.9.1
    -   @endo/errors@1.3.1
    -   @endo/harden@1.1.0

## @endo/check-bundle@1.1.1

### Patch Changes

- [#3182](#3182)
[`2b674ca`](2b674ca)
Thanks [@kriskowal](https://github.com/kriskowal)! - Cull
underscore-prefixed internal properties (like `__createdBy`) from
serialized compartment maps in archives. The compartment map validator
    now also ignores underscore-prefixed properties when checking for
    extraneous fields.
- Updated dependencies
\[[`154102b`](154102b),
[`2b674ca`](2b674ca),
[`b4820dc`](b4820dc),
[`acbacba`](acbacba),
[`cdb6eae`](cdb6eae),
[`6ada52b`](6ada52b),
[`6ad084a`](6ad084a),
[`1cd1246`](1cd1246)]:
    -   @endo/compartment-mapper@2.1.0
    -   @endo/errors@1.3.1
    -   @endo/harden@1.1.0

## @endo/errors@1.3.1

### Patch Changes

- Updated dependencies
\[[`e619205`](e619205),
[`a675d8e`](a675d8e)]:
    -   ses@2.0.0
    -   @endo/harden@1.1.0

## @endo/import-bundle@1.6.1

### Patch Changes

- Updated dependencies
\[[`154102b`](154102b),
[`2b674ca`](2b674ca),
[`b4820dc`](b4820dc),
[`acbacba`](acbacba),
[`e619205`](e619205),
[`cdb6eae`](cdb6eae),
[`6ada52b`](6ada52b),
[`6ad084a`](6ad084a),
[`1cd1246`](1cd1246),
[`a675d8e`](a675d8e)]:
    -   @endo/compartment-mapper@2.1.0
    -   ses@2.0.0
    -   @endo/errors@1.3.1
    -   @endo/harden@1.1.0

## @endo/lockdown@1.0.19

### Patch Changes

- Updated dependencies
\[[`e619205`](e619205),
[`a675d8e`](a675d8e)]:
    -   ses@2.0.0

## @endo/lp32@1.2.1

### Patch Changes

- [#3127](#3127)
[`6ada52b`](6ada52b)
Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime
dependencies from package manifests.

-   Updated dependencies \[]:
    -   @endo/errors@1.3.1
    -   @endo/harden@1.1.0
    -   @endo/stream@1.3.1

## @endo/marshal@1.9.1

### Patch Changes

- [#3153](#3153)
[`e619205`](e619205)
Thanks [@erights](https://github.com/erights)! - # Plug NaN Side-channel

The JavaScript language can leak the bit encoding of a NaN via shared
TypedArray views of an common ArrayBuffer. Although the JavaScript
language has only one NaN value, the underlying IEEE 754
double-precision floating-point representation has many different bit
patterns that represent NaN. This can be exploited as a side-channel to
leak information. This actually happens on some platforms such as v8.

@ChALkeR explains at
<tc39/ecma262#758 (comment)> that
the behavior of this side-channel on v8. At
<https://junk.rray.org/poc/nani.html> he demonstrates it, and it indeed
even worse than I expected.

    To plug this side-channel, we make two coordinated changes.

- We stop listing the `Float*Array` constructors as universal globals.
This prevents them from being implicitly endowed to created
compartments, because they are not harmless. However, we still keep them
on the start compartment (the original global), consider them
intrinsics, and still repair and harden them on `lockdown()`. Thus, they
can be explicitly endowed to child compartments at the price of enabling
code in that compartment to read the side-channel.
- On `lockdown()`, we repair the `DataView.prototype.setFloat*` methods
so that they only write canonical NaNs into the underlying ArrayBuffer.

The `@endo.marshal` package's `encodePassable` encodings need to obtain
the bit representation of floating point values. It had used
`Float64Array` for that. However, sometimes the `@endo/marshal` package
is evaluated in a created compartment that would now lack that
constructor. (This reevaluation typically occurs when bundling bundles
in that package.) So instead, `encodePassable` now uses the `DataView`
methods which are now safe.

- [#3127](#3127)
[`6ada52b`](6ada52b)
Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime
dependencies from package manifests.

- Updated dependencies
\[[`98c89b7`](98c89b7),
[`f65b000`](f65b000),
[`88bc2b9`](88bc2b9),
[`43165e5`](43165e5),
[`6ada52b`](6ada52b)]:
    -   @endo/common@1.4.0
    -   @endo/eventual-send@1.5.0
    -   @endo/pass-style@1.8.0
    -   @endo/errors@1.3.1
    -   @endo/harden@1.1.0
    -   @endo/nat@5.2.0

## @endo/memoize@1.2.1

### Patch Changes

- [#3107](#3107)
[`05cdb5f`](05cdb5f)
Thanks [@erights](https://github.com/erights)! - `@endo/memoize` no
longer depends on `ses`, just `@endo/harden`

-   Updated dependencies \[]:
    -   @endo/harden@1.1.0

## @endo/module-source@1.4.1

### Patch Changes

- [#3127](#3127)
[`6ada52b`](6ada52b)
Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime
dependencies from package manifests.

- Updated dependencies
\[[`e619205`](e619205),
[`a675d8e`](a675d8e)]:
    -   ses@2.0.0

## @endo/netstring@1.1.1

### Patch Changes

- [#3127](#3127)
[`6ada52b`](6ada52b)
Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime
dependencies from package manifests.

- Updated dependencies
\[[`d1d9625`](d1d9625)]:
    -   @endo/promise-kit@1.2.1
    -   @endo/harden@1.1.0
    -   @endo/stream@1.3.1

## @endo/promise-kit@1.2.1

### Patch Changes

- [#3108](#3108)
[`d1d9625`](d1d9625)
Thanks [@erights](https://github.com/erights)! - `@endo/promise-kit` no
longer depends on `ses`, just `@endo/harden`

-   Updated dependencies \[]:
    -   @endo/harden@1.1.0

## @endo/ses-ava@1.4.1

### Patch Changes

- Updated dependencies
\[[`e619205`](e619205),
[`a675d8e`](a675d8e)]:
    -   ses@2.0.0
    -   @endo/harden@1.1.0

## @endo/stream@1.3.1

### Patch Changes

- Updated dependencies
\[[`f65b000`](f65b000),
[`d1d9625`](d1d9625),
[`e619205`](e619205),
[`a675d8e`](a675d8e)]:
    -   @endo/eventual-send@1.5.0
    -   @endo/promise-kit@1.2.1
    -   ses@2.0.0
    -   @endo/harden@1.1.0

## @endo/stream-node@1.2.1

### Patch Changes

- [#3127](#3127)
[`6ada52b`](6ada52b)
Thanks [@turadg](https://github.com/turadg)! - Remove stale runtime
dependencies from package manifests.

-   Updated dependencies \[]:
    -   @endo/errors@1.3.1
    -   @endo/harden@1.1.0
    -   @endo/stream@1.3.1

## @endo/daemon@2.5.3

### Patch Changes

- Updated dependencies
\[[`8195a5a`](8195a5a),
[`154102b`](154102b),
[`2b674ca`](2b674ca),
[`f65b000`](f65b000),
[`d1d9625`](d1d9625),
[`b4820dc`](b4820dc),
[`88bc2b9`](88bc2b9),
[`9111b4e`](9111b4e),
[`acbacba`](acbacba),
[`e619205`](e619205),
[`df84eea`](df84eea),
[`cdb6eae`](cdb6eae),
[`6ada52b`](6ada52b),
[`6ad084a`](6ad084a),
[`1cd1246`](1cd1246),
[`a675d8e`](a675d8e)]:
    -   @endo/patterns@1.9.0
    -   @endo/compartment-mapper@2.1.0
    -   @endo/eventual-send@1.5.0
    -   @endo/promise-kit@1.2.1
    -   @endo/exo@1.7.0
    -   ses@2.0.0
    -   @endo/marshal@1.9.1
    -   @endo/netstring@1.1.1
    -   @endo/stream-node@1.2.1
    -   @endo/captp@4.5.0
    -   @endo/errors@1.3.1
    -   @endo/far@1.1.14
    -   @endo/harden@1.1.0
    -   @endo/import-bundle@1.6.1
    -   @endo/stream@1.3.1

## @endo/stream-types-test@1.0.19

### Patch Changes

- Updated dependencies
\[[`e619205`](e619205),
[`a675d8e`](a675d8e)]:
    -   ses@2.0.0
    -   @endo/nat@5.2.0
    -   @endo/stream@1.3.1

## @endo/test262-runner@0.1.50

### Patch Changes

- Updated dependencies
\[[`154102b`](154102b),
[`2b674ca`](2b674ca),
[`b4820dc`](b4820dc),
[`acbacba`](acbacba),
[`e619205`](e619205),
[`cdb6eae`](cdb6eae),
[`6ada52b`](6ada52b),
[`6ad084a`](6ad084a),
[`1cd1246`](1cd1246),
[`a675d8e`](a675d8e)]:
    -   @endo/compartment-mapper@2.1.0
    -   ses@2.0.0
kriskowal pushed a commit to endojs/endo-but-for-bots that referenced this pull request Apr 22, 2026
Closes: #XXXX
Refs: #XXXX

- [x] TODO cite Nikita's NaN side-channel demonstration.

## Description

The JavaScript language can leak the bit encoding of a NaN via shared
TypedArray views of an common ArrayBuffer. Although the JavaScript
language has only one NaN value, the underlying IEEE 754
double-precision floating-point representation has many different bit
patterns that represent NaN. This can be exploited as a side-channel to
leak information. This actually happens on some platforms such as v8.

@ChALkeR explains at
tc39/ecma262#758 (comment) that
the behavior of this side-channel on v8. At
https://junk.rray.org/poc/nani.html he demonstrates it, and it indeed
even worse than I expected.

To plug this side-channel, we make two coordinated changes.
* We stop listing the `Float*Array` constructors as universal globals.
This prevents them from being implicitly endowed to created
compartments, because they are not harmless. However, we still keep them
on the start compartment (the original global), consider them
intrinsics, and still repair and harden them on `lockdown()`. Thus, they
can be explicitly endowed to child compartments at the price of enabling
code in that compartment to read the side-channel.
* On `lockdown()`, we repair the `DataView.prototype.setFloat*` methods
so that they only write canonical NaNs into the underlying ArrayBuffer.

The `@endo.marshal` package's `encodePassable` encodings need to obtain
the bit representation of floating point values. It had used
`Float64Array` for that. However, sometimes the `@endo/marshal` package
is evaluated in a created compartment that would now lack that
constructor. (This reevaluation typically occurs when bundling bundles
in that package.) So instead, `encodePassable` now uses the `DataView`
methods which are now safe.


### Security Considerations

The point. This NaN side-channel in ses had been implicit, easily
forgotten, and hard to plug in user code.

### Scaling Considerations

Not really.

### Documentation Considerations

Somewhere we need to document that the `Float*Array` constructors are
not available in created compartments by default, and why.

### Testing Considerations

New tests included.

One test newly skipped because it always hangs for me locally, which is
annoying. ***Reviewers***, feel free to ask me to unskip it.

### Compatibility Considerations

The absence of the `Float*Array` constructors in created compartments is
potentially a compat break. In fact our need to repair `encodePassable`
demonstrates such a compat break.

### Upgrade Considerations

none.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs consensus This needs committee consensus before it can be eligible to be merged. needs test262 tests The proposal should specify how to test an implementation. Ideally via github.com/tc39/test262 normative change Affects behavior required to correctly evaluate some ECMAScript source text

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Should ECMAScript implementations be allowed to canonicalize NaN when writing into an Array?