chore(deps): upgrade dependencies, remove some unnecessary ones

This commit upgrades dependencies that are more or less trivial
to update, e.g. because they didn't have major version bumps or
simply didn't break anything. There are some dependencies which
have not been upgraded since this would have required larger
refactorings. This includes especially the markdown-it ecosystem
and the webpack ecosystem.
The largest refactorings in this commit come from the bump of
socket.io v2 to v4 which changed the handling of the connected
socket list for instance.

This commit further removes some outdated and/or unnecessary
dependencies. This includes the String.js library which is
unmaintained for 9 years and has some CVEs. We mainly used
this library for their escapeHTML and unescapeHTML methods.
This can be done using native DOM APIs nowadays, which is also
considered more safe since it is the same logic that the
browser itself uses.
Since we target Node 18 and above, we can also rely on the
built-in fetch function instead of the node-fetch package.
The current version of Chance.js includes a method for
generating a random color now too, so we don't need the
package randomcolor anymore.

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson
2025-11-23 23:24:51 +01:00
committed by Philip Molares
parent 637c451486
commit 9a45d1e2a9
14 changed files with 1198 additions and 983 deletions

20
app.js
View File

@@ -12,6 +12,7 @@ const session = require('express-session')
const SequelizeStore = require('connect-session-sequelize')(session.Store)
const fs = require('fs')
const path = require('path')
const { Server } = require('socket.io')
const morgan = require('morgan')
const passportSocketIo = require('passport.socketio')
@@ -81,7 +82,16 @@ if (config.enableStatsApi) {
}
// socket io
const io = require('socket.io')(server, { cookie: false })
const io = new Server(server, {
pingInterval: config.heartbeatInterval,
pingTimeout: config.heartbeatTimeout,
cookie: false,
cors: {
origin: config.serverURL,
methods: ['GET', 'POST'],
credentials: true
}
})
// others
const realtime = require('./lib/realtime.js')
@@ -272,9 +282,6 @@ io.use(passportSocketIo.authorize({
success: realtime.onAuthorizeSuccess,
fail: realtime.onAuthorizeFail
}))
// socket.io heartbeat
io.set('heartbeat interval', config.heartbeatInterval)
io.set('heartbeat timeout', config.heartbeatTimeout)
// socket.io connection
io.sockets.on('connection', realtime.connection)
@@ -349,8 +356,9 @@ function handleTermSignals () {
alreadyHandlingTermSignals = true
realtime.maintenance = true
// disconnect all socket.io clients
Object.keys(io.sockets.sockets).forEach(function (key) {
const socket = io.sockets.sockets[key]
io.sockets.sockets.keys().forEach(function (key) {
const socket = io.sockets.sockets.get(key)
if (!socket) return
// notify client server going into maintenance status
socket.emit('maintenance')
setTimeout(function () {

View File

@@ -1,14 +1,16 @@
'use strict'
// external modules
const crypto = require('crypto')
const randomcolor = require('randomcolor')
const Chance = require('chance')
const config = require('./config')
// core
exports.generateAvatar = function (name) {
const color = randomcolor({
seed: name,
luminosity: 'dark'
// use darker colors for better contrast
const color = new Chance(name).color({
max_red: 150,
max_green: 150,
max_blue: 150
})
const letter = name.substring(0, 1).toUpperCase()

View File

@@ -13,7 +13,6 @@ const async = require('async')
const moment = require('moment')
const DiffMatchPatch = require('diff-match-patch')
const dmp = new DiffMatchPatch()
const S = require('string')
// core
const config = require('../config')
@@ -348,7 +347,9 @@ module.exports = function (sequelize, DataTypes) {
title = meta.title
} else {
const h1s = $('h1')
if (h1s.length > 0 && h1s.first().text().split('\n').length === 1) { title = S(h1s.first().text()).stripTags().s }
if (h1s.length > 0 && h1s.first().text().split('\n').length === 1) {
title = h1s.first().text().trim()
}
}
if (!title) title = 'Untitled'
return title
@@ -378,7 +379,7 @@ module.exports = function (sequelize, DataTypes) {
if (/^tags/gmi.test($(value).text())) {
const codes = $(value).find('code')
for (let i = 0; i < codes.length; i++) {
const text = S($(codes[i]).text().trim()).stripTags().s
const text = $(codes[i]).text().trim()
if (text) rawtags.push(text)
}
}

View File

@@ -4,7 +4,6 @@
const cookie = require('cookie')
const cookieParser = require('cookie-parser')
const async = require('async')
const randomcolor = require('randomcolor')
const Chance = require('chance')
const chance = new Chance()
const moment = require('moment')
@@ -178,9 +177,9 @@ function finishUpdateNote (note, _note, callback) {
// clean when user not in any rooms or user not in connected list
setInterval(function () {
async.each(Object.keys(users), function (key, callback) {
let socket = realtime.io.sockets.connected[key]
let socket = realtime.io.sockets.sockets.get(key)
if ((!socket && users[key]) ||
(socket && (!socket.rooms || socket.rooms.length <= 0))) {
(socket && (!socket.rooms || socket.rooms.size <= 0))) {
logger.debug(`cleaner found redundant user: ${key}`)
if (!socket) {
socket = {
@@ -711,7 +710,7 @@ function connection (socket) {
// initialize user data
// random color
let color = randomcolor()
let color = chance.color()
// make sure color not duplicated or reach max random count
if (notes[noteId]) {
let randomcount = 0
@@ -724,7 +723,7 @@ function connection (socket) {
}
})
if (found) {
color = randomcolor()
color = chance.color()
randomcount++
}
} while (found && randomcount < maxrandomcount)

View File

@@ -3,7 +3,6 @@
// external modules
const fs = require('fs')
const path = require('path')
const fetch = require('node-fetch')
// core
const config = require('./config')
const logger = require('./logger')

View File

@@ -2,7 +2,6 @@
const config = require('../../config')
const logger = require('../../logger')
const fs = require('fs')
const fetch = require('node-fetch')
exports.uploadImage = function (imagePath, callback) {
if (!callback || typeof callback !== 'function') {

View File

@@ -1,6 +1,6 @@
'use strict'
const { rateLimit } = require('express-rate-limit')
const { rateLimit, ipKeyGenerator } = require('express-rate-limit')
const errors = require('../../errors')
const config = require('../../config')
@@ -8,7 +8,7 @@ const determineKey = (req) => {
if (req.user) {
return req.user.id
}
return req.header('cf-connecting-ip') || req.ip
return ipKeyGenerator(req.header('cf-connecting-ip') || req.ip)
}
// limits requests to user endpoints (login, signup) to 10 requests per 5 minutes

View File

@@ -18,40 +18,40 @@
},
"dependencies": {
"@hedgedoc/meta-marked": "14.1.0",
"@node-saml/passport-saml": "5.0.1",
"@node-saml/passport-saml": "5.1.0",
"@passport-next/passport-openid": "1.0.0",
"Idle.Js": "git+https://github.com/shawnmclean/Idle.js#commit=2b57cc6e49d177b7ddce0cca00ef5cbe07453541",
"archiver": "6.0.2",
"archiver": "7.0.1",
"async": "3.2.6",
"aws-sdk": "2.1692.0",
"azure-storage": "2.10.7",
"base64url": "3.0.1",
"body-parser": "2.2.0",
"chance": "1.1.12",
"cheerio": "0.22.0",
"chance": "1.1.13",
"cheerio": "1.1.2",
"clean-webpack-plugin": "4.0.0",
"compression": "1.8.0",
"compression": "1.8.1",
"connect-flash": "0.1.1",
"connect-session-sequelize": "7.1.7",
"connect-session-sequelize": "8.0.2",
"cookie": "1.0.2",
"cookie-parser": "1.4.7",
"deep-freeze": "0.0.1",
"diff-match-patch": "git+https://github.com/hackmdio/diff-match-patch.git#commit=59a9395ad9fe143e601e7ae5765ed943bdd2b11e",
"ejs": "3.1.10",
"express": "4.21.2",
"express-rate-limit": "7.5.0",
"express-session": "1.18.1",
"file-type": "20.4.1",
"formidable": "2.1.3",
"express-rate-limit": "8.2.1",
"express-session": "1.18.2",
"file-type": "21.1.1",
"formidable": "3.5.4",
"graceful-fs": "4.2.11",
"helmet": "8.1.0",
"i18n": "0.15.1",
"is-svg": "4.4.0",
"i18n": "0.15.3",
"is-svg": "6.1.0",
"jsdom-nogyp": "0.8.3",
"lodash": "4.17.21",
"lutim": "1.0.3",
"lz-string": "git+https://github.com/hackmdio/lz-string.git#commit=6edfccb79cd8c210f03fd3bf18e41ca144fbeefb",
"mariadb": "3.4.1",
"mariadb": "3.4.5",
"markdown-it": "13.0.2",
"markdown-it-abbr": "1.0.4",
"markdown-it-container": "3.0.0",
@@ -68,12 +68,11 @@
"mattermost": "3.4.0",
"method-override": "3.0.0",
"minimist": "1.2.8",
"minio": "7.1.3",
"minio": "8.0.6",
"moment": "2.30.1",
"morgan": "1.10.0",
"mysql2": "3.14.0",
"nanoid": "3.3.11",
"node-fetch": "2.7.0",
"mysql2": "3.15.3",
"nanoid": "5.1.6",
"passport": "patch:passport@npm%3A0.7.0#~/.yarn/patches/passport-npm-0.7.0-df02531736.patch",
"passport-dropbox-oauth2": "1.1.0",
"passport-facebook": "3.0.0",
@@ -86,25 +85,23 @@
"passport-twitter": "1.0.4",
"passport.socketio": "3.7.0",
"pdfobject": "2.3.1",
"pg": "8.14.1",
"pg": "8.16.3",
"pg-hstore": "2.3.4",
"prom-client": "15.1.3",
"prometheus-api-metrics": "4.0.0",
"randomcolor": "0.6.2",
"readline-sync": "1.4.10",
"rimraf": "5.0.10",
"rimraf": "6.1.2",
"sanitize-filename": "1.6.3",
"scrypt-kdf": "2.0.1",
"scrypt-kdf": "3.0.0",
"sequelize": "5.22.5",
"socket.io": "2.5.1",
"socket.io": "4.8.1",
"sqlite3": "5.1.7",
"store": "2.0.12",
"string": "3.3.3",
"toobusy-js": "0.5.1",
"umzug": "2.3.0",
"uuid": "11.1.0",
"validator": "13.15.0",
"winston": "3.17.0",
"uuid": "13.0.0",
"validator": "13.15.23",
"winston": "3.18.3",
"xss": "1.0.15"
},
"engines": {
@@ -138,9 +135,9 @@
},
"devDependencies": {
"@eslint/eslintrc": "3.3.1",
"@eslint/js": "9.24.0",
"@eslint/js": "9.39.1",
"@hedgedoc/codemirror-5": "5.65.12",
"abcjs": "6.4.4",
"abcjs": "6.5.2",
"babel-cli": "6.26.0",
"babel-core": "6.26.3",
"babel-loader": "7.1.5",
@@ -153,12 +150,11 @@
"copy-webpack-plugin": "6.4.1",
"css-loader": "5.2.7",
"emojify.js": "1.1.0",
"esbuild-loader": "4.3.0",
"escape-html": "1.0.3",
"eslint": "9.24.0",
"esbuild-loader": "4.4.0",
"eslint": "9.39.1",
"eslint-config-standard": "17.1.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-n": "17.17.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-n": "17.23.1",
"eslint-plugin-promise": "7.2.1",
"eslint-plugin-standard": "5.0.0",
"exports-loader": "1.1.1",
@@ -167,7 +163,7 @@
"file-saver": "2.0.5",
"flowchart.js": "1.18.0",
"fork-awesome": "1.2.0",
"globals": "16.0.0",
"globals": "16.5.0",
"highlight.js": "10.7.3",
"html-webpack-plugin": "4.5.2",
"imports-loader": "1.2.0",
@@ -180,13 +176,13 @@
"js-yaml": "3.14.1",
"jsonlint": "1.6.3",
"keymaster": "1.6.2",
"less": "4.3.0",
"less": "4.4.2",
"less-loader": "7.3.0",
"list.js": "2.3.1",
"mathjax": "2.7.9",
"mermaid": "9.1.7",
"mini-css-extract-plugin": "1.6.2",
"mocha": "11.1.0",
"mocha": "11.7.5",
"mock-require": "3.0.3",
"optimize-css-assets-webpack-plugin": "6.0.1",
"prismjs": "1.30.0",
@@ -195,10 +191,10 @@
"remark-preset-lint-markdown-style-guide": "5.1.3",
"reveal.js": "3.9.2",
"select2": "3.5.2-browserify",
"socket.io-client": "2.5.0",
"socket.io-client": "4.8.1",
"spin.js": "4.1.2",
"string-loader": "0.0.1",
"turndown": "7.2.0",
"turndown": "7.2.2",
"url-loader": "4.1.1",
"velocity-animate": "1.5.2",
"visibilityjs": "2.0.2",

View File

@@ -24,7 +24,7 @@ import {
import { saveAs } from 'file-saver'
import List from 'list.js'
import S from 'string'
import { unescapeHtml } from './utils'
require('./locale')
@@ -398,7 +398,7 @@ function buildTagsFilter (tags) {
for (let i = 0; i < tags.length; i++) {
tags[i] = {
id: i,
text: S(tags[i]).unescapeHTML().s
text: unescapeHtml(tags[i])
}
}
filtertags = tags

View File

@@ -3,11 +3,11 @@
import Prism from 'prismjs'
import PDFObject from 'pdfobject'
import S from 'string'
import { saveAs } from 'file-saver'
import escapeHTML from 'escape-html'
import filterXSS from 'xss'
import getUIElements from './lib/editor/ui-elements'
import { escapeHtml, unescapeHtml } from './utils'
import markdownit from 'markdown-it'
import markdownitContainer from 'markdown-it-container'
@@ -166,7 +166,11 @@ export function renderTags (view) {
function slugifyWithUTF8 (text) {
// remove HTML tags and trim spaces
let newText = S(text).trim().stripTags().s
let newText = filterXSS(text.trim(), {
whiteList: {},
stripIgnoreTag: true,
stripIgnoreTagBody: ['script', 'style']
})
// replace space between words with dashes
newText = newText.replace(/\s+/g, '-')
// slugify string to make it valid as an attribute
@@ -318,7 +322,7 @@ export function finishView (view) {
svg[0].setAttribute('preserveAspectRatio', 'xMidYMid meet')
} catch (err) {
$value.unwrap()
$value.parent().append(`<div class="alert alert-warning">${escapeHTML(err)}</div>`)
$value.parent().append(`<div class="alert alert-warning">${escapeHtml(err)}</div>`)
console.warn(err)
}
})
@@ -343,7 +347,7 @@ export function finishView (view) {
$value.children().unwrap().unwrap()
} catch (err) {
$value.unwrap()
$value.parent().append(`<div class="alert alert-warning">${escapeHTML(err)}</div>`)
$value.parent().append(`<div class="alert alert-warning">${escapeHtml(err)}</div>`)
console.warn(err)
}
})
@@ -365,7 +369,7 @@ export function finishView (view) {
})
} catch (err) {
$value.unwrap()
$value.parent().append(`<div class="alert alert-warning">${escapeHTML(err)}</div>`)
$value.parent().append(`<div class="alert alert-warning">${escapeHtml(err)}</div>`)
console.warn(err)
}
})
@@ -388,7 +392,7 @@ export function finishView (view) {
errormessage = err.str
}
$value.unwrap()
$value.parent().append(`<div class="alert alert-warning">${escapeHTML(errormessage)}</div>`)
$value.parent().append(`<div class="alert alert-warning">${escapeHtml(errormessage)}</div>`)
console.warn(errormessage)
}
})
@@ -411,7 +415,7 @@ export function finishView (view) {
})
} catch (err) {
$value.unwrap()
$value.parent().append(`<div class="alert alert-warning">${escapeHTML(err)}</div>`)
$value.parent().append(`<div class="alert alert-warning">${escapeHtml(err)}</div>`)
console.warn(err)
}
})
@@ -486,24 +490,24 @@ export function finishView (view) {
value: code
}
} else if (reallang === 'haskell' || reallang === 'go' || reallang === 'typescript' || reallang === 'jsx' || reallang === 'gherkin') {
code = S(code).unescapeHTML().s
code = unescapeHtml(code)
result = {
value: Prism.highlight(code, Prism.languages[reallang])
}
} else if (reallang === 'tiddlywiki' || reallang === 'mediawiki') {
code = S(code).unescapeHTML().s
code = unescapeHtml(code)
result = {
value: Prism.highlight(code, Prism.languages.wiki)
}
} else if (reallang === 'cmake') {
code = S(code).unescapeHTML().s
code = unescapeHtml(code)
result = {
value: Prism.highlight(code, Prism.languages.makefile)
}
} else {
require.ensure([], function (require) {
const hljs = require('highlight.js')
code = S(code).unescapeHTML().s
code = unescapeHtml(code)
const languages = hljs.listLanguages()
if (!languages.includes(reallang)) {
result = hljs.highlightAuto(code)
@@ -576,7 +580,7 @@ export function postProcess (code) {
if (warning && warning.length > 0) {
warning.text(md.metaError)
} else {
warning = $(`<div id="meta-error" class="alert alert-warning">${escapeHTML(md.metaError)}</div>`)
warning = $(`<div id="meta-error" class="alert alert-warning">${escapeHtml(md.metaError)}</div>`)
result.prepend(warning)
}
}
@@ -963,7 +967,7 @@ export function scrollToHash () {
function highlightRender (code, lang) {
if (!lang || /no(-?)highlight|plain|text/.test(lang)) { return }
code = S(code).escapeHTML().s
code = escapeHtml(code)
if (lang === 'sequence') {
return `<div class="sequence-diagram raw">${code}</div>`
} else if (lang === 'flow') {

View File

@@ -2,13 +2,13 @@
/* global serverurl, moment */
import store from 'store'
import S from 'string'
import LZString from 'lz-string'
import url from 'wurl'
import {
checkNoteIdValid,
encodeNoteId
encodeNoteId,
escapeHtml
} from './utils'
import {
@@ -275,8 +275,8 @@ function parseToHistory (list, notehistory, callback) {
notehistory[i].fromNow = timestamp.fromNow()
notehistory[i].time = timestamp.format('llll')
// prevent XSS
notehistory[i].text = S(notehistory[i].text).escapeHTML().s
notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? S(notehistory[i].tags).escapeHTML().s.split(',') : []
notehistory[i].text = escapeHtml(notehistory[i].text)
notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? escapeHtml(notehistory[i].tags).split(',') : []
// add to list
if (notehistory[i].id && list.get('id', notehistory[i].id).length === 0) { list.add(notehistory[i]) }
}

View File

@@ -16,7 +16,7 @@ import { ot } from '../vendor/ot/ot.min.js'
import hex2rgb from '../vendor/ot/hex2rgb'
import { saveAs } from 'file-saver'
import randomColor from 'randomcolor'
import chance from 'chance'
import store from 'store'
import url from 'wurl'
import { Spinner } from 'spin.js'
@@ -427,7 +427,7 @@ const supportExtraTags = [
text: '[random color tag]',
search: '[]',
command: function () {
const color = randomColor()
const color = chance().color()
return '[color=' + color + ']'
}
}

View File

@@ -30,3 +30,21 @@ export function decodeNoteId (encodedId) {
idParts.push(id.substr(20, 12))
return idParts.join('-')
}
// use browser's DOM APIs for escaping and unescaping HTML
export function escapeHtml (unsafe) {
if (!unsafe) {
return ''
}
const tempDiv = document.createElement('div')
tempDiv.appendChild(document.createTextNode(String(unsafe)))
return tempDiv.innerHTML
}
export function unescapeHtml (escapedHtml) {
if (!escapedHtml) {
return ''
}
const doc = new DOMParser().parseFromString(escapedHtml, 'text/html')
return doc.documentElement.textContent || ''
}

1991
yarn.lock

File diff suppressed because it is too large Load Diff