René's URL Explorer Experiment


Title: UTF-8 character corruption in fast-utf8-stream.js via releaseWritingBuf() leads to silent data loss on partial writes · Issue #61744 · nodejs/node · GitHub

Open Graph Title: UTF-8 character corruption in fast-utf8-stream.js via releaseWritingBuf() leads to silent data loss on partial writes · Issue #61744 · nodejs/node

X Title: UTF-8 character corruption in fast-utf8-stream.js via releaseWritingBuf() leads to silent data loss on partial writes · Issue #61744 · nodejs/node

Description: Version No response Platform **Summary:** releaseWritingBuf() in lib/internal/streams/fast-utf8-stream.js incorrectly calculates string slice positions when fs.write returns a byte count that splits a multi-byte UTF-8 character, causing ...

Open Graph Description: Version No response Platform **Summary:** releaseWritingBuf() in lib/internal/streams/fast-utf8-stream.js incorrectly calculates string slice positions when fs.write returns a byte count that split...

X Description: Version No response Platform **Summary:** releaseWritingBuf() in lib/internal/streams/fast-utf8-stream.js incorrectly calculates string slice positions when fs.write returns a byte count that split...

Opengraph URL: https://github.com/nodejs/node/issues/61744

X: @github

direct link

Domain: github.com


Hey, it has json ld scripts:
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"UTF-8 character corruption in fast-utf8-stream.js via releaseWritingBuf() leads to silent data loss on partial writes","articleBody":"### Version\n\n_No response_\n\n### Platform\n\n```text\n**Summary:** releaseWritingBuf() in lib/internal/streams/fast-utf8-stream.js incorrectly calculates string slice positions when fs.write returns a byte count that splits a multi-byte UTF-8 character, causing silent data corruption (lost characters, lone surrogates in output).\n\n**Description:** \n\nThe releaseWritingBuf function (line 896) converts bytes-written to character count using:\n\n\nn = Buffer.from(writingBuf).subarray(0, n).toString().length;\n\n\nWhen n bytes cuts through a multi-byte character, the incomplete UTF-8 sequence becomes U+FFFD (replacement character) via .toString(). This replacement character has a different .length than the original character in JS UTF-16, causing .slice(n) to cut at the wrong position:\n\n- 3-byte characters (CJK, most non-Latin): character silently dropped from output\n- 4-byte characters (emoji, supplementary CJK): lone low surrogate left in remaining buffer, producing invalid UTF-8 on next write\n\nThe file was recently added (January 2026), ported from SonicBoom. It is used as the fast path for streaming UTF-8 output.\n\n## Steps To Reproduce:\n\n1. Save this as poc.js and run with node poc.js:\n\n\n// Reproduces the releaseWritingBuf logic from lib/internal/streams/fast-utf8-stream.js lines 896-906\nfunction releaseWritingBuf(writingBuf, len, n) {\n  if (typeof writingBuf === 'string' \u0026\u0026 Buffer.byteLength(writingBuf) !== n) {\n    n = Buffer.from(writingBuf).subarray(0, n).toString().length;\n  }\n  len = Math.max(len - n, 0);\n  writingBuf = writingBuf.slice(n);\n  return { writingBuf, len };\n}\n\n// Case 1: 4-byte emoji split at byte 7 — lone surrogate\nconst r1 = releaseWritingBuf(\"hello🌍world\", 14, 7);\nconsole.log(\"Case 1 - Emoji split:\");\nconsole.log(\"  Result:\", JSON.stringify(r1.writingBuf));\nconsole.log(\"  Expected:\", JSON.stringify(\"🌍world\"));\nconsole.log(\"  First char code: 0x\" + r1.writingBuf.charCodeAt(0).toString(16));\nconsole.log(\"  Is lone surrogate:\", r1.writingBuf.charCodeAt(0) \u003e= 0xDC00 \u0026\u0026\nr1.writingBuf.charCodeAt(0) \u003c= 0xDFFF);\n\n// Case 2: 3-byte CJK char split at byte 4 — character lost\nconst r2 = releaseWritingBuf(\"abc中def\", 9, 4);\nconsole.log(\"\\nCase 2 - CJK split:\");\nconsole.log(\"  Result:\", JSON.stringify(r2.writingBuf));\nconsole.log(\"  Expected:\", JSON.stringify(\"中def\"));\nconsole.log(\"  Character 中 lost:\", !r2.writingBuf.includes(\"中\"));\n\n\n2. Output shows:\n\nCase 1 - Emoji split:\n  Result: \"\\udf0dworld\"        ← CORRUPTED (lone surrogate)\n  Expected: \"🌍world\"\n  First char code: 0xdf0d\n  Is lone surrogate: true\n\nCase 2 - CJK split:\n  Result: \"def\"                ← CHARACTER LOST\n  Expected: \"中def\"\n  Character 中 lost: true\n\n\n3. The vulnerable code is at:\nhttps://github.com/nodejs/node/blob/main/lib/internal/streams/fast-utf8-stream.js#L896-L906\n\nPartial fs.write returns are possible when writing to pipes near capacity, under disk I/O pressure, or to Docker log pipes (the exact use case mentioned in the file's comments on line 69-70).\n\nAdditional finding: Line 240 has a typo from the SonicBoom port — this._asyncDrainScheduled should be this.#asyncDrainScheduled. All other 5 references use the private field correctly. The newListener handler is effectively dead code.\n\n## Impact:\n\nSilent data corruption in output files. Applications using Utf8Stream for logging with international characters (CJK, emoji, Cyrillic) can produce corrupted output when partial writes occur. 3-byte characters are silently lost (no error emitted). 4-byte characters produce invalid UTF-8 (lone surrogates). This is especially relevant for the Docker container logging use case the file was designed for.\n\n## Supporting Material/References:\n\n- Vulnerable function: releaseWritingBuf() at https://github.com/nodejs/node/blob/main/lib/internal/streams/fast-utf8-stream.js#L896-L906\n- Typo (secondary): line 240, _asyncDrainScheduled vs #asyncDrainScheduled\n- File derived from SonicBoom (https://github.com/pinojs/sonic-boom) — the original has a similar issue but uses _ prefix consistently\n- The PoC script above is standalone and runs on any Node.js version\n```\n\n### Subsystem\n\n_No response_\n\n### What steps will reproduce the bug?\n\n1. Save the following script as `poc.js` and run it with `node poc.js`.\n\n2. The script reproduces the exact logic from\n   `lib/internal/streams/fast-utf8-stream.js` (lines 896–906),\n   specifically the `releaseWritingBuf()` function.\n\n3. The script simulates partial `fs.write()` behavior where the number\n   of bytes written splits a multi-byte UTF-8 character.\n\n4. Observe the output:\n   - When a 4-byte UTF-8 character (emoji) is split, a lone surrogate\n     remains in the output.\n   - When a 3-byte UTF-8 character (CJK) is split, the character is\n     silently dropped.\n\n5. This demonstrates incorrect string slicing caused by converting\n   byte counts to character counts via `.toString().length`.\n\n### How often does it reproduce? Is there a required condition?\n\nIt reproduces deterministically whenever `fs.write()` (or an equivalent\ninternal write) returns a byte count that splits a multi-byte UTF-8\ncharacter.\n\nThe issue is not timing-dependent or race-based. The required condition\nis a partial write that ends in the middle of a UTF-8 sequence.\n\nThis can occur when writing to pipes, sockets, or log streams under\nbackpressure (e.g. near-capacity pipes, Docker container logs, or heavy\nI/O), which is a documented and expected behavior of `fs.write()`.\n\n### What is the expected behavior? Why is that the expected behavior?\n\nThe output must always preserve valid UTF-8 and must not silently\ncorrupt data.\n\nWhen a partial write ends in the middle of a multi-byte UTF-8 character,\nthe remaining bytes for that character should be preserved and written\nin a subsequent write, rather than being dropped or converted into\nreplacement characters.\n\nThis is the expected behavior because:\n- `fs.write()` is documented to return partial byte counts.\n- UTF-8 stream handling must be byte-safe across writes.\n- Producing lone surrogates or dropping characters violates UTF-8\n  correctness and results in silent data corruption.\n\nThe current behavior breaks UTF-8 invariants and can corrupt log output\nin real-world streaming scenarios, such as container logging and pipe-\nbased streams, which this module explicitly targets.\n\n### What do you see instead?\n\nInstead of preserving valid UTF-8 output, the stream produces corrupted\nresults when a partial write splits a multi-byte character.\n\nSpecifically:\n- For 3-byte UTF-8 characters (e.g. CJK), the character is silently\n  dropped from the output with no error.\n- For 4-byte UTF-8 characters (e.g. emoji), the remaining buffer starts\n  with a lone UTF-16 surrogate, producing invalid UTF-8 on subsequent\n  writes.\n\nNo error or warning is emitted, resulting in silent data corruption in\nthe output stream.\n\n### Additional information\n\n_No response_","author":{"url":"https://github.com/pitiflautico","@type":"Person","name":"pitiflautico"},"datePublished":"2026-02-08T18:50:26.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/61744/node/issues/61744"}

route-pattern/_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format)
route-controllervoltron_issues_fragments
route-actionissue_layout
fetch-noncev2:83040702-b431-82c1-ec9e-1e23f9c9dc89
current-catalog-service-hash81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114
request-idEE02:20AD09:27426AB:37872A5:6A4CEBAB
html-safe-nonce30b326e76ea297247f7b9874307e17ba21b247de29c3e4f7012c24fff3a7ea84
visitor-payloadeyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJFRTAyOjIwQUQwOToyNzQyNkFCOjM3ODcyQTU6NkE0Q0VCQUIiLCJ2aXNpdG9yX2lkIjoiMTM0ODkxNjYyMjM0MTc2MTk2MyIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9
visitor-hmac61486b3b3e4fa505711d835bbcc53c003f858b91448549593f1dc125e1cd942a
hovercard-subject-tagissue:3913313662
github-keyboard-shortcutsrepository,issues,copilot
google-site-verificationApib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I
octolytics-urlhttps://collector.github.com/github/collect
analytics-location///voltron/issues_fragments/issue_layout
fb:app_id1401488693436528
apple-itunes-appapp-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/61744/issue_layout
twitter:imagehttps://opengraph.githubassets.com/15dc821bbff6f069e03f8081dd68cb681a83e5fea2dd6c90b1226dc30324a833/nodejs/node/issues/61744
twitter:cardsummary_large_image
og:imagehttps://opengraph.githubassets.com/15dc821bbff6f069e03f8081dd68cb681a83e5fea2dd6c90b1226dc30324a833/nodejs/node/issues/61744
og:image:altVersion No response Platform **Summary:** releaseWritingBuf() in lib/internal/streams/fast-utf8-stream.js incorrectly calculates string slice positions when fs.write returns a byte count that split...
og:image:width1200
og:image:height600
og:site_nameGitHub
og:typeobject
og:author:usernamepitiflautico
hostnamegithub.com
expected-hostnamegithub.com
None299b43bca6e02ad35197ffeba30d2466846d5fb02ab96fbced5b5e6cec589fb8
turbo-cache-controlno-preview
go-importgithub.com/nodejs/node git https://github.com/nodejs/node.git
octolytics-dimension-user_id9950313
octolytics-dimension-user_loginnodejs
octolytics-dimension-repository_id27193779
octolytics-dimension-repository_nwonodejs/node
octolytics-dimension-repository_publictrue
octolytics-dimension-repository_is_forkfalse
octolytics-dimension-repository_network_root_id27193779
octolytics-dimension-repository_network_root_nwonodejs/node
turbo-body-classeslogged-out env-production page-responsive
disable-turbofalse
browser-stats-urlhttps://api.github.com/_private/browser/stats
browser-errors-urlhttps://api.github.com/_private/browser/errors
releasec5a57f04eeb310f57c73fd6d751d957e2ca27ed2
ui-targetfull
theme-color#1e2327
color-schemelight dark

Links:

Skip to contenthttps://github.com/nodejs/node/issues/61744#start-of-content
https://github.com/
Sign in https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fnodejs%2Fnode%2Fissues%2F61744
GitHub CopilotWrite better code with AIhttps://github.com/features/copilot
GitHub Copilot appDirect agents from issue to mergehttps://github.com/features/ai/github-app
MCP RegistryNewIntegrate external toolshttps://github.com/mcp
ActionsAutomate any workflowhttps://github.com/features/actions
CodespacesInstant dev environmentshttps://github.com/features/codespaces
IssuesPlan and track workhttps://github.com/features/issues
Code ReviewManage code changeshttps://github.com/features/code-review
GitHub Advanced SecurityFind and fix vulnerabilitieshttps://github.com/security/advanced-security
Code securitySecure your code as you buildhttps://github.com/security/advanced-security/code-security
Secret protectionStop leaks before they starthttps://github.com/security/advanced-security/secret-protection
Why GitHubhttps://github.com/why-github
Documentationhttps://docs.github.com
Bloghttps://github.blog
Changeloghttps://github.blog/changelog
Marketplacehttps://github.com/marketplace
View all featureshttps://github.com/features
Enterpriseshttps://github.com/enterprise
Small and medium teamshttps://github.com/team
Startupshttps://github.com/enterprise/startups
Nonprofitshttps://github.com/solutions/industry/nonprofits
App Modernizationhttps://github.com/solutions/use-case/app-modernization
DevSecOpshttps://github.com/solutions/use-case/devsecops
DevOpshttps://github.com/solutions/use-case/devops
CI/CDhttps://github.com/solutions/use-case/ci-cd
View all use caseshttps://github.com/solutions/use-case
Healthcarehttps://github.com/solutions/industry/healthcare
Financial serviceshttps://github.com/solutions/industry/financial-services
Manufacturinghttps://github.com/solutions/industry/manufacturing
Governmenthttps://github.com/solutions/industry/government
View all industrieshttps://github.com/solutions/industry
View all solutionshttps://github.com/solutions
AIhttps://github.com/resources/articles?topic=ai
Software Developmenthttps://github.com/resources/articles?topic=software-development
DevOpshttps://github.com/resources/articles?topic=devops
Securityhttps://github.com/resources/articles?topic=security
View all topicshttps://github.com/resources/articles
Customer storieshttps://github.com/customer-stories
Events & webinarshttps://github.com/resources/events
Ebooks & reportshttps://github.com/resources/whitepapers
Business insightshttps://github.com/solutions/executive-insights
GitHub Skillshttps://skills.github.com
Documentationhttps://docs.github.com
Customer supporthttps://support.github.com
Community forumhttps://github.com/orgs/community/discussions
Trust centerhttps://github.com/trust-center
Partnershttps://github.com/partners
View all resourceshttps://github.com/resources
GitHub SponsorsFund open source developershttps://github.com/sponsors
Security Labhttps://securitylab.github.com
Maintainer Communityhttps://maintainers.github.com
Acceleratorhttps://github.com/accelerator
GitHub Starshttps://stars.github.com
Archive Programhttps://archiveprogram.github.com
Topicshttps://github.com/topics
Trendinghttps://github.com/trending
Collectionshttps://github.com/collections
Enterprise platformAI-powered developer platformhttps://github.com/enterprise
GitHub Advanced SecurityEnterprise-grade security featureshttps://github.com/security/advanced-security
Copilot for BusinessEnterprise-grade AI featureshttps://github.com/features/copilot/copilot-business
Premium SupportEnterprise-grade 24/7 supporthttps://github.com/premium-support
Pricinghttps://github.com/pricing
Search syntax tipshttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
documentationhttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
Sign in https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fnodejs%2Fnode%2Fissues%2F61744
Sign up https://github.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fvoltron%2Fissues_fragments%2Fissue_layout&source=header-repo&source_repo=nodejs%2Fnode
Reloadhttps://github.com/nodejs/node/issues/61744
Reloadhttps://github.com/nodejs/node/issues/61744
Reloadhttps://github.com/nodejs/node/issues/61744
Please reload this pagehttps://github.com/nodejs/node/issues/61744
nodejs https://github.com/nodejs
nodehttps://github.com/nodejs/node
Please reload this pagehttps://github.com/nodejs/node/issues/61744
Notifications https://github.com/login?return_to=%2Fnodejs%2Fnode
Fork 36k https://github.com/login?return_to=%2Fnodejs%2Fnode
Star 118k https://github.com/login?return_to=%2Fnodejs%2Fnode
Code https://github.com/nodejs/node
Issues 1.4k https://github.com/nodejs/node/issues
Pull requests 959 https://github.com/nodejs/node/pulls
Actions https://github.com/nodejs/node/actions
Projects https://github.com/nodejs/node/projects
Security and quality 0 https://github.com/nodejs/node/security
Insights https://github.com/nodejs/node/pulse
Code https://github.com/nodejs/node
Issues https://github.com/nodejs/node/issues
Pull requests https://github.com/nodejs/node/pulls
Actions https://github.com/nodejs/node/actions
Projects https://github.com/nodejs/node/projects
Security and quality https://github.com/nodejs/node/security
Insights https://github.com/nodejs/node/pulse
#61745https://github.com/nodejs/node/pull/61745
UTF-8 character corruption in fast-utf8-stream.js via releaseWritingBuf() leads to silent data loss on partial writeshttps://github.com/nodejs/node/issues/61744#top
#61745https://github.com/nodejs/node/pull/61745
confirmed-bugIssues with confirmed bugs.https://github.com/nodejs/node/issues?q=state%3Aopen%20label%3A%22confirmed-bug%22
fsIssues and PRs related to the fs subsystem / file system.https://github.com/nodejs/node/issues?q=state%3Aopen%20label%3A%22fs%22
https://github.com/pitiflautico
pitiflauticohttps://github.com/pitiflautico
on Feb 8, 2026https://github.com/nodejs/node/issues/61744#issue-3913313662
confirmed-bugIssues with confirmed bugs.https://github.com/nodejs/node/issues?q=state%3Aopen%20label%3A%22confirmed-bug%22
fsIssues and PRs related to the fs subsystem / file system.https://github.com/nodejs/node/issues?q=state%3Aopen%20label%3A%22fs%22
https://github.com
Termshttps://docs.github.com/site-policy/github-terms/github-terms-of-service
Privacyhttps://docs.github.com/site-policy/privacy-policies/github-privacy-statement
Securityhttps://github.com/security
Statushttps://www.githubstatus.com/
Communityhttps://github.community/
Docshttps://docs.github.com/
Contacthttps://support.github.com?tags=dotcom-footer

Viewport: width=device-width


URLs of crawlers that visited me.