fix: Fix issue with restricted file access order (#17329)

This commit is contained in:
Jon
2025-07-16 02:31:18 +01:00
committed by GitHub
parent 56ef0dbb77
commit e1805fb14f
2 changed files with 68 additions and 42 deletions

View File

@@ -119,6 +119,36 @@ describe('isFilePathBlocked', () => {
expect(isFilePathBlocked(invitePath)).toBe(true); expect(isFilePathBlocked(invitePath)).toBe(true);
expect(isFilePathBlocked(pwResetPath)).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', () => { describe('getFileSystemHelperFunctions', () => {

View File

@@ -1,4 +1,4 @@
import { safeJoinPath } from '@n8n/backend-common'; import { isContainedWithin, safeJoinPath } from '@n8n/backend-common';
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import type { FileSystemHelperFunctions, INode } from 'n8n-workflow'; import type { FileSystemHelperFunctions, INode } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow';
@@ -34,52 +34,17 @@ export function isFilePathBlocked(filePath: string): boolean {
const resolvedFilePath = resolve(filePath); const resolvedFilePath = resolve(filePath);
const blockFileAccessToN8nFiles = process.env[BLOCK_FILE_ACCESS_TO_N8N_FILES] !== 'false'; const blockFileAccessToN8nFiles = process.env[BLOCK_FILE_ACCESS_TO_N8N_FILES] !== 'false';
//if allowed paths are defined, allow access only to those paths const restrictedPaths = blockFileAccessToN8nFiles ? getN8nRestrictedPaths() : [];
if (
restrictedPaths.some((restrictedPath) => isContainedWithin(restrictedPath, resolvedFilePath))
) {
return true;
}
if (allowedPaths.length) { if (allowedPaths.length) {
for (const path of allowedPaths) { return !allowedPaths.some((allowedPath) => isContainedWithin(allowedPath, resolvedFilePath));
if (resolvedFilePath.startsWith(path)) {
return false;
}
} }
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;
}
}
}
//path is not restricted
return false; return false;
} }
@@ -120,3 +85,34 @@ export const getFileSystemHelperFunctions = (node: INode): FileSystemHelperFunct
return await fsWriteFile(filePath, content, { encoding: 'binary', flag }); 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;
}