Critical severityNVD Advisory· Published Feb 4, 2026· Updated Feb 5, 2026
n8n Has an Expression Escape Vulnerability Leading to RCE
CVE-2026-25049
Description
n8n is an open source workflow automation platform. Prior to versions 1.123.17 and 2.5.2, an authenticated user with permission to create or modify workflows could abuse crafted expressions in workflow parameters to trigger unintended system command execution on the host running n8n. This issue has been patched in versions 1.123.17 and 2.5.2.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
n8nnpm | < 1.123.17 | 1.123.17 |
n8nnpm | >= 2.0.0, < 2.5.2 | 2.5.2 |
Affected products
1Patches
27860896909b3fix(Git Node): Clean up URLs returned from config (#24754)
3 files changed · +179 −7
packages/nodes-base/nodes/Git/GenericFunctions.ts+43 −0 added@@ -0,0 +1,43 @@ +import type { ConfigListSummary } from 'simple-git'; + +const REMOTE_ORIGIN_URL_KEY = 'remote.origin.url'; + +const REMOTE_ORIGIN_PUSH_URL_KEY = 'remote.origin.pushurl'; + +function sanitizeUrl(url: string): string { + const urlObj = new URL(url); + urlObj.username = ''; + urlObj.password = ''; + return urlObj.toString(); +} + +export function mapGitConfigList(config: ConfigListSummary) { + const data = []; + for (const fileName of Object.keys(config.values)) { + let remoteOriginUrl = config.values[fileName][REMOTE_ORIGIN_URL_KEY]; + if (remoteOriginUrl) { + if (Array.isArray(remoteOriginUrl)) { + remoteOriginUrl = remoteOriginUrl.map(sanitizeUrl); + } else { + remoteOriginUrl = sanitizeUrl(remoteOriginUrl); + } + } + + let remoteOriginPushUrl = config.values[fileName][REMOTE_ORIGIN_PUSH_URL_KEY]; + if (remoteOriginPushUrl) { + if (Array.isArray(remoteOriginPushUrl)) { + remoteOriginPushUrl = remoteOriginPushUrl.map(sanitizeUrl); + } else { + remoteOriginPushUrl = sanitizeUrl(remoteOriginPushUrl); + } + } + + data.push({ + _file: fileName, + ...config.values[fileName], + [REMOTE_ORIGIN_URL_KEY]: remoteOriginUrl, + [REMOTE_ORIGIN_PUSH_URL_KEY]: remoteOriginPushUrl, + }); + } + return data; +}
packages/nodes-base/nodes/Git/Git.node.ts+2 −7 modified@@ -28,6 +28,7 @@ import { } from './descriptions'; import { Container } from '@n8n/di'; import { DeploymentConfig, SecurityConfig } from '@n8n/config'; +import { mapGitConfigList } from './GenericFunctions'; export class Git implements INodeType { description: INodeTypeDescription = { @@ -563,13 +564,7 @@ export class Git implements INodeType { const config = await git.listConfig(); - const data = []; - for (const fileName of Object.keys(config.values)) { - data.push({ - _file: fileName, - ...config.values[fileName], - }); - } + const data = mapGitConfigList(config); returnItems.push( ...this.helpers.returnJsonArray(data).map((item) => {
packages/nodes-base/nodes/Git/__test__/GenericFunctions.test.ts+134 −0 added@@ -0,0 +1,134 @@ +import { mockDeep } from 'jest-mock-extended'; +import type { ConfigListSummary } from 'simple-git'; + +import { mapGitConfigList } from '../GenericFunctions'; + +describe('GenericFunctions', () => { + describe('mapGitConfigList', () => { + it('should map the git config list', () => { + const config = mockDeep<ConfigListSummary>({ + values: { + '.git/config': { + 'user.name': 'test', + 'core.autocrlf': 'true', + 'remote.origin.url': undefined, + 'remote.origin.pushurl': undefined, + }, + '/other/config': { + 'user.name': 'other', + 'core.autocrlf': 'false', + 'remote.origin.url': undefined, + 'remote.origin.pushurl': undefined, + }, + }, + }); + + const result = mapGitConfigList(config); + + expect(result).toEqual([ + { + _file: '.git/config', + 'user.name': 'test', + 'core.autocrlf': 'true', + }, + { + _file: '/other/config', + 'user.name': 'other', + 'core.autocrlf': 'false', + }, + ]); + }); + + it('should sanitize the remote origin url', () => { + const config = mockDeep<ConfigListSummary>({ + values: { + '.git/config': { + 'remote.origin.url': 'https://user:password@github.com/test/test.git', + 'remote.origin.pushurl': undefined, + }, + }, + }); + + const result = mapGitConfigList(config); + + expect(result).toEqual([ + { + _file: '.git/config', + 'remote.origin.url': 'https://github.com/test/test.git', + }, + ]); + }); + + it('should sanitize the remote origin urls', () => { + const config = mockDeep<ConfigListSummary>({ + values: { + '.git/config': { + 'remote.origin.url': [ + 'https://user:password@github.com/test/test.git', + 'https://user:password@github.com/test/other.git', + ], + 'remote.origin.pushurl': undefined, + }, + }, + }); + + const result = mapGitConfigList(config); + + expect(result).toEqual([ + { + _file: '.git/config', + 'remote.origin.url': [ + 'https://github.com/test/test.git', + 'https://github.com/test/other.git', + ], + }, + ]); + }); + + it('should sanitize the remote origin push url', () => { + const config = mockDeep<ConfigListSummary>({ + values: { + '.git/config': { + 'remote.origin.pushurl': 'https://user:password@github.com/test/test.git', + 'remote.origin.url': undefined, + }, + }, + }); + + const result = mapGitConfigList(config); + + expect(result).toEqual([ + { + _file: '.git/config', + 'remote.origin.pushurl': 'https://github.com/test/test.git', + }, + ]); + }); + + it('should sanitize the remote origin push urls', () => { + const config = mockDeep<ConfigListSummary>({ + values: { + '.git/config': { + 'remote.origin.pushurl': [ + 'https://user:password@github.com/test/test.git', + 'https://user:password@github.com/test/other.git', + ], + 'remote.origin.url': undefined, + }, + }, + }); + + const result = mapGitConfigList(config); + + expect(result).toEqual([ + { + _file: '.git/config', + 'remote.origin.pushurl': [ + 'https://github.com/test/test.git', + 'https://github.com/test/other.git', + ], + }, + ]); + }); + }); +});
936c06cfc1adfix(Git Node): Clean up URLs returned from config (#24713)
3 files changed · +179 −8
packages/nodes-base/nodes/Git/GenericFunctions.ts+43 −0 modified@@ -1,5 +1,6 @@ import type { INode } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; +import type { ConfigListSummary } from 'simple-git'; /** * Validates a git reference to prevent command injection attacks @@ -40,3 +41,45 @@ export function validateGitReference(reference: string, node: INode): void { ); } } + +const REMOTE_ORIGIN_URL_KEY = 'remote.origin.url'; + +const REMOTE_ORIGIN_PUSH_URL_KEY = 'remote.origin.pushurl'; + +function sanitizeUrl(url: string): string { + const urlObj = new URL(url); + urlObj.username = ''; + urlObj.password = ''; + return urlObj.toString(); +} + +export function mapGitConfigList(config: ConfigListSummary) { + const data = []; + for (const fileName of Object.keys(config.values)) { + let remoteOriginUrl = config.values[fileName][REMOTE_ORIGIN_URL_KEY]; + if (remoteOriginUrl) { + if (Array.isArray(remoteOriginUrl)) { + remoteOriginUrl = remoteOriginUrl.map(sanitizeUrl); + } else { + remoteOriginUrl = sanitizeUrl(remoteOriginUrl); + } + } + + let remoteOriginPushUrl = config.values[fileName][REMOTE_ORIGIN_PUSH_URL_KEY]; + if (remoteOriginPushUrl) { + if (Array.isArray(remoteOriginPushUrl)) { + remoteOriginPushUrl = remoteOriginPushUrl.map(sanitizeUrl); + } else { + remoteOriginPushUrl = sanitizeUrl(remoteOriginPushUrl); + } + } + + data.push({ + _file: fileName, + ...config.values[fileName], + [REMOTE_ORIGIN_URL_KEY]: remoteOriginUrl, + [REMOTE_ORIGIN_PUSH_URL_KEY]: remoteOriginPushUrl, + }); + } + return data; +}
packages/nodes-base/nodes/Git/Git.node.ts+2 −8 modified@@ -29,7 +29,7 @@ import { switchBranchFields, tagFields, } from './descriptions'; -import { validateGitReference } from './GenericFunctions'; +import { mapGitConfigList, validateGitReference } from './GenericFunctions'; export class Git implements INodeType { description: INodeTypeDescription = { @@ -623,13 +623,7 @@ export class Git implements INodeType { const config = await git.listConfig(); - const data = []; - for (const fileName of Object.keys(config.values)) { - data.push({ - _file: fileName, - ...config.values[fileName], - }); - } + const data = mapGitConfigList(config); returnItems.push( ...this.helpers.returnJsonArray(data).map((item) => {
packages/nodes-base/nodes/Git/__test__/GenericFunctions.test.ts+134 −0 added@@ -0,0 +1,134 @@ +import { mockDeep } from 'jest-mock-extended'; +import type { ConfigListSummary } from 'simple-git'; + +import { mapGitConfigList } from '../GenericFunctions'; + +describe('GenericFunctions', () => { + describe('mapGitConfigList', () => { + it('should map the git config list', () => { + const config = mockDeep<ConfigListSummary>({ + values: { + '.git/config': { + 'user.name': 'test', + 'core.autocrlf': 'true', + 'remote.origin.url': undefined, + 'remote.origin.pushurl': undefined, + }, + '/other/config': { + 'user.name': 'other', + 'core.autocrlf': 'false', + 'remote.origin.url': undefined, + 'remote.origin.pushurl': undefined, + }, + }, + }); + + const result = mapGitConfigList(config); + + expect(result).toEqual([ + { + _file: '.git/config', + 'user.name': 'test', + 'core.autocrlf': 'true', + }, + { + _file: '/other/config', + 'user.name': 'other', + 'core.autocrlf': 'false', + }, + ]); + }); + + it('should sanitize the remote origin url', () => { + const config = mockDeep<ConfigListSummary>({ + values: { + '.git/config': { + 'remote.origin.url': 'https://user:password@github.com/test/test.git', + 'remote.origin.pushurl': undefined, + }, + }, + }); + + const result = mapGitConfigList(config); + + expect(result).toEqual([ + { + _file: '.git/config', + 'remote.origin.url': 'https://github.com/test/test.git', + }, + ]); + }); + + it('should sanitize the remote origin urls', () => { + const config = mockDeep<ConfigListSummary>({ + values: { + '.git/config': { + 'remote.origin.url': [ + 'https://user:password@github.com/test/test.git', + 'https://user:password@github.com/test/other.git', + ], + 'remote.origin.pushurl': undefined, + }, + }, + }); + + const result = mapGitConfigList(config); + + expect(result).toEqual([ + { + _file: '.git/config', + 'remote.origin.url': [ + 'https://github.com/test/test.git', + 'https://github.com/test/other.git', + ], + }, + ]); + }); + + it('should sanitize the remote origin push url', () => { + const config = mockDeep<ConfigListSummary>({ + values: { + '.git/config': { + 'remote.origin.pushurl': 'https://user:password@github.com/test/test.git', + 'remote.origin.url': undefined, + }, + }, + }); + + const result = mapGitConfigList(config); + + expect(result).toEqual([ + { + _file: '.git/config', + 'remote.origin.pushurl': 'https://github.com/test/test.git', + }, + ]); + }); + + it('should sanitize the remote origin push urls', () => { + const config = mockDeep<ConfigListSummary>({ + values: { + '.git/config': { + 'remote.origin.pushurl': [ + 'https://user:password@github.com/test/test.git', + 'https://user:password@github.com/test/other.git', + ], + 'remote.origin.url': undefined, + }, + }, + }); + + const result = mapGitConfigList(config); + + expect(result).toEqual([ + { + _file: '.git/config', + 'remote.origin.pushurl': [ + 'https://github.com/test/test.git', + 'https://github.com/test/other.git', + ], + }, + ]); + }); + }); +});
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-6cqr-8cfr-67f8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-25049ghsaADVISORY
- github.com/n8n-io/n8n/commit/7860896909b3d42993a36297f053d2b0e633235dghsax_refsource_MISCWEB
- github.com/n8n-io/n8n/commit/936c06cfc1ad269a89e8ef7f8ac79c104436d54bghsax_refsource_MISCWEB
- github.com/n8n-io/n8n/security/advisories/GHSA-6cqr-8cfr-67f8ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.