Skip to content

Non-finite numbers collapse to one identity when the output is used as a digest input #67

Description

@Cubits11

This is a documentation question rather than a bug report — the behaviour matches JSON.stringify, which I assume is deliberate.

safe-stable-stringify is frequently used to produce a stable string for hashing (that is why I was using it). In that use, non-finite numbers merge:

const stringify = require('safe-stable-stringify').default;

stringify(JSON.parse('{"v":1e400}')) // '{"v":null}'
stringify(JSON.parse('{"v":1e401}')) // '{"v":null}'
stringify(JSON.parse('{"v":1e999}')) // '{"v":null}'
All three are syntactically valid JSON (RFC 8259 puts no bound on the exponent) and all three parse to Infinity, so every overflowing literal receives the same digest. Same for NaN where it can arise from computation rather than parsing.

Node v22.22.3, safe-stable-stringify 2.5.0.

Why I am raising it
For logging — the primary use case — null is clearly the right choice, and I am not suggesting changing it. For identity, it silently merges distinct values, and the README's framing ("deterministic and safely JSON stringify") reads to a hashing user as a stronger guarantee than it is: deterministic output for a given input value, not injective over distinct inputs.

For comparison, canonicalize@4.0.0 (RFC 8785) throws on the same input, because JCS §3.2.2.3 requires terminating with an error on Infinity.

Suggested change
A line in the README noting that non-finite numbers serialize to null and therefore are not distinguishable in the output, so callers using the result as a hash input should reject non-finite values first. No code change.

How I found it
A pathology corpus of 31 pre-registered pairs of byte-distinct documents, checking which pairs receive one digest:

npm install -g @ghost-ark/kernel-probe
kernel-probe --command "node your-wrapper.mjs"
Full measurement across five libraries: https://github.com/PSUCyberSecurityLab/ghost-ark/blob/main/docs/research/JCS_CANONICALIZER_PROBE.md

The same corpus also shows duplicate keys collapsing ({"a":1,"a":2} and {"a":2} produce one digest) — that one happens inside JSON.parse before this library is reached, so it is genuinely not this package's concern; I mention it only so the numbers in that document are not misread as five separate library defects.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions