From ae17e8ec618db2dc7668aa178eee6e193d131150 Mon Sep 17 00:00:00 2001 From: Marc Littlemore Date: Thu, 29 Jan 2026 08:58:00 +0000 Subject: [PATCH] fix(core): Log warning where file values contain white space at the start or end (e.g. new lines) (#24442) --- packages/@n8n/config/src/decorators.ts | 10 ++- packages/@n8n/config/test/config.test.ts | 18 ++++++ packages/@n8n/config/test/decorators.test.ts | 64 ++++++++++++++++++++ packages/cli/src/config/index.ts | 5 ++ 4 files changed, 96 insertions(+), 1 deletion(-) diff --git a/packages/@n8n/config/src/decorators.ts b/packages/@n8n/config/src/decorators.ts index 06b1574984d..dcf2887a78d 100644 --- a/packages/@n8n/config/src/decorators.ts +++ b/packages/@n8n/config/src/decorators.ts @@ -21,7 +21,15 @@ const readEnv = (envName: string) => { // Read the value from a file, if "_FILE" environment variable is defined const filePath = process.env[`${envName}_FILE`]; - if (filePath) return readFileSync(filePath, 'utf8'); + if (filePath) { + const value = readFileSync(filePath, 'utf8'); + if (value !== value.trim()) { + console.warn( + `[n8n] Warning: The file specified by ${envName}_FILE contains leading or trailing whitespace, which may cause authentication failures.`, + ); + } + return value; + } return undefined; }; diff --git a/packages/@n8n/config/test/config.test.ts b/packages/@n8n/config/test/config.test.ts index 4bea469e041..3bf64875e35 100644 --- a/packages/@n8n/config/test/config.test.ts +++ b/packages/@n8n/config/test/config.test.ts @@ -516,6 +516,24 @@ describe('GlobalConfig', () => { expect(mockFs.readFileSync).toHaveBeenCalled(); }); + it('should warn when _FILE env variable value contains whitespace', () => { + const passwordFile = '/path/to/postgres/password'; + process.env = { + DB_POSTGRESDB_PASSWORD_FILE: passwordFile, + }; + mockFs.readFileSync + .calledWith(passwordFile, 'utf8') + .mockReturnValueOnce('password-from-file\n'); + + const config = Container.get(GlobalConfig); + expect(config.database.postgresdb.password).toBe('password-from-file'); + expect(consoleWarnMock).toHaveBeenCalledWith( + expect.stringContaining( + 'DB_POSTGRESDB_PASSWORD_FILE contains leading or trailing whitespace', + ), + ); + }); + it('should handle invalid numbers', () => { process.env = { DB_LOGGING_MAX_EXECUTION_TIME: 'abcd', diff --git a/packages/@n8n/config/test/decorators.test.ts b/packages/@n8n/config/test/decorators.test.ts index a346b442e0c..a9010be36eb 100644 --- a/packages/@n8n/config/test/decorators.test.ts +++ b/packages/@n8n/config/test/decorators.test.ts @@ -1,10 +1,22 @@ import { Container } from '@n8n/di'; +import fs from 'fs'; import { Config, Env } from '../src/decorators'; +jest.mock('fs'); +const mockFs = jest.mocked(fs); + describe('decorators', () => { + const originalEnv = process.env; + beforeEach(() => { Container.reset(); + process.env = {}; + jest.clearAllMocks(); + }); + + afterEach(() => { + process.env = originalEnv; }); it('should throw when explicit typing is missing', () => { @@ -19,4 +31,56 @@ describe('decorators', () => { 'Invalid decorator metadata on key "value" on InvalidConfig\n Please use explicit typing on all config fields', ); }); + + it('should read value from _FILE env variable', () => { + const filePath = '/path/to/secret'; + process.env.TEST_VALUE_FILE = filePath; + mockFs.readFileSync.mockReturnValueOnce('secret-value'); + + @Config + class TestConfig { + @Env('TEST_VALUE') + value: string = 'default'; + } + + const config = Container.get(TestConfig); + expect(config.value).toBe('secret-value'); + expect(mockFs.readFileSync).toHaveBeenCalledWith(filePath, 'utf8'); + }); + + it('should warn when _FILE env variable value contains whitespace', () => { + const filePath = '/path/to/secret'; + process.env.TEST_VALUE_FILE = filePath; + mockFs.readFileSync.mockReturnValueOnce('secret-value\n'); + const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + + @Config + class TestConfig { + @Env('TEST_VALUE') + value: string = 'default'; + } + + const config = Container.get(TestConfig); + expect(config.value).toBe('secret-value'); + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining('TEST_VALUE_FILE contains leading or trailing whitespace'), + ); + consoleWarnSpy.mockRestore(); + }); + + it('should prefer direct env variable over _FILE variant', () => { + const filePath = '/path/to/secret'; + process.env.TEST_VALUE = 'direct-value'; + process.env.TEST_VALUE_FILE = filePath; + + @Config + class TestConfig { + @Env('TEST_VALUE') + value: string = 'default'; + } + + const config = Container.get(TestConfig); + expect(config.value).toBe('direct-value'); + expect(mockFs.readFileSync).not.toHaveBeenCalled(); + }); }); diff --git a/packages/cli/src/config/index.ts b/packages/cli/src/config/index.ts index f7f6fd53719..ed2ebcc0de5 100644 --- a/packages/cli/src/config/index.ts +++ b/packages/cli/src/config/index.ts @@ -49,6 +49,11 @@ if (!inE2ETests && !inTest) { } throw error; } + if (value !== value.trim()) { + console.warn( + `[n8n] Warning: The file specified by ${envName} contains leading or trailing whitespace, which may cause authentication failures.`, + ); + } config.set(key, value); } }