VYPR
High severityNVD Advisory· Published Oct 30, 2025· Updated Oct 31, 2025

n8n Vulnerable to Remote Code Execution via Git Node Pre-Commit Hook

CVE-2025-62726

Description

n8n is an open source workflow automation platform. Prior to 1.113.0, a remote code execution vulnerability exists in the Git Node component available in both Cloud and Self-Hosted versions of n8n. When a malicious actor clones a remote repository containing a pre-commit hook, the subsequent use of the Commit operation in the Git Node can inadvertently trigger the hook’s execution. This allows attackers to execute arbitrary code within the n8n environment, potentially compromising the system and any connected credentials or workflows. This vulnerability is fixed in 1.113.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
n8nnpm
< 1.113.01.113.0

Affected products

1

Patches

1
5bf3db5ba84d

feat: Add N8N_GIT_NODE_DISABLE_BARE_REPOS environment variable to allow users to disable bare repositories in Git Node (#19559)

https://github.com/n8n-io/n8nRomanDavydchukSep 18, 2025via ghsa
7 files changed · +180 0
  • packages/cli/BREAKING-CHANGES.md+10 0 modified
    @@ -2,6 +2,16 @@
     
     This list shows all the versions which include breaking changes and how to upgrade.
     
    +# 1.113.0
    +
    +### What changed?
    +
    +Support for bare repositories in Git Node was dropped in the cloud version of n8n due to security reasons. Also, an environment variable `N8N_GIT_NODE_DISABLE_BARE_REPOS` was added that allows self-hosted users to disable bare repositories as well.
    +
    +### When is action necessary?
    +
    +If you have workflows that use the Git Node and work with bare git repositories.
    +
     # 1.109.0
     
     ### What changed?
    
  • packages/cli/src/deprecation/deprecation.service.ts+6 0 modified
    @@ -109,6 +109,12 @@ export class DeprecationService {
     				'The default value of N8N_BLOCK_ENV_ACCESS_IN_NODE will be changed from false to true in a future version. If you need to access environment variables from the Code Node or from expressions, please set N8N_BLOCK_ENV_ACCESS_IN_NODE=false. Learn more: https://docs.n8n.io/hosting/configuration/environment-variables/security/',
     			checkValue: (value: string | undefined) => value === undefined || value === '',
     		},
    +		{
    +			envVar: 'N8N_GIT_NODE_DISABLE_BARE_REPOS',
    +			message:
    +				'Support for bare repositories in the Git Node will be removed in a future version due to security concerns. If you are not using bare repositories in the Git Node, please set N8N_GIT_NODE_DISABLE_BARE_REPOS=true. Learn more: https://docs.n8n.io/hosting/configuration/environment-variables/security/',
    +			checkValue: (value: string | undefined) => value === undefined || value === '',
    +		},
     	];
     
     	/** Runtime state of deprecation-related env vars. */
    
  • packages/cli/src/deprecation/__tests__/deprecation.service.test.ts+28 0 modified
    @@ -20,6 +20,7 @@ describe('DeprecationService', () => {
     		// this test suite.
     		process.env = {
     			N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false',
    +			N8N_GIT_NODE_DISABLE_BARE_REPOS: 'false',
     		};
     
     		jest.resetAllMocks();
    @@ -140,6 +141,7 @@ describe('DeprecationService', () => {
     			process.env = {
     				N8N_RUNNERS_ENABLED: 'true',
     				N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false',
    +				N8N_GIT_NODE_DISABLE_BARE_REPOS: 'false',
     			};
     
     			jest.spyOn(config, 'getEnv').mockImplementation((key) => {
    @@ -239,6 +241,7 @@ describe('DeprecationService', () => {
     		beforeEach(() => {
     			process.env = {
     				N8N_RUNNERS_ENABLED: 'true',
    +				N8N_GIT_NODE_DISABLE_BARE_REPOS: 'false',
     			};
     
     			jest.resetAllMocks();
    @@ -259,4 +262,29 @@ describe('DeprecationService', () => {
     			},
     		);
     	});
    +
    +	describe('N8N_GIT_NODE_DISABLE_BARE_REPOS', () => {
    +		beforeEach(() => {
    +			process.env = {
    +				N8N_RUNNERS_ENABLED: 'true',
    +				N8N_BLOCK_ENV_ACCESS_IN_NODE: 'false',
    +			};
    +			jest.resetAllMocks();
    +		});
    +
    +		test('should warn when N8N_GIT_NODE_DISABLE_BARE_REPOS is not set', () => {
    +			delete process.env.N8N_GIT_NODE_DISABLE_BARE_REPOS;
    +			deprecationService.warn();
    +			expect(logger.warn).toHaveBeenCalled();
    +		});
    +
    +		test.each(['false', 'true'])(
    +			'should not warn when N8N_GIT_NODE_DISABLE_BARE_REPOS is %s',
    +			(value) => {
    +				process.env.N8N_GIT_NODE_DISABLE_BARE_REPOS = value;
    +				deprecationService.warn();
    +				expect(logger.warn).not.toHaveBeenCalled();
    +			},
    +		);
    +	});
     });
    
  • packages/@n8n/config/src/configs/security.config.ts+6 0 modified
    @@ -45,4 +45,10 @@ export class SecurityConfig {
     	 */
     	@Env('N8N_INSECURE_DISABLE_WEBHOOK_IFRAME_SANDBOX')
     	disableWebhookHtmlSandboxing: boolean = false;
    +
    +	/**
    +	 * Whether to disable bare repositories support in the Git node.
    +	 */
    +	@Env('N8N_GIT_NODE_DISABLE_BARE_REPOS')
    +	disableBareRepos: boolean = false;
     }
    
  • packages/@n8n/config/test/config.test.ts+1 0 modified
    @@ -307,6 +307,7 @@ describe('GlobalConfig', () => {
     			contentSecurityPolicy: '{}',
     			contentSecurityPolicyReportOnly: false,
     			disableWebhookHtmlSandboxing: false,
    +			disableBareRepos: false,
     		},
     		executions: {
     			timeout: -1,
    
  • packages/nodes-base/nodes/Git/Git.node.ts+12 0 modified
    @@ -20,6 +20,8 @@ import {
     	switchBranchFields,
     	tagFields,
     } from './descriptions';
    +import { Container } from '@n8n/di';
    +import { DeploymentConfig, SecurityConfig } from '@n8n/config';
     
     export class Git implements INodeType {
     	description: INodeTypeDescription = {
    @@ -291,8 +293,18 @@ export class Git implements INodeType {
     					}
     				}
     
    +				const gitConfig: string[] = [];
    +				const deploymentConfig = Container.get(DeploymentConfig);
    +				const isCloud = deploymentConfig.type === 'cloud';
    +				const securityConfig = Container.get(SecurityConfig);
    +				const disableBareRepos = securityConfig.disableBareRepos;
    +				if (isCloud || disableBareRepos) {
    +					gitConfig.push('safe.bareRepository=explicit');
    +				}
    +
     				const gitOptions: Partial<SimpleGitOptions> = {
     					baseDir: repositoryPath,
    +					config: gitConfig,
     				};
     
     				const git: SimpleGit = simpleGit(gitOptions)
    
  • packages/nodes-base/nodes/Git/__test__/Git.node.test.ts+117 0 added
    @@ -0,0 +1,117 @@
    +import { DeploymentConfig, SecurityConfig } from '@n8n/config';
    +import { Container } from '@n8n/di';
    +import { mock } from 'jest-mock-extended';
    +import type { IExecuteFunctions } from 'n8n-workflow';
    +import type { SimpleGit } from 'simple-git';
    +import simpleGit from 'simple-git';
    +
    +import { Git } from '../Git.node';
    +
    +const mockGit = {
    +	log: jest.fn(),
    +	env: jest.fn().mockReturnThis(),
    +};
    +
    +jest.mock('simple-git');
    +const mockSimpleGit = simpleGit as jest.MockedFunction<typeof simpleGit>;
    +mockSimpleGit.mockReturnValue(mockGit as unknown as SimpleGit);
    +
    +describe('Git Node', () => {
    +	let gitNode: Git;
    +	let executeFunctions: jest.Mocked<IExecuteFunctions>;
    +	let deploymentConfig: jest.Mocked<DeploymentConfig>;
    +	let securityConfig: jest.Mocked<SecurityConfig>;
    +
    +	beforeEach(() => {
    +		jest.clearAllMocks();
    +
    +		deploymentConfig = mock<DeploymentConfig>({
    +			type: 'default',
    +		});
    +		securityConfig = mock<SecurityConfig>({
    +			disableBareRepos: false,
    +		});
    +		Container.set(DeploymentConfig, deploymentConfig);
    +		Container.set(SecurityConfig, securityConfig);
    +
    +		executeFunctions = mock<IExecuteFunctions>({
    +			getInputData: jest.fn().mockReturnValue([{ json: {} }]),
    +			getNodeParameter: jest.fn(),
    +			helpers: {
    +				returnJsonArray: jest
    +					.fn()
    +					.mockImplementation((data: unknown[]) => data.map((item: unknown) => ({ json: item }))),
    +			},
    +		});
    +		executeFunctions.getNodeParameter.mockImplementation((name: string) => {
    +			switch (name) {
    +				case 'operation':
    +					return 'log';
    +				case 'repositoryPath':
    +					return '/tmp/test-repo';
    +				case 'options':
    +					return {};
    +				default:
    +					return '';
    +			}
    +		});
    +
    +		mockGit.log.mockResolvedValue({ all: [] });
    +
    +		gitNode = new Git();
    +	});
    +
    +	describe('Bare Repository Configuration', () => {
    +		it('should add safe.bareRepository=explicit when deployment type is cloud', async () => {
    +			deploymentConfig.type = 'cloud';
    +			securityConfig.disableBareRepos = false;
    +
    +			await gitNode.execute.call(executeFunctions);
    +
    +			expect(mockSimpleGit).toHaveBeenCalledWith(
    +				expect.objectContaining({
    +					config: ['safe.bareRepository=explicit'],
    +				}),
    +			);
    +		});
    +
    +		it('should add safe.bareRepository=explicit when disableBareRepos is true', async () => {
    +			deploymentConfig.type = 'default';
    +			securityConfig.disableBareRepos = true;
    +
    +			await gitNode.execute.call(executeFunctions);
    +
    +			expect(mockSimpleGit).toHaveBeenCalledWith(
    +				expect.objectContaining({
    +					config: ['safe.bareRepository=explicit'],
    +				}),
    +			);
    +		});
    +
    +		it('should add safe.bareRepository=explicit when both cloud and disableBareRepos are true', async () => {
    +			deploymentConfig.type = 'cloud';
    +			securityConfig.disableBareRepos = true;
    +
    +			await gitNode.execute.call(executeFunctions);
    +
    +			expect(mockSimpleGit).toHaveBeenCalledWith(
    +				expect.objectContaining({
    +					config: ['safe.bareRepository=explicit'],
    +				}),
    +			);
    +		});
    +
    +		it('should not add safe.bareRepository=explicit when neither cloud nor disableBareRepos is true', async () => {
    +			deploymentConfig.type = 'default';
    +			securityConfig.disableBareRepos = false;
    +
    +			await gitNode.execute.call(executeFunctions);
    +
    +			expect(mockSimpleGit).toHaveBeenCalledWith(
    +				expect.objectContaining({
    +					config: [],
    +				}),
    +			);
    +		});
    +	});
    +});
    

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

News mentions

0

No linked articles in our index yet.