Replace eval with JSON.parse in serialization revive helper#1848
Replace eval with JSON.parse in serialization revive helper#1848
Conversation
devalue.stringify() always produces valid JSON — special values (undefined, NaN, Infinity, -0) are encoded as negative integer sentinels. JSON.parse yields the same flattened array form that unflatten() expects, without the eval anti-pattern (VULN-918). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 3c7e59a The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
| @@ -0,0 +1,6 @@ | |||
| --- | |||
| "@workflow/core": patch | |||
| "workflow": patch | |||
There was a problem hiding this comment.
don't add workflow to changeset. that's implicit from deps like @workflow/core
🧪 E2E Test Results✅ All tests passed Summary
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
✅ 📋 Other
|
📊 Benchmark Results
workflow with no steps💻 Local Development
workflow with 1 step💻 Local Development
workflow with 10 sequential steps💻 Local Development
workflow with 25 sequential steps💻 Local Development
workflow with 50 sequential steps💻 Local Development
Promise.all with 10 concurrent steps💻 Local Development
Promise.all with 25 concurrent steps💻 Local Development
Promise.all with 50 concurrent steps💻 Local Development
Promise.race with 10 concurrent steps💻 Local Development
Promise.race with 25 concurrent steps💻 Local Development
Promise.race with 50 concurrent steps💻 Local Development
workflow with 10 sequential data payload steps (10KB)💻 Local Development
workflow with 25 sequential data payload steps (10KB)💻 Local Development
workflow with 50 sequential data payload steps (10KB)💻 Local Development
workflow with 10 concurrent data payload steps (10KB)💻 Local Development
workflow with 25 concurrent data payload steps (10KB)💻 Local Development
workflow with 50 concurrent data payload steps (10KB)💻 Local Development
Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
stream pipeline with 5 transform steps (1MB)💻 Local Development
10 parallel streams (1MB each)💻 Local Development
fan-out fan-in 10 streams (1MB each)💻 Local Development
SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
karthikscale3
left a comment
There was a problem hiding this comment.
Fix seems straightforward. I will let others take a pass.
TooTallNate
left a comment
There was a problem hiding this comment.
LGTM. Verified devalue.stringify() output is always valid JSON — special values (undefined, NaN, ±Infinity, -0, holes, sparse arrays) are encoded as negative integer sentinels (see constants.js: UNDEFINED=-1, HOLE=-2, NAN=-3, POSITIVE_INFINITY=-4, NEGATIVE_INFINITY=-5, NEGATIVE_ZERO=-6, SPARSE=-7), and the top-level short-circuit in stringify.js returns a stringified integer (\${index}`) rather than a raw JS literal. The devalue README's evalguidance applies touneval(), not stringify()—devalue.parse()itself is internallyunflatten(JSON.parse(serialized)), so JSON.parseis the canonical way to consumestringify()` output.
Empirically verified JSON.parse and (0, eval)(\(${s})`)produce identical results forundefined, NaN, ±Infinity, -0`, and compound structures containing them.
Non-blocking follow-up: once this lands, the revive() wrapper itself becomes trivial and could be inlined — the 4 call sites could just call JSON.parse(str) directly and the helper deleted. Happy to open a follow-up PR for that.
* Replace eval with JSON.parse in serialization revive helper devalue.stringify() always produces valid JSON — special values (undefined, NaN, Infinity, -0) are encoded as negative integer sentinels. JSON.parse yields the same flattened array form that unflatten() expects, without the eval anti-pattern (VULN-918). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Drop redundant workflow package from changeset Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Decode typed array stream chunks * Render decoded stream bytes with raw view * Render decoded bytes in data inspector * Use generic byte inspector for streams * review feedback: narrow stream-display exports, fix tab a11y, add collapseRefs tests - Remove unused formatStreamChunkForDisplay/sanitizeStreamChunkForDisplay exports; keep only the formatArrayBufferViewForDisplay path actually used by DataInspector. - Replace broken role=tablist/role=tab on the Decoded/Bytes switcher with aria-pressed toggle-button semantics. - Export collapseRefs/isBytesDisplay and add regression tests covering typed-array detection (top-level, nested in object/array/Map/Set, DataView exclusion). * Replace eval with JSON.parse in serialization revive helper (#1848) * Replace eval with JSON.parse in serialization revive helper devalue.stringify() always produces valid JSON — special values (undefined, NaN, Infinity, -0) are encoded as negative integer sentinels. JSON.parse yields the same flattened array form that unflatten() expects, without the eval anti-pattern (VULN-918). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Drop redundant workflow package from changeset Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> * Add e2e test for UTF-8 parseable stream chunks Emits Uint8Array chunks containing multi-byte UTF-8 (Latin Extended, CJK, emoji, RTL Arabic) plus a UTF-8 encoded JSON document, and asserts each chunk round-trips through TextDecoder({ fatal: true }). Exercises the same decode path the web inspector relies on for typed-array stream values. Made-with: Cursor --------- Co-authored-by: Pranay Prakash <pranay.gp@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Description
The
revive()helper inpackages/core/src/serialization.tsused(0, eval)to deserialize the output ofdevalue.stringify()into its flattened-array form (used by v1Compat paths and later consumed bydevalue.unflatten). While the input was controlled (always the result ofdevalue.stringify()called moments earlier),evalis still an unnecessary anti-pattern flagged by the security review.devalue.stringify()is documented and implemented to always emit valid JSON — special values (undefined,NaN,Infinity,-Infinity,-0) are encoded as negative integer sentinels (-1,-3,-4,-5,-6), and the rest of the structure is ordinary JSON.devalue.parse()itself internally doesunflatten(JSON.parse(serialized)). SoJSON.parseis a safe drop-in replacement.Note: devalue 5.6.3 does not export a public
flatten()function (onlyunflatten), so the stringify-then-parse approach is retained — just withouteval.How did you test your changes?
pnpm vitest run src/serialization.test.tsinpackages/core. 116 tests pass; 7 DOMException failures are pre-existing onmain(verified viagit stash) and unrelated to this change.revive().PR Checklist - Required to merge
pnpm changesetwas run to create a changelog for this PRgit commit --signoffon your commits)@vercel/workflowin a comment once the PR is ready, and the above checklist is complete