Skip to content

Drop libxmljs, fix XML injection bugs, modernize for Node 20+ - #6

Merged
ebensing merged 1 commit into
masterfrom
modernize-and-fix-security
Sep 9, 2026
Merged

Drop libxmljs, fix XML injection bugs, modernize for Node 20+#6
ebensing merged 1 commit into
masterfrom
modernize-and-fix-security

Conversation

@ebensing

@ebensing ebensing commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Why

libxmljs@0.15.x — the only dependency — has four open advisories, one critical, and npm reports No fix available for every published version:

Advisory Issue
GHSA-773h-w45w-f2f9 Denial of service
GHSA-mg49-jqgw-gcj6 Type confusion parsing crafted XML
GHSA-6433-x5p4-8jc7 Type confusion parsing crafted XML
GHSA-jv72-59wq-8rxm Segfault → denial of service

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 audit reports found 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:

  • 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. An element name cannot be escaped, so names are validated as XML names and rejected otherwise.
  • Prototype pollution. for (var opt in options) feed[opt] = ... copied __proto__/constructor, and let options silently clobber addNewItem/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:

  • document is well-formed
  • zero unexpected elements (nothing injected)
  • every payload round-tripped as text, not markup
  • no illegal control characters in the output

Compatibility

Output is unchanged in shape from the libxmljs version — same element order, namespaces, and 2-space indentation — with one deliberate exception:

Date values 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 what lastBuildDate already did. It's the reason for the major version bump.

Requires Node 20+. The public API (createNewFeed, getFeedXML, addNewItem, defaults.cdata) is otherwise unchanged, and examples/simple.js runs untouched.

Also in here

  • exports map so ESM named imports work (import { getFeedXML } from 'node-rss'), with deep ./lib/* imports preserved
  • Committed package-lock.jsonnpm audit previously failed outright with ENOLOCK
  • files allowlist trims the published tarball to 4 files
  • Strict mode, const/let, removed a stray UTF-8 BOM, fixed an "efficently" typo
  • Tests: 19 cases on the built-in node:test runner covering every injection case above. No devDependencies, so npm ci installs nothing. Closes the long-standing "add tests" TODO.
  • CI: test matrix across Node 20/22/24, plus an npm audit job to catch regressions
  • Relicensed under MIT with the license text actually included; previous releases declared a bare "BSD", which is not a valid SPDX identifier, and shipped no LICENSE file

Review note

This repo has merged PRs from outside contributors (@dhendo, @jmathews, @upraised, @cnak145). Their contributions were all libxmljs version bumps in package.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

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>
@ebensing
ebensing merged commit 69a28cf into master Sep 9, 2026
4 checks passed
@ebensing
ebensing deleted the modernize-and-fix-security branch September 9, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant