From afc2623f70eaadfffbac2b984b8433adf0c7dfa6 Mon Sep 17 00:00:00 2001 From: Jaakko Husso Date: Wed, 9 Jul 2025 21:51:09 +0300 Subject: [PATCH 1/3] fix(core): Make our file reads more robust (no-changelog) (#17162) --- packages/@n8n/backend-common/src/index.ts | 1 + .../src/utils/__tests__/path-util.test.ts | 53 +++++++++++++++++++ .../backend-common/src/utils/path-util.ts | 36 +++++++++++++ .../source-control-helper.ee.ts | 17 +++--- .../cli/src/load-nodes-and-credentials.ts | 3 +- .../cli/src/utils/__tests__/path-util.test.ts | 24 --------- packages/cli/src/utils/path-util.ts | 16 ------ .../utils/file-system-helper-functions.ts | 5 +- .../__tests__/directory-loader.test.ts | 32 +++++++++++ .../core/src/nodes-loader/directory-loader.ts | 11 +++- 10 files changed, 144 insertions(+), 54 deletions(-) create mode 100644 packages/@n8n/backend-common/src/utils/__tests__/path-util.test.ts create mode 100644 packages/@n8n/backend-common/src/utils/path-util.ts delete mode 100644 packages/cli/src/utils/__tests__/path-util.test.ts delete mode 100644 packages/cli/src/utils/path-util.ts diff --git a/packages/@n8n/backend-common/src/index.ts b/packages/@n8n/backend-common/src/index.ts index 6f3f9092af7ee..8f26f40690e2d 100644 --- a/packages/@n8n/backend-common/src/index.ts +++ b/packages/@n8n/backend-common/src/index.ts @@ -6,3 +6,4 @@ export { isObjectLiteral } from './utils/is-object-literal'; export { Logger } from './logging/logger'; export { ModuleRegistry } from './modules/module-registry'; export { ModulesConfig, ModuleName } from './modules/modules.config'; +export { isContainedWithin, safeJoinPath } from './utils/path-util'; diff --git a/packages/@n8n/backend-common/src/utils/__tests__/path-util.test.ts b/packages/@n8n/backend-common/src/utils/__tests__/path-util.test.ts new file mode 100644 index 0000000000000..9eb11347a945d --- /dev/null +++ b/packages/@n8n/backend-common/src/utils/__tests__/path-util.test.ts @@ -0,0 +1,53 @@ +import { isContainedWithin, safeJoinPath } from '../path-util'; + +describe('isContainedWithin', () => { + it('should return true when parent and child paths are the same', () => { + expect(isContainedWithin('/some/parent/folder', '/some/parent/folder')).toBe(true); + }); + + test.each([ + ['/some/parent/folder', '/some/parent/folder/subfolder/file.txt'], + ['/some/parent/folder', '/some/parent/folder/../folder/subfolder/file.txt'], + ['/some/parent/folder/', '/some/parent/folder/subfolder/file.txt'], + ['/some/parent/folder', '/some/parent/folder/subfolder/'], + ])('should return true for parent %s and child %s', (parent, child) => { + expect(isContainedWithin(parent, child)).toBe(true); + }); + + test.each([ + ['/some/parent/folder', '/some/other/folder/file.txt'], + ['/some/parent/folder', '/some/parent/folder_but_not_really'], + ['/one/path', '/another/path'], + ])('should return false for parent %s and child %s', (parent, child) => { + expect(isContainedWithin(parent, child)).toBe(false); + }); +}); + +describe('safeJoinPath', () => { + it('should join valid paths successfully', () => { + expect(safeJoinPath('path', '')).toBe('path'); + expect(safeJoinPath('path', '.')).toBe('path'); + expect(safeJoinPath('path', '../path')).toBe('path'); + expect(safeJoinPath('path', 'foo')).toBe('path/foo'); + expect(safeJoinPath('path', 'foo/file.json')).toBe('path/foo/file.json'); + expect(safeJoinPath('path', './foo/file.json')).toBe('path/foo/file.json'); + expect(safeJoinPath('path', './foo/../file.json')).toBe('path/file.json'); + expect(safeJoinPath('/foo/bar', 'baz')).toBe('/foo/bar/baz'); + expect(safeJoinPath('/foo/bar/', 'baz')).toBe('/foo/bar/baz'); + expect(safeJoinPath('/foo', '')).toBe('/foo'); + expect(safeJoinPath('/foo', '.')).toBe('/foo'); + expect(safeJoinPath('/foo', 'bar//baz')).toBe('/foo/bar/baz'); + expect(safeJoinPath('/foo', 'bar/../baz')).toBe('/foo/baz'); + expect(safeJoinPath('/foo', '/bar/baz')).toBe('/foo/bar/baz'); + expect(safeJoinPath('/foo', '.././foo/bar')).toBe('/foo/bar'); + }); + + it('should throw an error for invalid paths', () => { + expect(() => safeJoinPath('path', '../outside/file.json')).toThrow('Path traversal detected'); + expect(() => safeJoinPath('path', './foo/../../file.json')).toThrow('Path traversal detected'); + expect(() => safeJoinPath('/foo/bar', '../../baz')).toThrow('Path traversal detected'); + expect(() => safeJoinPath('/foo/bar', '../baz')).toThrow('Path traversal detected'); + expect(() => safeJoinPath('path', '..')).toThrow('Path traversal detected'); + expect(() => safeJoinPath('/foo/bar', '..')).toThrow('Path traversal detected'); + }); +}); diff --git a/packages/@n8n/backend-common/src/utils/path-util.ts b/packages/@n8n/backend-common/src/utils/path-util.ts new file mode 100644 index 0000000000000..f55a4ea1705c1 --- /dev/null +++ b/packages/@n8n/backend-common/src/utils/path-util.ts @@ -0,0 +1,36 @@ +import { UnexpectedError } from 'n8n-workflow'; +import * as path from 'node:path'; + +/** + * Checks if the given childPath is contained within the parentPath. Resolves + * the paths before comparing them, so that relative paths are also supported. + */ +export function isContainedWithin(parentPath: string, childPath: string): boolean { + parentPath = path.resolve(parentPath); + childPath = path.resolve(childPath); + + if (parentPath === childPath) { + return true; + } + + return childPath.startsWith(parentPath + path.sep); +} + +/** + * Joins the given paths to the parentPath, ensuring that the resulting path + * is still contained within the parentPath. If not, it throws an error to + * prevent path traversal vulnerabilities. + * + * @throws {UnexpectedError} If the resulting path is not contained within the parentPath. + */ +export function safeJoinPath(parentPath: string, ...paths: string[]): string { + const candidate = path.join(parentPath, ...paths); + + if (!isContainedWithin(parentPath, candidate)) { + throw new UnexpectedError( + `Path traversal detected, refusing to join paths: ${parentPath} and ${JSON.stringify(paths)}`, + ); + } + + return candidate; +} diff --git a/packages/cli/src/environments.ee/source-control/source-control-helper.ee.ts b/packages/cli/src/environments.ee/source-control/source-control-helper.ee.ts index 8d95bb05126a1..4a35ba216aaa5 100644 --- a/packages/cli/src/environments.ee/source-control/source-control-helper.ee.ts +++ b/packages/cli/src/environments.ee/source-control/source-control-helper.ee.ts @@ -1,5 +1,5 @@ import type { SourceControlledFile } from '@n8n/api-types'; -import { Logger } from '@n8n/backend-common'; +import { Logger, isContainedWithin, safeJoinPath } from '@n8n/backend-common'; import type { TagEntity, WorkflowTagMapping } from '@n8n/db'; import { Container } from '@n8n/di'; import { generateKeyPairSync } from 'crypto'; @@ -10,7 +10,6 @@ import { readFile as fsReadFile } from 'node:fs/promises'; import path from 'path'; import { License } from '@/license'; -import { isContainedWithin } from '@/utils/path-util'; import { SOURCE_CONTROL_FOLDERS_EXPORT_FILE, @@ -28,26 +27,26 @@ export function stringContainsExpression(testString: string): boolean { } export function getWorkflowExportPath(workflowId: string, workflowExportFolder: string): string { - return path.join(workflowExportFolder, `${workflowId}.json`); + return safeJoinPath(workflowExportFolder, `${workflowId}.json`); } export function getCredentialExportPath( credentialId: string, credentialExportFolder: string, ): string { - return path.join(credentialExportFolder, `${credentialId}.json`); + return safeJoinPath(credentialExportFolder, `${credentialId}.json`); } export function getVariablesPath(gitFolder: string): string { - return path.join(gitFolder, SOURCE_CONTROL_VARIABLES_EXPORT_FILE); + return safeJoinPath(gitFolder, SOURCE_CONTROL_VARIABLES_EXPORT_FILE); } export function getTagsPath(gitFolder: string): string { - return path.join(gitFolder, SOURCE_CONTROL_TAGS_EXPORT_FILE); + return safeJoinPath(gitFolder, SOURCE_CONTROL_TAGS_EXPORT_FILE); } export function getFoldersPath(gitFolder: string): string { - return path.join(gitFolder, SOURCE_CONTROL_FOLDERS_EXPORT_FILE); + return safeJoinPath(gitFolder, SOURCE_CONTROL_FOLDERS_EXPORT_FILE); } export async function readTagAndMappingsFromSourceControlFile(file: string): Promise<{ @@ -232,7 +231,9 @@ export function normalizeAndValidateSourceControlledFilePath( ) { ok(path.isAbsolute(gitFolderPath), 'gitFolder must be an absolute path'); - const normalizedPath = path.isAbsolute(filePath) ? filePath : path.join(gitFolderPath, filePath); + const normalizedPath = path.isAbsolute(filePath) + ? filePath + : safeJoinPath(gitFolderPath, filePath); if (!isContainedWithin(gitFolderPath, filePath)) { throw new UserError(`File path ${filePath} is invalid`); diff --git a/packages/cli/src/load-nodes-and-credentials.ts b/packages/cli/src/load-nodes-and-credentials.ts index 59426bb424ae5..544872c7299b1 100644 --- a/packages/cli/src/load-nodes-and-credentials.ts +++ b/packages/cli/src/load-nodes-and-credentials.ts @@ -1,4 +1,4 @@ -import { inTest, Logger } from '@n8n/backend-common'; +import { inTest, isContainedWithin, Logger } from '@n8n/backend-common'; import { GlobalConfig } from '@n8n/config'; import { Container, Service } from '@n8n/di'; import glob from 'fast-glob'; @@ -30,7 +30,6 @@ import path from 'path'; import picocolors from 'picocolors'; import { CUSTOM_API_CALL_KEY, CUSTOM_API_CALL_NAME, CLI_DIR, inE2ETests } from '@/constants'; -import { isContainedWithin } from '@/utils/path-util'; @Service() export class LoadNodesAndCredentials { diff --git a/packages/cli/src/utils/__tests__/path-util.test.ts b/packages/cli/src/utils/__tests__/path-util.test.ts deleted file mode 100644 index d67e97f9e0058..0000000000000 --- a/packages/cli/src/utils/__tests__/path-util.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { isContainedWithin } from '../path-util'; - -describe('isContainedWithin', () => { - it('should return true when parent and child paths are the same', () => { - expect(isContainedWithin('/some/parent/folder', '/some/parent/folder')).toBe(true); - }); - - test.each([ - ['/some/parent/folder', '/some/parent/folder/subfolder/file.txt'], - ['/some/parent/folder', '/some/parent/folder/../folder/subfolder/file.txt'], - ['/some/parent/folder/', '/some/parent/folder/subfolder/file.txt'], - ['/some/parent/folder', '/some/parent/folder/subfolder/'], - ])('should return true for parent %s and child %s', (parent, child) => { - expect(isContainedWithin(parent, child)).toBe(true); - }); - - test.each([ - ['/some/parent/folder', '/some/other/folder/file.txt'], - ['/some/parent/folder', '/some/parent/folder_but_not_really'], - ['/one/path', '/another/path'], - ])('should return false for parent %s and child %s', (parent, child) => { - expect(isContainedWithin(parent, child)).toBe(false); - }); -}); diff --git a/packages/cli/src/utils/path-util.ts b/packages/cli/src/utils/path-util.ts deleted file mode 100644 index f42dc01890545..0000000000000 --- a/packages/cli/src/utils/path-util.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as path from 'node:path'; - -/** - * Checks if the given childPath is contained within the parentPath. Resolves - * the paths before comparing them, so that relative paths are also supported. - */ -export function isContainedWithin(parentPath: string, childPath: string): boolean { - parentPath = path.resolve(parentPath); - childPath = path.resolve(childPath); - - if (parentPath === childPath) { - return true; - } - - return childPath.startsWith(parentPath + path.sep); -} diff --git a/packages/core/src/execution-engine/node-execution-context/utils/file-system-helper-functions.ts b/packages/core/src/execution-engine/node-execution-context/utils/file-system-helper-functions.ts index 23153bb94d691..175f780a41e0a 100644 --- a/packages/core/src/execution-engine/node-execution-context/utils/file-system-helper-functions.ts +++ b/packages/core/src/execution-engine/node-execution-context/utils/file-system-helper-functions.ts @@ -1,9 +1,10 @@ +import { safeJoinPath } from '@n8n/backend-common'; import { Container } from '@n8n/di'; import type { FileSystemHelperFunctions, INode } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; import { createReadStream } from 'node:fs'; import { access as fsAccess, writeFile as fsWriteFile } from 'node:fs/promises'; -import { join, resolve } from 'node:path'; +import { resolve } from 'node:path'; import { BINARY_DATA_STORAGE_PATH, @@ -107,7 +108,7 @@ export const getFileSystemHelperFunctions = (node: INode): FileSystemHelperFunct }, getStoragePath() { - return join(Container.get(InstanceSettings).n8nFolder, `storage/${node.type}`); + return safeJoinPath(Container.get(InstanceSettings).n8nFolder, `storage/${node.type}`); }, async writeContentToFile(filePath, content, flag) { diff --git a/packages/core/src/nodes-loader/__tests__/directory-loader.test.ts b/packages/core/src/nodes-loader/__tests__/directory-loader.test.ts index 5d80b28ab576e..36634e516ff67 100644 --- a/packages/core/src/nodes-loader/__tests__/directory-loader.test.ts +++ b/packages/core/src/nodes-loader/__tests__/directory-loader.test.ts @@ -1,5 +1,6 @@ import { mock } from 'jest-mock-extended'; import type { + IconFile, ICredentialType, INodeType, INodeTypeDescription, @@ -133,6 +134,20 @@ describe('DirectoryLoader', () => { expect(mockNode2.description.iconUrl).toBe('icons/n8n-nodes-testing/dist/Node2/node2.svg'); }); + it('should throw error if node has icon not contained within the package directory', async () => { + mockFs.readFileSync.calledWith(`${directory}/package.json`).mockReturnValue(packageJson); + mockNode2.description.icon = { + light: 'file:../../../../../../evil' as IconFile, + dark: 'file:dark.svg', + }; + + const loader = new PackageDirectoryLoader(directory); + + await expect(loader.loadAll()).rejects.toThrow( + 'Icon path "../../../../evil" is not contained within', + ); + }); + it('should throw error when package.json is missing', async () => { mockFs.readFileSync.mockImplementationOnce(() => { throw new Error('ENOENT'); @@ -661,6 +676,23 @@ describe('DirectoryLoader', () => { expect(nodeWithIcon.description.icon).toBeUndefined(); }); + it('should error if icon path is not contained within the package directory', () => { + const loader = new CustomDirectoryLoader(directory); + const filePath = 'dist/Node1/Node1.node.js'; + + const nodeWithIcon = createNode('nodeWithIcon'); + nodeWithIcon.description.icon = { + light: 'file:../../../evil' as IconFile, + dark: 'file:dark.svg', + }; + + jest.spyOn(classLoader, 'loadClassInIsolation').mockReturnValueOnce(nodeWithIcon); + + expect(() => loader.loadNodeFromFile(filePath)).toThrow( + 'Icon path "../evil" is not contained within', + ); + }); + it('should skip node if not in includeNodes', () => { const loader = new CustomDirectoryLoader(directory, [], ['CUSTOM.other']); const filePath = 'dist/Node1/Node1.node.js'; diff --git a/packages/core/src/nodes-loader/directory-loader.ts b/packages/core/src/nodes-loader/directory-loader.ts index 677844936f1c9..3d4b985d20eb3 100644 --- a/packages/core/src/nodes-loader/directory-loader.ts +++ b/packages/core/src/nodes-loader/directory-loader.ts @@ -1,4 +1,4 @@ -import { Logger } from '@n8n/backend-common'; +import { isContainedWithin, Logger } from '@n8n/backend-common'; import { Container } from '@n8n/di'; import uniqBy from 'lodash/uniqBy'; import type { @@ -16,7 +16,7 @@ import type { IVersionedNodeType, KnownNodesAndCredentials, } from 'n8n-workflow'; -import { ApplicationError, isSubNodeType } from 'n8n-workflow'; +import { ApplicationError, isSubNodeType, UnexpectedError } from 'n8n-workflow'; import { realpathSync } from 'node:fs'; import * as path from 'path'; @@ -382,6 +382,13 @@ export abstract class DirectoryLoader { private getIconPath(icon: string, filePath: string) { const iconPath = path.join(path.dirname(filePath), icon.replace('file:', '')); + + if (!isContainedWithin(this.directory, path.join(this.directory, iconPath))) { + throw new UnexpectedError( + `Icon path "${iconPath}" is not contained within the package directory "${this.directory}"`, + ); + } + return `icons/${this.packageName}/${iconPath}`; } From de5e55b9f20eee50c5addb1ddef2c9639023c541 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 16 Jul 2025 02:31:18 +0100 Subject: [PATCH 2/3] fix: Fix issue with restricted file access order (#17329) --- .../file-system-helper-functions.test.ts | 30 +++++++ .../utils/file-system-helper-functions.ts | 80 +++++++++---------- 2 files changed, 68 insertions(+), 42 deletions(-) diff --git a/packages/core/src/execution-engine/node-execution-context/utils/__tests__/file-system-helper-functions.test.ts b/packages/core/src/execution-engine/node-execution-context/utils/__tests__/file-system-helper-functions.test.ts index d031fb4993e32..8972b1210ee4d 100644 --- a/packages/core/src/execution-engine/node-execution-context/utils/__tests__/file-system-helper-functions.test.ts +++ b/packages/core/src/execution-engine/node-execution-context/utils/__tests__/file-system-helper-functions.test.ts @@ -119,6 +119,36 @@ describe('isFilePathBlocked', () => { expect(isFilePathBlocked(invitePath)).toBe(true); expect(isFilePathBlocked(pwResetPath)).toBe(true); }); + + it('should block access to n8n files if restrict and block are set', () => { + const homeVarName = process.platform === 'win32' ? 'USERPROFILE' : 'HOME'; + const userHome = process.env.N8N_USER_FOLDER ?? process.env[homeVarName] ?? process.cwd(); + + process.env[RESTRICT_FILE_ACCESS_TO] = userHome; + process.env[BLOCK_FILE_ACCESS_TO_N8N_FILES] = 'true'; + const restrictedPath = instanceSettings.n8nFolder; + expect(isFilePathBlocked(restrictedPath)).toBe(true); + }); + + it('should allow access to parent folder if restrict and block are set', () => { + const homeVarName = process.platform === 'win32' ? 'USERPROFILE' : 'HOME'; + const userHome = process.env.N8N_USER_FOLDER ?? process.env[homeVarName] ?? process.cwd(); + + process.env[RESTRICT_FILE_ACCESS_TO] = userHome; + process.env[BLOCK_FILE_ACCESS_TO_N8N_FILES] = 'true'; + const restrictedPath = join(userHome, 'somefile.txt'); + expect(isFilePathBlocked(restrictedPath)).toBe(false); + }); + + it('should not block similar paths', () => { + const homeVarName = process.platform === 'win32' ? 'USERPROFILE' : 'HOME'; + const userHome = process.env.N8N_USER_FOLDER ?? process.env[homeVarName] ?? process.cwd(); + + process.env[RESTRICT_FILE_ACCESS_TO] = userHome; + process.env[BLOCK_FILE_ACCESS_TO_N8N_FILES] = 'true'; + const restrictedPath = join(userHome, '.n8n_x'); + expect(isFilePathBlocked(restrictedPath)).toBe(false); + }); }); describe('getFileSystemHelperFunctions', () => { diff --git a/packages/core/src/execution-engine/node-execution-context/utils/file-system-helper-functions.ts b/packages/core/src/execution-engine/node-execution-context/utils/file-system-helper-functions.ts index 175f780a41e0a..e575b298454ab 100644 --- a/packages/core/src/execution-engine/node-execution-context/utils/file-system-helper-functions.ts +++ b/packages/core/src/execution-engine/node-execution-context/utils/file-system-helper-functions.ts @@ -1,4 +1,4 @@ -import { safeJoinPath } from '@n8n/backend-common'; +import { isContainedWithin, safeJoinPath } from '@n8n/backend-common'; import { Container } from '@n8n/di'; import type { FileSystemHelperFunctions, INode } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; @@ -34,52 +34,17 @@ export function isFilePathBlocked(filePath: string): boolean { const resolvedFilePath = resolve(filePath); const blockFileAccessToN8nFiles = process.env[BLOCK_FILE_ACCESS_TO_N8N_FILES] !== 'false'; - //if allowed paths are defined, allow access only to those paths - if (allowedPaths.length) { - for (const path of allowedPaths) { - if (resolvedFilePath.startsWith(path)) { - return false; - } - } - + const restrictedPaths = blockFileAccessToN8nFiles ? getN8nRestrictedPaths() : []; + if ( + restrictedPaths.some((restrictedPath) => isContainedWithin(restrictedPath, resolvedFilePath)) + ) { return true; } - //restrict access to .n8n folder, ~/.cache/n8n/public, and other .env config related paths - if (blockFileAccessToN8nFiles) { - const { n8nFolder, staticCacheDir } = Container.get(InstanceSettings); - const restrictedPaths = [n8nFolder, staticCacheDir]; - - if (process.env[CONFIG_FILES]) { - restrictedPaths.push(...process.env[CONFIG_FILES].split(',')); - } - - if (process.env[CUSTOM_EXTENSION_ENV]) { - const customExtensionFolders = process.env[CUSTOM_EXTENSION_ENV].split(';'); - restrictedPaths.push(...customExtensionFolders); - } - - if (process.env[BINARY_DATA_STORAGE_PATH]) { - restrictedPaths.push(process.env[BINARY_DATA_STORAGE_PATH]); - } - - if (process.env[UM_EMAIL_TEMPLATES_INVITE]) { - restrictedPaths.push(process.env[UM_EMAIL_TEMPLATES_INVITE]); - } - - if (process.env[UM_EMAIL_TEMPLATES_PWRESET]) { - restrictedPaths.push(process.env[UM_EMAIL_TEMPLATES_PWRESET]); - } - - //check if the file path is restricted - for (const path of restrictedPaths) { - if (resolvedFilePath.startsWith(path)) { - return true; - } - } + if (allowedPaths.length) { + return !allowedPaths.some((allowedPath) => isContainedWithin(allowedPath, resolvedFilePath)); } - //path is not restricted return false; } @@ -120,3 +85,34 @@ export const getFileSystemHelperFunctions = (node: INode): FileSystemHelperFunct return await fsWriteFile(filePath, content, { encoding: 'binary', flag }); }, }); + +/** + * @returns The restricted paths for the n8n instance. + */ +function getN8nRestrictedPaths() { + const { n8nFolder, staticCacheDir } = Container.get(InstanceSettings); + const restrictedPaths = [n8nFolder, staticCacheDir]; + + if (process.env[CONFIG_FILES]) { + restrictedPaths.push(...process.env[CONFIG_FILES].split(',')); + } + + if (process.env[CUSTOM_EXTENSION_ENV]) { + const customExtensionFolders = process.env[CUSTOM_EXTENSION_ENV].split(';'); + restrictedPaths.push(...customExtensionFolders); + } + + if (process.env[BINARY_DATA_STORAGE_PATH]) { + restrictedPaths.push(process.env[BINARY_DATA_STORAGE_PATH]); + } + + if (process.env[UM_EMAIL_TEMPLATES_INVITE]) { + restrictedPaths.push(process.env[UM_EMAIL_TEMPLATES_INVITE]); + } + + if (process.env[UM_EMAIL_TEMPLATES_PWRESET]) { + restrictedPaths.push(process.env[UM_EMAIL_TEMPLATES_PWRESET]); + } + + return restrictedPaths; +} From 10e2690859c08b3862595a67c05da9932927936b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 12:10:19 +0200 Subject: [PATCH 3/3] :rocket: Release 1.101.3 (#17445) Co-authored-by: burivuhster <3273843+burivuhster@users.noreply.github.com> --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- packages/@n8n/backend-common/package.json | 2 +- packages/@n8n/backend-test-utils/package.json | 2 +- packages/@n8n/db/package.json | 2 +- packages/@n8n/task-runner/package.json | 2 +- packages/cli/package.json | 2 +- packages/core/package.json | 2 +- packages/node-dev/package.json | 2 +- 9 files changed, 17 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index daabef509b0ee..e128eae878da2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [1.101.3](https://github.com/n8n-io/n8n/compare/n8n@1.101.2...n8n@1.101.3) (2025-07-18) + + +### Bug Fixes + +* Fix issue with restricted file access order ([#17329](https://github.com/n8n-io/n8n/issues/17329)) ([de5e55b](https://github.com/n8n-io/n8n/commit/de5e55b9f20eee50c5addb1ddef2c9639023c541)) + + + ## [1.101.2](https://github.com/n8n-io/n8n/compare/n8n@1.101.1...n8n@1.101.2) (2025-07-11) diff --git a/package.json b/package.json index 2b683f31087ef..d421b3ec51518 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-monorepo", - "version": "1.101.2", + "version": "1.101.3", "private": true, "engines": { "node": ">=22.16", diff --git a/packages/@n8n/backend-common/package.json b/packages/@n8n/backend-common/package.json index 72c0ceac16271..31ce7028ab258 100644 --- a/packages/@n8n/backend-common/package.json +++ b/packages/@n8n/backend-common/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/backend-common", - "version": "0.11.0", + "version": "0.11.1", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", diff --git a/packages/@n8n/backend-test-utils/package.json b/packages/@n8n/backend-test-utils/package.json index c8a5870ba0c25..f0f881d56adb2 100644 --- a/packages/@n8n/backend-test-utils/package.json +++ b/packages/@n8n/backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/backend-test-utils", - "version": "0.4.0", + "version": "0.4.1", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", diff --git a/packages/@n8n/db/package.json b/packages/@n8n/db/package.json index 4a861f82e49bd..2f09f4dfe990c 100644 --- a/packages/@n8n/db/package.json +++ b/packages/@n8n/db/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/db", - "version": "0.12.0", + "version": "0.12.1", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", diff --git a/packages/@n8n/task-runner/package.json b/packages/@n8n/task-runner/package.json index 7c8d87a9b4be8..3eaf11e871795 100644 --- a/packages/@n8n/task-runner/package.json +++ b/packages/@n8n/task-runner/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/task-runner", - "version": "1.37.0", + "version": "1.37.1", "scripts": { "clean": "rimraf dist .turbo", "start": "node dist/start.js", diff --git a/packages/cli/package.json b/packages/cli/package.json index be68782c8a496..54dc6a8f3ad2e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "1.101.2", + "version": "1.101.3", "description": "n8n Workflow Automation Tool", "main": "dist/index", "types": "dist/index.d.ts", diff --git a/packages/core/package.json b/packages/core/package.json index 96f5291513444..4cfbff730c971 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "1.100.0", + "version": "1.100.1", "description": "Core functionality of n8n", "main": "dist/index", "types": "dist/index.d.ts", diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index d212ff23199dc..20842710cd98c 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "1.99.0", + "version": "1.99.1", "description": "CLI to simplify n8n credentials/node development", "main": "dist/src/index", "types": "dist/src/index.d.ts",