Drop libxmljs, fix XML injection bugs, modernize for Node 20+ - #6
Merged
Conversation
libxmljs 0.15.x has four open advisories, one critical, and npm reports "No fix available" for every published version (GHSA-773h-w45w-f2f9, GHSA-mg49-jqgw-gcj6, GHSA-6433-x5p4-8jc7, GHSA-jv72-59wq-8rxm). The package was last published in Oct 2023 and needs a node-gyp/libxml2 native build against a NAN release pinned to the Node 4 era, so bumping the version -- what every previous update to this repo did -- cannot fix it. Build the feed XML directly instead; node-rss now has zero dependencies and `npm audit` is clean. Taking over serialization surfaced three injection bugs in our own code: - CDATA breakout: title and description are CDATA-wrapped, and a `]]>` in either terminated the section early, injecting arbitrary XML into the feed. The sequence is now split across two sections. - Unvalidated element names: keys from `options`/`fields` became tag names verbatim, so a key like `x><script` injected markup. Element names cannot be escaped, so they are validated as XML names. - Prototype pollution: `for (var opt in options)` copied `__proto__` and `constructor`, and let options silently clobber `addNewItem`, `items` and `defaults`. Both are now rejected, and inherited properties are skipped. Characters XML 1.0 cannot represent (control characters, unpaired surrogates) are stripped rather than producing a malformed document. Output is unchanged in shape from the libxmljs version -- same element order, namespaces and 2-space indentation -- with one deliberate exception noted in the README: Date values now serialize as RFC-1123 instead of JavaScript's default date string, which is not valid in RSS and never was. Also modernize the package: Node 20+ floor, an exports map so ESM named imports work, a `files` allowlist, a committed lockfile (`npm audit` previously failed with ENOLOCK), strict mode and const/let, and removal of a stray UTF-8 BOM. Adds a node:test suite covering the injection cases above -- closing the long-standing "add tests" TODO -- and CI across Node 20, 22 and 24. Relicense under MIT with the license text actually included; previous releases declared a bare "BSD" that is not a valid SPDX identifier and shipped no LICENSE file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
libxmljs@0.15.x— the only dependency — has four open advisories, one critical, and npm reportsNo fix availablefor every published version:The package was last published in Oct 2023 and needs a
node-gyp/libxml2 native build against a NAN release pinned to the Node 4 era. Bumping the version — which is what every previous update to this repo did — cannot fix this.So this PR builds the feed XML directly. node-rss now has zero dependencies,
npm auditreportsfound 0 vulnerabilities, and there is no native module to compile.Injection bugs fixed in our own code
Taking over serialization surfaced three real bugs that were previously masked by libxmljs:
titleanddescriptionare CDATA-wrapped, and a]]>in either terminated the section early, injecting arbitrary XML into the feed. The sequence is now split across two sections.options/fieldsbecame tag names verbatim, so a key likex><scriptinjected markup. An element name cannot be escaped, so names are validated as XML names and rejected otherwise.for (var opt in options) feed[opt] = ...copied__proto__/constructor, and let options silently clobberaddNewItem/items/defaults. Both are rejected now, and inherited properties are skipped.Characters XML 1.0 cannot represent (control characters, unpaired surrogates) are stripped rather than producing a malformed document.
Verification
Beyond the unit tests, I generated a feed containing 11 adversarial payloads —
]]><script>,</item></channel></rss><evil/>, comment and processing-instruction breakouts, lone surrogates, nested CDATA — and parsed the result with a real XML parser:Compatibility
Output is unchanged in shape from the libxmljs version — same element order, namespaces, and 2-space indentation — with one deliberate exception:
Datevalues now serialize as RFC-1123 (Wed, 09 Sep 2026 15:04:56 GMT) instead of JavaScript's default date string (Wed Sep 09 2026 10:04:56 GMT-0500 (Central Daylight Time)), which is not a valid RSS date and never was. This matches whatlastBuildDatealready did. It's the reason for the major version bump.Requires Node 20+. The public API (
createNewFeed,getFeedXML,addNewItem,defaults.cdata) is otherwise unchanged, andexamples/simple.jsruns untouched.Also in here
exportsmap so ESM named imports work (import { getFeedXML } from 'node-rss'), with deep./lib/*imports preservedpackage-lock.json—npm auditpreviously failed outright withENOLOCKfilesallowlist trims the published tarball to 4 filesconst/let, removed a stray UTF-8 BOM, fixed an "efficently" typonode:testrunner covering every injection case above. No devDependencies, sonpm ciinstalls nothing. Closes the long-standing "add tests" TODO.npm auditjob to catch regressions"BSD", which is not a valid SPDX identifier, and shipped no LICENSE fileReview note
This repo has merged PRs from outside contributors (@dhendo, @jmathews, @upraised, @cnak145). Their contributions were all
libxmljsversion bumps inpackage.json, which no longer exist now that the dependency is gone — so there is likely nothing of theirs left in the tree to relicense. Worth a glance before merging if you want to be thorough about the license change.🤖 Generated with Claude Code