SolutionsCraft
← Automation

Automation · 6 min read

When n8n's $fromAI Becomes an Escape Hatch

$fromAI() is the little function that makes n8n's AI Agent tools feel almost magic: instead of hardcoding a value in a tool's parameters, you write something like {{ $fromAI('city', 'the city the user asked about', 'string') }} and let the agent fill it in at call time based on what the user actually said. n8n's bi-weekly security update on 20 August 2026 disclosed that this exact helper had a hole in it. A crafted placeholder name could leak a live reference to one of n8n's own host prototypes, and from there, an attacker could walk the prototype chain up to the Function constructor and run arbitrary code as the n8n process. Rated High with a CVSS 4.0 score of 8.7, tracked as GHSA-9x83-43r8-5hwc.

If that sounds familiar, it should. It's a different bug from the expression-sandbox escape covered here in July (GHSA-gv7g-jm28-cr3m), but the shape of the problem is the same: code that's supposed to run inside a sandbox finds a crack in the wall.

This walkthrough is for self-hosted instances you manage yourself, Docker or npm/pnpm. If you're on n8n Cloud, patching is handled for you.

Who's affected

Like most expression-sandbox bugs, this one needs an authenticated account with permission to build or edit workflows, so it's not a drive-by, unauthenticated attack. But the specific surface here is narrower than the July bug: exploitation goes through $fromAI(), which only exists inside tool parameters wired up to an AI Agent node. If your instance doesn't use AI Agent workflows with tool nodes at all, this particular door isn't open to you, though you should still patch, since it costs nothing and the next advisory might not be so forgiving.

If you do run AI Agent tools, the risk is real: anyone who can add or edit a tool node (again, not just admins) could craft a malicious $fromAI() placeholder, and a successful exploit runs commands as the n8n process itself, which means it can reach N8N_ENCRYPTION_KEY and every credential n8n has stored behind it.

Affected versions: anything below 2.35.4, the 2.36.02.36.1 range, and anything below 1.123.73 on the legacy 1.x maintenance train. Patched versions: 2.35.4, 2.36.2, and 1.123.73, each patched within its own train.

Blip · Confused

A parameter that's supposed to describe itself to an AI model isn't supposed to describe a way into the host filesystem. And yet.

Before you start

You'll need shell access to the machine (or container) running n8n, so you can check its version and, if needed, pull a new image or run an npm install.

Step 1: Check your installed version

npm/pnpm install:

n8n --version

Docker:

docker exec <container-name> n8n --version

Step 2: Run it against the affected ranges

n8n currently keeps three maintenance trains alive at once: 1.123.x for teams still on the pre-2.0 line, plus the current and previous 2.x minors (2.35.x and 2.36.x). That means eyeballing one version range against your version string isn't enough here, so this script checks all three and exits non-zero if you're exposed:

#!/usr/bin/env bash
# Checks an n8n version string against GHSA-9x83-43r8-5hwc's affected ranges:
#   vulnerable: <2.35.4  OR  (>=2.36.0 AND <2.36.2)  OR  on the legacy 1.x train, <1.123.73
set -euo pipefail
 
version="${1:?Usage: check-n8n-version.sh <n8n-version>}"
 
ver_lt() {
  [ "$1" = "$2" ] && return 1
  lower=$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)
  [ "$lower" = "$1" ]
}
ver_ge() { ! ver_lt "$1" "$2"; }
 
major="${version%%.*}"
 
if [ "$major" = "1" ]; then
  if ver_lt "$version" "1.123.73"; then
    echo "VULNERABLE: $version is below 1.123.73 on the legacy 1.x train"
    exit 1
  fi
else
  if ver_lt "$version" "2.35.4"; then
    echo "VULNERABLE: $version is below 2.35.4"
    exit 1
  elif ver_ge "$version" "2.36.0" && ver_lt "$version" "2.36.2"; then
    echo "VULNERABLE: $version is in the 2.36.0 pre-patch range"
    exit 1
  fi
fi
 
echo "OK: $version is patched against GHSA-9x83-43r8-5hwc"
exit 0

Save it as check-n8n-version.sh, then pipe your actual version straight in:

chmod +x check-n8n-version.sh
./check-n8n-version.sh "$(n8n --version)"
# or, for Docker:
./check-n8n-version.sh "$(docker exec <container-name> n8n --version)"

Step 3: Patch

Docker / Docker Compose, pin the image tag to a patched version for whichever train you're on and redeploy:

# docker-compose.yml
services:
  n8n:
    image: n8nio/n8n:2.36.2   # or 2.35.4 to stay on the 2.35.x train, or 1.123.73 on the legacy 1.x line
docker compose pull
docker compose up -d

npm/pnpm global install:

npm install -g n8n@2.36.2

Step 4: Confirm the patch took

Re-run the same check, it should now report OK:

./check-n8n-version.sh "$(n8n --version)"

What actually leaked, and why it matters even for "safe-looking" tools

The part worth understanding, not just patching around: $fromAI() takes a placeholder name as its first argument, a plain string you write yourself when you build the tool node, like 'city' in the example above. The bug was that n8n didn't fully validate that name before using it internally, so a crafted one could collide with a reserved prototype key instead of behaving like an ordinary string. That let an attacker reach a live reference to one of n8n's own host prototype objects from inside the expression sandbox, something the sandbox exists specifically to prevent. From a host prototype, the well-worn path to Function and arbitrary code execution is short.

The reason this matters beyond "patch and move on": the vulnerable code path lives in how $fromAI() itself parses its arguments, not in what the AI model does with the value afterward. A tool node that looks completely benign, one where the AI is only ever asked to fill in something like a city name or a ticket ID, was just as exposed as one handling something sensitive. The danger was never in what the parameter was for. It was in the placeholder name sitting right there in the node's configuration, which is exactly the kind of thing a workflow-editor account can already touch.

If you can't upgrade right away

n8n's own advisory lists a few stopgaps, worth noting because none of them close the hole on its own:

  • Restrict workflow-build and tool-node access to people you fully trust, since exploitation requires that permission.
  • Disable AI Agent tool nodes that use $fromAI() if you can live without them until you patch.
  • Run the n8n process itself under a dedicated, unprivileged OS account, so a successful exploit has less to work with even if it lands.

Treat all three as a stopgap, not a substitute for patching. Anyone who still has workflow-editor access can still trigger it.

If keeping up with a bi-weekly cadence of advisories like this one isn't something you want to own yourself, a free n8n Cloud trial hands this one, and every future one, to n8n's own team instead.

Disclosure: the n8n Cloud link above is an affiliate link, if you sign up through it, we may earn a commission at no extra cost to you. See our affiliate disclosure for details.

Where to start

Check your version today if you run any AI Agent workflow with tool nodes, whether or not you remember using $fromAI() directly. If you're running one of the setups from the MCP Server Trigger or MCP Client Tool walkthroughs, this is the same trust boundary those posts described, patch it the same way you'd patch any other high-severity RCE: today, not on the next maintenance window.

Get new tutorials by email

No spam, unsubscribe anytime.