chore(deps): vendor the unmaintained security escaper into core#7993
Conversation
The `security` npm package (escapeHTML / escapeHTMLAttribute and the JS/CSS
encoders) has had no release since 2012, yet it sits directly in Etherpad's
client-side XSS-defense path (pad_utils, domline) and the server-side HTML
export. Rather than keep a 14-year-old, single-maintainer dependency guarding
output encoding, vendor its implementation into core.
- static/js/security.ts now contains the escaping logic directly (reproduced
verbatim from security@1.0.0, MIT, Chad Weider — byte-identical output) and
no longer does `require('security')`. The full public API is preserved, so
plugins that `require('ep_etherpad-lite/static/js/security')` keep working
unchanged.
- pad_utils.ts requires the local './security' module instead of the bare
'security' specifier (domline.ts and ExportHtml.ts already did).
- Drop `security` from src/package.json dependencies and from Minify's
LIBRARY_WHITELIST (no bare specifier is served to the browser anymore).
Added tests/backend/specs/security.ts locking the byte-for-byte escaping
output so the vendored copy can never silently drift.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
PR Summary by QodoVendor Description
Diagram
High-Level Assessment
Files changed (6)
|
Code Review by Qodo
1. No default export
|
CI "Run the new vitest tests" failed with `Cannot find module './security'`
from pad_utils.ts. vitest/vite's CJS require() shim doesn't add a `.ts`
extension when resolving a relative specifier, so `require('./security')`
couldn't locate security.ts. (The old bare `require('security')` resolved to
a real .js in node_modules, which is why this only surfaced after vendoring.)
- security.ts now uses ESM `export const` for the seven helpers instead of a
`module.exports = {...}` block.
- pad_utils.ts imports it as `import * as Security from './security'`, which
goes through vite's resolver (knows .ts) and is also properly typed.
CJS consumers (domline.ts, ExportHtml.ts, the backend spec) keep working via
tsx/esbuild ESM->CJS interop. Verified: tsc clean, full vitest suite 721
passing, and the mocha security/export/import specs 27 passing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
|
Code review by qodo was updated up to the latest commit dd99bd7 |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CodeQL flagged a high-severity exponential-backtracking alert on the JSON-string-literal regex vendored from the `security` package: `/"(?:\\.|[^"])*"/`. The `[^"]` class also matches a backslash, so it overlaps with the `\\.` alternative and backtracks exponentially on adversarial input like `"\!\!\!...` (no closing quote). The original lived inside node_modules so it was never scanned; vendoring it surfaced the alert. Fix to the canonical linear form `/"(?:[^"\\]|\\.)*"/`, where the backslash is excluded from the character class so the two alternatives are mutually exclusive. It matches exactly the same well-formed JSON string literals (and encodeJavaScriptData only ever runs it over JSON.stringify output), so behaviour is unchanged for valid input. Added tests: encodeJavaScriptData output + a ReDoS guard that runs the regex over 50k adversarial chars and asserts it returns in well under a second. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
The
securitynpm package —escapeHTML,escapeHTMLAttribute, and the JS/CSS encoders — has had no release since 2012 (v1.0.0), yet it sits directly in Etherpad's XSS-defense path:pad_utils.ts(linkify /escapeHtml),domline.tsExportHtml.ts(HTML export)Keeping a 14-year-old, single-maintainer dependency guarding output encoding is a risk we don't need to carry. This PR vendors its implementation into core instead of swapping in a different escaper, so the escaping output is byte-for-byte identical and nothing downstream changes.
Second slice of the dependency-staleness audit (follows #7992).
Changes
static/js/security.tsnow contains the escaping logic directly — reproduced verbatim fromsecurity@1.0.0(MIT, Chad Weider, an original Etherpad author), with attribution. It no longer doesrequire('security'). The full public API is preserved (all 7 functions), so plugins thatrequire('ep_etherpad-lite/static/js/security')keep working unchanged.pad_utils.tsrequires the local./securitymodule instead of the bare'security'specifier. (domline.tsandExportHtml.tsalready used the local module path.)securityfromsrc/package.jsondependencies and fromMinify.ts'sLIBRARY_WHITELIST— no baresecurityspecifier is served to the browser anymore.After this change there are zero bare
require('security')call sites in the tree; every consumer routes through the vendored first-party module.Why vendor instead of replace
The only functions actually used in core are
escapeHTML(×5) andescapeHTMLAttribute(×10). Swapping to a different escaper (e.g.he) would change encoding output (named vs numeric entities, casing) and risk breaking plugins and snapshot/round-trip expectations. Vendoring keeps behaviour provably identical while removing the dead dependency.Tests
tests/backend/specs/security.tslocking the byte-for-byte escaping output (named entities for& < > " ' /, lowercase hex&#xNN;for other ASCII, JS/CSS\uXXXX/\XXXXXXencoders, falsy-passthrough, full API surface).ExportHtml→ Security path with a full server boot): pass.tsc --noEmit: clean.🤖 Generated with Claude Code