diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 103547d7d..1ba351acc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -103,3 +103,9 @@ mergeManifests: only: - stable - develop + +include: + - template: Jobs/Dependency-Scanning.latest.gitlab-ci.yml + - template: Jobs/License-Scanning.latest.gitlab-ci.yml + - template: Jobs/SAST.latest.gitlab-ci.yml + - template: Jobs/Secret-Detection.latest.gitlab-ci.yml diff --git a/.njsscan b/.njsscan new file mode 100644 index 000000000..2acb98d0e --- /dev/null +++ b/.njsscan @@ -0,0 +1,6 @@ +--- +- ignore-filenames: + # there may be a "header injection", I'm pretty sure we don't + # trigger it given the way we use this, but please remove this + # when/if the library gets updated + - packages/frontend/assets/libopenmpt.js diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 000000000..4704b61b0 --- /dev/null +++ b/.semgrepignore @@ -0,0 +1,4 @@ +/packages/frontend/.storybook/ +/packages/frontend/assets/ +/scripts/ +/tossface-emojis/ diff --git a/locales/index.js b/locales/index.js index 650e55233..86fb26c81 100644 --- a/locales/index.js +++ b/locales/index.js @@ -49,7 +49,7 @@ const primaries = { }; // 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く -const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), ''); +const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), ''); // eslint-disable-line detect-non-literal-regexp export function build() { const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, import.meta.url), 'utf-8'))) || {}, a), {}); diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index 51c425271..f502d3e07 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -474,177 +474,177 @@ export class MfmService { } const handlers: { - [K in mfm.MfmNode['type']]: (node: mfm.NodeType) => any; - } = { - async bold(node) { - const el = doc.createElement('span'); - el.textContent = '**'; - await appendChildren(node.children, el); - el.textContent += '**'; - return el; - }, + [K in mfm.MfmNode['type']]: (node: mfm.NodeType) => any; + } = { + async bold(node) { + const el = doc.createElement('span'); + el.textContent = '**'; + await appendChildren(node.children, el); + el.textContent += '**'; + return el; + }, - async small(node) { - const el = doc.createElement('small'); - await appendChildren(node.children, el); - return el; - }, + async small(node) { + const el = doc.createElement('small'); + await appendChildren(node.children, el); + return el; + }, - async strike(node) { - const el = doc.createElement('span'); - el.textContent = '~~'; - await appendChildren(node.children, el); - el.textContent += '~~'; - return el; - }, + async strike(node) { + const el = doc.createElement('span'); + el.textContent = '~~'; + await appendChildren(node.children, el); + el.textContent += '~~'; + return el; + }, - async italic(node) { - const el = doc.createElement('span'); - el.textContent = '*'; - await appendChildren(node.children, el); - el.textContent += '*'; - return el; - }, + async italic(node) { + const el = doc.createElement('span'); + el.textContent = '*'; + await appendChildren(node.children, el); + el.textContent += '*'; + return el; + }, - async fn(node) { - const el = doc.createElement('span'); - el.textContent = '*'; - await appendChildren(node.children, el); - el.textContent += '*'; - return el; - }, + async fn(node) { + const el = doc.createElement('span'); + el.textContent = '*'; + await appendChildren(node.children, el); + el.textContent += '*'; + return el; + }, - blockCode(node) { - const pre = doc.createElement('pre'); - const inner = doc.createElement('code'); + blockCode(node) { + const pre = doc.createElement('pre'); + const inner = doc.createElement('code'); - const nodes = node.props.code - .split(/\r\n|\r|\n/) - .map((x) => doc.createTextNode(x)); + const nodes = node.props.code + .split(/\r\n|\r|\n/) + .map((x) => doc.createTextNode(x)); - for (const x of intersperse('br', nodes)) { - inner.appendChild(x === 'br' ? doc.createElement('br') : x); - } + for (const x of intersperse('br', nodes)) { + inner.appendChild(x === 'br' ? doc.createElement('br') : x); + } - pre.appendChild(inner); - return pre; - }, + pre.appendChild(inner); + return pre; + }, - async center(node) { - const el = doc.createElement('div'); - await appendChildren(node.children, el); - return el; - }, + async center(node) { + const el = doc.createElement('div'); + await appendChildren(node.children, el); + return el; + }, - emojiCode(node) { - return doc.createTextNode(`\u200B:${node.props.name}:\u200B`); - }, + emojiCode(node) { + return doc.createTextNode(`\u200B:${node.props.name}:\u200B`); + }, - unicodeEmoji(node) { - return doc.createTextNode(node.props.emoji); - }, + unicodeEmoji(node) { + return doc.createTextNode(node.props.emoji); + }, - hashtag: (node) => { - const a = doc.createElement('a'); - a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`); - a.textContent = `#${node.props.hashtag}`; - a.setAttribute('rel', 'tag'); - a.setAttribute('class', 'hashtag'); - return a; - }, + hashtag: (node) => { + const a = doc.createElement('a'); + a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`); + a.textContent = `#${node.props.hashtag}`; + a.setAttribute('rel', 'tag'); + a.setAttribute('class', 'hashtag'); + return a; + }, - inlineCode(node) { - const el = doc.createElement('code'); - el.textContent = node.props.code; - return el; - }, + inlineCode(node) { + const el = doc.createElement('code'); + el.textContent = node.props.code; + return el; + }, - mathInline(node) { - const el = doc.createElement('code'); - el.textContent = node.props.formula; - return el; - }, + mathInline(node) { + const el = doc.createElement('code'); + el.textContent = node.props.formula; + return el; + }, - mathBlock(node) { - const el = doc.createElement('code'); - el.textContent = node.props.formula; - return el; - }, + mathBlock(node) { + const el = doc.createElement('code'); + el.textContent = node.props.formula; + return el; + }, - async link(node) { - const a = doc.createElement('a'); - a.setAttribute('rel', 'nofollow noopener noreferrer'); - a.setAttribute('target', '_blank'); - a.setAttribute('href', node.props.url); - await appendChildren(node.children, a); - return a; - }, + async link(node) { + const a = doc.createElement('a'); + a.setAttribute('rel', 'nofollow noopener noreferrer'); + a.setAttribute('target', '_blank'); + a.setAttribute('href', node.props.url); + await appendChildren(node.children, a); + return a; + }, - async mention(node) { - const { username, host, acct } = node.props; - const resolved = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host); + async mention(node) { + const { username, host, acct } = node.props; + const resolved = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host); - const el = doc.createElement('span'); - if (!resolved) { - el.textContent = acct; - } else { - el.setAttribute('class', 'h-card'); - el.setAttribute('translate', 'no'); - const a = doc.createElement('a'); - a.setAttribute('href', resolved.url ? resolved.url : resolved.uri); - a.className = 'u-url mention'; - const span = doc.createElement('span'); - span.textContent = resolved.username || username; - a.textContent = '@'; - a.appendChild(span); - el.appendChild(a); - } + const el = doc.createElement('span'); + if (!resolved) { + el.textContent = acct; + } else { + el.setAttribute('class', 'h-card'); + el.setAttribute('translate', 'no'); + const a = doc.createElement('a'); + a.setAttribute('href', resolved.url ? resolved.url : resolved.uri); + a.className = 'u-url mention'; + const span = doc.createElement('span'); + span.textContent = resolved.username || username; + a.textContent = '@'; + a.appendChild(span); + el.appendChild(a); + } - return el; - }, + return el; + }, - async quote(node) { - const el = doc.createElement('blockquote'); - await appendChildren(node.children, el); - return el; - }, + async quote(node) { + const el = doc.createElement('blockquote'); + await appendChildren(node.children, el); + return el; + }, - text(node) { - const el = doc.createElement('span'); - const nodes = node.props.text - .split(/\r\n|\r|\n/) - .map((x) => doc.createTextNode(x)); + text(node) { + const el = doc.createElement('span'); + const nodes = node.props.text + .split(/\r\n|\r|\n/) + .map((x) => doc.createTextNode(x)); - for (const x of intersperse('br', nodes)) { - el.appendChild(x === 'br' ? doc.createElement('br') : x); - } + for (const x of intersperse('br', nodes)) { + el.appendChild(x === 'br' ? doc.createElement('br') : x); + } - return el; - }, + return el; + }, - url(node) { - const a = doc.createElement('a'); - a.setAttribute('rel', 'nofollow noopener noreferrer'); - a.setAttribute('target', '_blank'); - a.setAttribute('href', node.props.url); - a.textContent = node.props.url.replace(/^https?:\/\//, ''); - return a; - }, + url(node) { + const a = doc.createElement('a'); + a.setAttribute('rel', 'nofollow noopener noreferrer'); + a.setAttribute('target', '_blank'); + a.setAttribute('href', node.props.url); + a.textContent = node.props.url.replace(/^https?:\/\//, ''); + return a; + }, - search: (node) => { - const a = doc.createElement('a'); - a.setAttribute('href', `https"google.com/${node.props.query}`); - a.textContent = node.props.content; - return a; - }, + search: (node) => { + const a = doc.createElement('a'); + a.setAttribute('href', `https"google.com/${node.props.query}`); + a.textContent = node.props.content; + return a; + }, + + async plain(node) { + const el = doc.createElement('span'); + await appendChildren(node.children, el); + return el; + }, + }; - async plain(node) { - const el = doc.createElement('span'); - await appendChildren(node.children, el); - return el; - }, - }; - await appendChildren(nodes, doc.body); if (quoteUri !== null) { diff --git a/packages/backend/src/core/UserAuthService.ts b/packages/backend/src/core/UserAuthService.ts index ccf4dfc6b..eb9dcb8c0 100644 --- a/packages/backend/src/core/UserAuthService.ts +++ b/packages/backend/src/core/UserAuthService.ts @@ -11,6 +11,7 @@ import type { MiUserProfile, UserProfilesRepository, UsersRepository } from '@/m import { bindThis } from '@/decorators.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import type { MiLocalUser } from '@/models/User.js'; +import { secureishCompare } from '@/misc/secure-ish-compare.js'; @Injectable() export class UserAuthService { @@ -27,7 +28,9 @@ export class UserAuthService { public async twoFactorAuthenticate(profile: MiUserProfile, token: string): Promise { if (profile.twoFactorBackupSecret?.includes(token)) { await this.userProfilesRepository.update({ userId: profile.userId }, { - twoFactorBackupSecret: profile.twoFactorBackupSecret.filter((secret) => secret !== token), + twoFactorBackupSecret: profile.twoFactorBackupSecret.filter( + (secret) => !secureishCompare(secret, token) + ), }); } else { const delta = OTPAuth.TOTP.validate({ diff --git a/packages/backend/src/misc/emoji-regex.ts b/packages/backend/src/misc/emoji-regex.ts index 04c2f2e91..692298038 100644 --- a/packages/backend/src/misc/emoji-regex.ts +++ b/packages/backend/src/misc/emoji-regex.ts @@ -6,5 +6,6 @@ // taken from @twemoji/parser/dist/lib/regex.js const twemojiRegex = /(?:\ud83d\udc68\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffc-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb\udffd-\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb-\udffd\udfff]|\ud83e\uddd1\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83e\uddd1\ud83c[\udffb-\udffe]|\ud83d\udc68\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc68\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc68\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc68\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc68\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83d\udc69\ud83c[\udffb-\udfff]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffe]|\ud83e\uddd1\ud83c\udffb\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffc-\udfff]|\ud83e\uddd1\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb\udffd-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb-\udffd\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udfff\u200d\u2764\ufe0f\u200d\ud83e\uddd1\ud83c[\udffb-\udffe]|\ud83e\uddd1\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d[\udc68\udc69]|\ud83e\udef1\ud83c\udffb\u200d\ud83e\udef2\ud83c[\udffc-\udfff]|\ud83e\udef1\ud83c\udffc\u200d\ud83e\udef2\ud83c[\udffb\udffd-\udfff]|\ud83e\udef1\ud83c\udffd\u200d\ud83e\udef2\ud83c[\udffb\udffc\udffe\udfff]|\ud83e\udef1\ud83c\udffe\u200d\ud83e\udef2\ud83c[\udffb-\udffd\udfff]|\ud83e\udef1\ud83c\udfff\u200d\ud83e\udef2\ud83c[\udffb-\udffe]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d[\udc68\udc69]|\ud83e\uddd1\u200d\ud83e\udd1d\u200d\ud83e\uddd1|\ud83d\udc6b\ud83c[\udffb-\udfff]|\ud83d\udc6c\ud83c[\udffb-\udfff]|\ud83d\udc6d\ud83c[\udffb-\udfff]|\ud83d\udc8f\ud83c[\udffb-\udfff]|\ud83d\udc91\ud83c[\udffb-\udfff]|\ud83e\udd1d\ud83c[\udffb-\udfff]|\ud83d[\udc6b-\udc6d\udc8f\udc91]|\ud83e\udd1d)|(?:\ud83d[\udc68\udc69]|\ud83e\uddd1)(?:\ud83c[\udffb-\udfff])?\u200d(?:\u2695\ufe0f|\u2696\ufe0f|\u2708\ufe0f|\ud83c[\udf3e\udf73\udf7c\udf84\udf93\udfa4\udfa8\udfeb\udfed]|\ud83d[\udcbb\udcbc\udd27\udd2c\ude80\ude92]|\ud83e[\uddaf-\uddb3\uddbc\uddbd])|(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75]|\u26f9)((?:\ud83c[\udffb-\udfff]|\ufe0f)\u200d[\u2640\u2642]\ufe0f)|(?:\ud83c[\udfc3\udfc4\udfca]|\ud83d[\udc6e\udc70\udc71\udc73\udc77\udc81\udc82\udc86\udc87\ude45-\ude47\ude4b\ude4d\ude4e\udea3\udeb4-\udeb6]|\ud83e[\udd26\udd35\udd37-\udd39\udd3d\udd3e\uddb8\uddb9\uddcd-\uddcf\uddd4\uddd6-\udddd])(?:\ud83c[\udffb-\udfff])?\u200d[\u2640\u2642]\ufe0f|(?:\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f|\ud83c\udff3\ufe0f\u200d\ud83c\udf08|\ud83d\ude36\u200d\ud83c\udf2b\ufe0f|\u2764\ufe0f\u200d\ud83d\udd25|\u2764\ufe0f\u200d\ud83e\ude79|\ud83c\udff4\u200d\u2620\ufe0f|\ud83d\udc15\u200d\ud83e\uddba|\ud83d\udc3b\u200d\u2744\ufe0f|\ud83d\udc41\u200d\ud83d\udde8|\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc6f\u200d\u2640\ufe0f|\ud83d\udc6f\u200d\u2642\ufe0f|\ud83d\ude2e\u200d\ud83d\udca8|\ud83d\ude35\u200d\ud83d\udcab|\ud83e\udd3c\u200d\u2640\ufe0f|\ud83e\udd3c\u200d\u2642\ufe0f|\ud83e\uddde\u200d\u2640\ufe0f|\ud83e\uddde\u200d\u2642\ufe0f|\ud83e\udddf\u200d\u2640\ufe0f|\ud83e\udddf\u200d\u2642\ufe0f|\ud83d\udc08\u200d\u2b1b|\ud83d\udc26\u200d\u2b1b)|[#*0-9]\ufe0f?\u20e3|(?:[©®\u2122\u265f]\ufe0f)|(?:\ud83c[\udc04\udd70\udd71\udd7e\udd7f\ude02\ude1a\ude2f\ude37\udf21\udf24-\udf2c\udf36\udf7d\udf96\udf97\udf99-\udf9b\udf9e\udf9f\udfcd\udfce\udfd4-\udfdf\udff3\udff5\udff7]|\ud83d[\udc3f\udc41\udcfd\udd49\udd4a\udd6f\udd70\udd73\udd76-\udd79\udd87\udd8a-\udd8d\udda5\udda8\uddb1\uddb2\uddbc\uddc2-\uddc4\uddd1-\uddd3\udddc-\uddde\udde1\udde3\udde8\uddef\uddf3\uddfa\udecb\udecd-\udecf\udee0-\udee5\udee9\udef0\udef3]|[\u203c\u2049\u2139\u2194-\u2199\u21a9\u21aa\u231a\u231b\u2328\u23cf\u23ed-\u23ef\u23f1\u23f2\u23f8-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267b\u267f\u2692-\u2697\u2699\u269b\u269c\u26a0\u26a1\u26a7\u26aa\u26ab\u26b0\u26b1\u26bd\u26be\u26c4\u26c5\u26c8\u26cf\u26d1\u26d3\u26d4\u26e9\u26ea\u26f0-\u26f5\u26f8\u26fa\u26fd\u2702\u2708\u2709\u270f\u2712\u2714\u2716\u271d\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u2764\u27a1\u2934\u2935\u2b05-\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299])(?:\ufe0f|(?!\ufe0e))|(?:(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75\udd90]|\ud83e\udef0|[\u261d\u26f7\u26f9\u270c\u270d])(?:\ufe0f|(?!\ufe0e))|(?:\ud83c[\udf85\udfc2-\udfc4\udfc7\udfca]|\ud83d[\udc42\udc43\udc46-\udc50\udc66-\udc69\udc6e\udc70-\udc78\udc7c\udc81-\udc83\udc85-\udc87\udcaa\udd7a\udd95\udd96\ude45-\ude47\ude4b-\ude4f\udea3\udeb4-\udeb6\udec0\udecc]|\ud83e[\udd0c\udd0f\udd18-\udd1c\udd1e\udd1f\udd26\udd30-\udd39\udd3d\udd3e\udd77\uddb5\uddb6\uddb8\uddb9\uddbb\uddcd-\uddcf\uddd1-\udddd\udec3-\udec5\udef1-\udef8]|[\u270a\u270b]))(?:\ud83c[\udffb-\udfff])?|(?:\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f|\ud83c\udde6\ud83c[\udde8-\uddec\uddee\uddf1\uddf2\uddf4\uddf6-\uddfa\uddfc\uddfd\uddff]|\ud83c\udde7\ud83c[\udde6\udde7\udde9-\uddef\uddf1-\uddf4\uddf6-\uddf9\uddfb\uddfc\uddfe\uddff]|\ud83c\udde8\ud83c[\udde6\udde8\udde9\uddeb-\uddee\uddf0-\uddf5\uddf7\uddfa-\uddff]|\ud83c\udde9\ud83c[\uddea\uddec\uddef\uddf0\uddf2\uddf4\uddff]|\ud83c\uddea\ud83c[\udde6\udde8\uddea\uddec\udded\uddf7-\uddfa]|\ud83c\uddeb\ud83c[\uddee-\uddf0\uddf2\uddf4\uddf7]|\ud83c\uddec\ud83c[\udde6\udde7\udde9-\uddee\uddf1-\uddf3\uddf5-\uddfa\uddfc\uddfe]|\ud83c\udded\ud83c[\uddf0\uddf2\uddf3\uddf7\uddf9\uddfa]|\ud83c\uddee\ud83c[\udde8-\uddea\uddf1-\uddf4\uddf6-\uddf9]|\ud83c\uddef\ud83c[\uddea\uddf2\uddf4\uddf5]|\ud83c\uddf0\ud83c[\uddea\uddec-\uddee\uddf2\uddf3\uddf5\uddf7\uddfc\uddfe\uddff]|\ud83c\uddf1\ud83c[\udde6-\udde8\uddee\uddf0\uddf7-\uddfb\uddfe]|\ud83c\uddf2\ud83c[\udde6\udde8-\udded\uddf0-\uddff]|\ud83c\uddf3\ud83c[\udde6\udde8\uddea-\uddec\uddee\uddf1\uddf4\uddf5\uddf7\uddfa\uddff]|\ud83c\uddf4\ud83c\uddf2|\ud83c\uddf5\ud83c[\udde6\uddea-\udded\uddf0-\uddf3\uddf7-\uddf9\uddfc\uddfe]|\ud83c\uddf6\ud83c\udde6|\ud83c\uddf7\ud83c[\uddea\uddf4\uddf8\uddfa\uddfc]|\ud83c\uddf8\ud83c[\udde6-\uddea\uddec-\uddf4\uddf7-\uddf9\uddfb\uddfd-\uddff]|\ud83c\uddf9\ud83c[\udde6\udde8\udde9\uddeb-\udded\uddef-\uddf4\uddf7\uddf9\uddfb\uddfc\uddff]|\ud83c\uddfa\ud83c[\udde6\uddec\uddf2\uddf3\uddf8\uddfe\uddff]|\ud83c\uddfb\ud83c[\udde6\udde8\uddea\uddec\uddee\uddf3\uddfa]|\ud83c\uddfc\ud83c[\uddeb\uddf8]|\ud83c\uddfd\ud83c\uddf0|\ud83c\uddfe\ud83c[\uddea\uddf9]|\ud83c\uddff\ud83c[\udde6\uddf2\uddfc]|\ud83c[\udccf\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude32-\ude36\ude38-\ude3a\ude50\ude51\udf00-\udf20\udf2d-\udf35\udf37-\udf7c\udf7e-\udf84\udf86-\udf93\udfa0-\udfc1\udfc5\udfc6\udfc8\udfc9\udfcf-\udfd3\udfe0-\udff0\udff4\udff8-\udfff]|\ud83d[\udc00-\udc3e\udc40\udc44\udc45\udc51-\udc65\udc6a\udc6f\udc79-\udc7b\udc7d-\udc80\udc84\udc88-\udc8e\udc90\udc92-\udca9\udcab-\udcfc\udcff-\udd3d\udd4b-\udd4e\udd50-\udd67\udda4\uddfb-\ude44\ude48-\ude4a\ude80-\udea2\udea4-\udeb3\udeb7-\udebf\udec1-\udec5\uded0-\uded2\uded5-\uded7\udedc-\udedf\udeeb\udeec\udef4-\udefc\udfe0-\udfeb\udff0]|\ud83e[\udd0d\udd0e\udd10-\udd17\udd20-\udd25\udd27-\udd2f\udd3a\udd3c\udd3f-\udd45\udd47-\udd76\udd78-\uddb4\uddb7\uddba\uddbc-\uddcc\uddd0\uddde-\uddff\ude70-\ude7c\ude80-\ude88\ude90-\udebd\udebf-\udec2\udece-\udedb\udee0-\udee8]|[\u23e9-\u23ec\u23f0\u23f3\u267e\u26ce\u2705\u2728\u274c\u274e\u2753-\u2755\u2795-\u2797\u27b0\u27bf\ue50a])|\ufe0f/g; +// eslint-disable detect-non-literal-regexp export const emojiRegex = new RegExp(`(${twemojiRegex.source})`); export const emojiRegexAtStartToEnd = new RegExp(`^(${twemojiRegex.source})$`); diff --git a/packages/backend/src/misc/secure-ish-compare.ts b/packages/backend/src/misc/secure-ish-compare.ts new file mode 100644 index 000000000..1a359e799 --- /dev/null +++ b/packages/backend/src/misc/secure-ish-compare.ts @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: dakkar and other sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as crypto from 'node:crypto'; + +export function secureishCompare(expected: string, given:string) { + const expectedLen = expected.length; + const givenLen = given.length; + + /* + this ensures that the two strings are the same length, so + `timingSafeEqual` will not throw an error + + notice that we perform the same operations regardless of the + length of the strings, we're trying not to leak timing + information! + */ + const paddedExpected = Buffer.from(expected + given); + const paddedGiven = Buffer.from(given + expected); + + /* + if the two strings were equal, `sortOfEqual` will be true + + if will be true in other cases, too! like `abc` and `abcabc`; but + then, `expectedLen` and `givenLen` would be different, and we'd + return false + */ + const sortOfEqual = crypto.timingSafeEqual(paddedExpected, paddedGiven); + + return sortOfEqual && (givenLen === expectedLen); +} diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index d0968d292..ad5f17642 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -46,6 +46,7 @@ export class ExportAntennasProcessorService { return; } const [path, cleanup] = await createTemp(); + // eslint-disable-next-line detect-non-literal-fs-filename const stream = fs.createWriteStream(path, { flags: 'a' }); const write = (input: string): Promise => { return new Promise((resolve, reject) => { diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index b1562a95f..9a38d5dd3 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -38,6 +38,7 @@ import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOption import type { FindOptionsWhere } from 'typeorm'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { secureishCompare } from '@/misc/secure-ish-compare.js'; const ACTIVITY_JSON = 'application/activity+json; charset=utf-8'; const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8'; @@ -287,7 +288,7 @@ export class ActivityPubServerService { const hash = crypto.createHash('sha256').update(request.rawBody).digest('base64'); - if (hash !== digestValue) { + if (!secureishCompare(hash, digestValue)) { // Invalid digest reply.code(401); return; @@ -795,7 +796,7 @@ export class ActivityPubServerService { fastify.get<{ Params: { user: string; } }>('/users/:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { if (await this.shouldRefuseGetRequest(request, reply, request.params.user)) return; - + vary(reply.raw, 'Accept'); const userId = request.params.user; @@ -811,7 +812,7 @@ export class ActivityPubServerService { fastify.get<{ Params: { user: string; } }>('/@:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { if (await this.shouldRefuseGetRequest(request, reply, request.params.user)) return; - + vary(reply.raw, 'Accept'); const user = await this.usersRepository.findOneBy({ diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index f07568819..2ce3c2e5b 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -42,7 +42,7 @@ export class AuthenticateService implements OnApplicationShutdown { @bindThis public async authenticate(token: string | null | undefined): Promise<[MiLocalUser | null, MiAccessToken | null]> { - if (token == null) { + if (token == null) { // eslint-disable-line detect-possible-timing-attacks return [null, null]; } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index 0494af276..bf74b923b 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -76,7 +76,7 @@ export default class extends Endpoint { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); if (profile.twoFactorEnabled) { - if (token == null) { + if (token == null) { // eslint-disable-line detect-possible-timing-attacks throw new Error('authentication failed'); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index 7c4665857..63d5d6623 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -208,7 +208,7 @@ export default class extends Endpoint { // Compare password if (profile.twoFactorEnabled) { - if (token == null) { + if (token == null) { // eslint-disable-line detect-possible-timing-attacks throw new Error('authentication failed'); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index 15e50c49f..56a650b1d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -67,7 +67,7 @@ export default class extends Endpoint { // eslint- const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); if (profile.twoFactorEnabled) { - if (token == null) { + if (token == null) { // eslint-disable-line detect-possible-timing-attacks throw new Error('authentication failed'); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 159a311d3..e537f5c09 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -57,7 +57,7 @@ export default class extends Endpoint { // eslint- // Compare password if (profile.twoFactorEnabled) { - if (token == null) { + if (token == null) { // eslint-disable-line detect-possible-timing-attacks throw new Error('authentication failed'); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index 8503c72a4..e6b59a65d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -52,7 +52,7 @@ export default class extends Endpoint { // eslint- const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); if (profile.twoFactorEnabled) { - if (token == null) { + if (token == null) { // eslint-disable-line detect-possible-timing-attacks throw new Error('authentication failed'); } diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index fb0f2bc88..03a5bbc12 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -40,7 +40,7 @@ export default class extends Endpoint { // eslint- const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); if (profile.twoFactorEnabled) { - if (token == null) { + if (token == null) { // eslint-disable-line detect-possible-timing-attacks throw new Error('authentication failed'); } diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index e0b40db91..071307a5c 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -44,7 +44,7 @@ export default class extends Endpoint { // eslint- const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); if (profile.twoFactorEnabled) { - if (token == null) { + if (token == null) { // eslint-disable-line detect-possible-timing-attacks throw new Error('authentication failed'); } diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 58809871c..847c81ec1 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -77,7 +77,7 @@ export default class extends Endpoint { // eslint- const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); if (profile.twoFactorEnabled) { - if (token == null) { + if (token == null) { // eslint-disable-line detect-possible-timing-attacks throw new Error('authentication failed'); } diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 9fdfafb73..e9eaf24fd 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -200,7 +200,7 @@ export class ClientServerService { const url = decodeURI(request.routeOptions.url); if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) { const token = request.cookies.token; - if (token == null) { + if (token == null) { // eslint-disable-line detect-possible-timing-attacks reply.code(401).send('Login required'); return; } diff --git a/packages/backend/test/unit/misc/secure-ish-compare.ts b/packages/backend/test/unit/misc/secure-ish-compare.ts new file mode 100644 index 000000000..659ffba85 --- /dev/null +++ b/packages/backend/test/unit/misc/secure-ish-compare.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: dakkar and other sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { secureishCompare } from '@/misc/secure-ish-compare.js'; + +describe(secureishCompare, () => { + it('should return true if strings are equal', () => { + expect(secureishCompare('abc','abc')).toBe(true); + expect(secureishCompare('aaa','aaa')).toBe(true); + }); + it('should return false if strings are different', () => { + expect(secureishCompare('abc','def')).toBe(false); + }); + it('should return false if one is prefix of the other', () => { + expect(secureishCompare('abc','abcabc')).toBe(false); + expect(secureishCompare('abcabc','abc')).toBe(false); + expect(secureishCompare('aaa','aa')).toBe(false); + }); + it('should return false if strings are very different', () => { + expect(secureishCompare('abc','defghi')).toBe(false); + expect(secureishCompare('defghi','abc')).toBe(false); + }); +}); diff --git a/packages/frontend/public/mockServiceWorker.js b/packages/frontend/public/mockServiceWorker.js index 5384ce6b9..66fd67c58 100644 --- a/packages/frontend/public/mockServiceWorker.js +++ b/packages/frontend/public/mockServiceWorker.js @@ -117,6 +117,7 @@ self.addEventListener('fetch', function (event) { } // Generate unique request ID. + // eslint-disable-next-line node_insecure_random_generator const requestId = Math.random().toString(16).slice(2) event.respondWith( diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 0b1060530..764e55025 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + ({{ emoji.aliasOf }}) diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue index b06bb70e9..78d4b6090 100644 --- a/packages/frontend/src/components/MkCode.core.vue +++ b/packages/frontend/src/components/MkCode.core.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only