-
Notifications
You must be signed in to change notification settings - Fork 13.8k
Release 8.7.1 #41821
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Release 8.7.1 #41821
Changes from all commits
b461b4f
11eeb00
700af55
3535014
6d90fb8
b264b12
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@rocket.chat/meteor': patch | ||
| --- | ||
|
|
||
| Bump @rocket.chat/meteor version. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@rocket.chat/meteor': patch | ||
| --- | ||
|
|
||
| Adds per-client rate limiting to the unauthenticated sendForgotPasswordEmail method, matching the REST users.forgotPassword endpoint |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@rocket.chat/meteor': patch | ||
| --- | ||
|
|
||
| Security Hotfix (https://docs.rocket.chat/docs/security-fixes-and-updates) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@rocket.chat/meteor': patch | ||
| --- | ||
|
|
||
| Fixes an issue where a `MultiSelect` option checkbox remained checked after the option was deselected |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@rocket.chat/meteor': patch | ||
| --- | ||
|
|
||
| Replace http with serverFetch in downloadPublicImportFile to add SSRF protection |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@rocket.chat/meteor': patch | ||
| --- | ||
|
|
||
| Fixes special characters not being escaped in the visitor name shown in the Omnichannel queue side panel's message preview |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,23 +1,34 @@ | ||||||||||||||||||||||||||||
| import fs from 'node:fs'; | ||||||||||||||||||||||||||||
| import http from 'node:http'; | ||||||||||||||||||||||||||||
| import https from 'node:https'; | ||||||||||||||||||||||||||||
| import type { Readable } from 'node:stream'; | ||||||||||||||||||||||||||||
| import { pipeline } from 'node:stream/promises'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import { Import } from '@rocket.chat/core-services'; | ||||||||||||||||||||||||||||
| import type { IUser } from '@rocket.chat/core-typings'; | ||||||||||||||||||||||||||||
| import type { ServerMethods } from '@rocket.chat/ddp-client'; | ||||||||||||||||||||||||||||
| import { serverFetch as fetch } from '@rocket.chat/server-fetch'; | ||||||||||||||||||||||||||||
| import { Meteor } from 'meteor/meteor'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import { ProgressStep } from '../../../app/importer/lib/ImporterProgressStep'; | ||||||||||||||||||||||||||||
| import { hasPermissionAsync } from '../../lib/authorization/hasPermission'; | ||||||||||||||||||||||||||||
| import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger'; | ||||||||||||||||||||||||||||
| import { Importers } from '../../lib/import'; | ||||||||||||||||||||||||||||
| import { RocketChatImportFileInstance } from '../../lib/import/startup/store'; | ||||||||||||||||||||||||||||
| import { SystemLogger } from '../../lib/logger/system'; | ||||||||||||||||||||||||||||
| import { settings } from '../../settings'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function downloadHttpFile(fileUrl: string, writeStream: fs.WriteStream): void { | ||||||||||||||||||||||||||||
| const protocol = fileUrl.startsWith('https') ? https : http; | ||||||||||||||||||||||||||||
| protocol.get(fileUrl, (response) => { | ||||||||||||||||||||||||||||
| response.pipe(writeStream); | ||||||||||||||||||||||||||||
| async function getHttpFileStream(fileUrl: string): Promise<Readable> { | ||||||||||||||||||||||||||||
| const response = await fetch(fileUrl, { | ||||||||||||||||||||||||||||
| ignoreSsrfValidation: false, | ||||||||||||||||||||||||||||
| allowList: settings.get<string>('SSRF_Allowlist'), | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const body = response.body as Readable; | ||||||||||||||||||||||||||||
| if (!response.ok) { | ||||||||||||||||||||||||||||
| body.resume(); | ||||||||||||||||||||||||||||
| throw new Error(`Unexpected response status ${response.status}`); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return body; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function copyLocalFile(filePath: fs.PathLike, writeStream: fs.WriteStream): void { | ||||||||||||||||||||||||||||
|
|
@@ -53,27 +64,46 @@ export const executeDownloadPublicImportFile = async (userId: IUser['_id'], file | |||||||||||||||||||||||||||
| await instance.updateProgress(ProgressStep.DOWNLOADING_FILE); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const writeStream = RocketChatImportFileInstance.createWriteStream(newFileName); | ||||||||||||||||||||||||||||
| let errorProgressUpdate: Promise<unknown> | undefined; | ||||||||||||||||||||||||||||
| const markImportAsFailed = (): Promise<unknown> => { | ||||||||||||||||||||||||||||
| errorProgressUpdate ??= instance.updateProgress(ProgressStep.ERROR).catch((error) => { | ||||||||||||||||||||||||||||
| SystemLogger.error({ msg: 'Failed to update import progress to ERROR', err: error }); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| return errorProgressUpdate; | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| writeStream.on('error', () => { | ||||||||||||||||||||||||||||
| void instance.updateProgress(ProgressStep.ERROR); | ||||||||||||||||||||||||||||
| void markImportAsFailed(); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| writeStream.on('end', () => { | ||||||||||||||||||||||||||||
| let readStream: Readable | undefined; | ||||||||||||||||||||||||||||
| if (isUrl) { | ||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||
| readStream = await getHttpFileStream(fileUrl); | ||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||
| writeStream.destroy(); | ||||||||||||||||||||||||||||
| await markImportAsFailed(); | ||||||||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: When SSRF validation or a non-OK response fails, Prompt for AI agents |
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| writeStream.on('finish', () => { | ||||||||||||||||||||||||||||
| void instance.updateProgress(ProgressStep.FILE_LOADED); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (isUrl) { | ||||||||||||||||||||||||||||
| downloadHttpFile(fileUrl, writeStream); | ||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||
| // If the url is actually a folder path on the current machine, skip moving it to the file store | ||||||||||||||||||||||||||||
| if (fs.statSync(fileUrl).isDirectory()) { | ||||||||||||||||||||||||||||
| await instance.updateRecord({ file: fileUrl }); | ||||||||||||||||||||||||||||
| await instance.updateProgress(ProgressStep.FILE_LOADED); | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| if (readStream) { | ||||||||||||||||||||||||||||
| void pipeline(readStream, writeStream).catch(() => markImportAsFailed()); | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| copyLocalFile(fileUrl, writeStream); | ||||||||||||||||||||||||||||
| // If the url is actually a folder path on the current machine, skip moving it to the file store | ||||||||||||||||||||||||||||
| if (fs.statSync(fileUrl).isDirectory()) { | ||||||||||||||||||||||||||||
| await instance.updateRecord({ file: fileUrl }); | ||||||||||||||||||||||||||||
| await instance.updateProgress(ProgressStep.FILE_LOADED); | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+99
to
104
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Close
🛠️ Proposed fix // If the url is actually a folder path on the current machine, skip moving it to the file store
if (fs.statSync(fileUrl).isDirectory()) {
+ writeStream.destroy();
await instance.updateRecord({ file: fileUrl });
await instance.updateProgress(ProgressStep.FILE_LOADED);
return;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| copyLocalFile(fileUrl, writeStream); | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| declare module '@rocket.chat/ddp-client' { | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Keep the rate-limit scope and release note consistent. The new rule covers DDP method invocations, but the REST handler directly calls the shared password-reset function. Add equivalent client-address limiting for the REST path, or restrict the changeset text to DDP coverage.
📍 Affects 2 files
apps/meteor/server/meteor-methods/auth/sendForgotPasswordEmail.ts#L48-L59(this comment).changeset/fuzzy-ends-refuse.md#L5-L5🤖 Prompt for AI Agents