n8n-syntax-expressions — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited n8n-syntax-expressions (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
All n8n expressions use double-curly-brace syntax inside node parameter fields:
{{ expression }}Expressions support standard JavaScript operations plus n8n-specific built-in variables.
| Variable | Returns | Description |
|---|---|---|
$json | Object | Shorthand for $input.item.json — current item's JSON data |
$binary | Object | Shorthand for $input.item.binary — current item's binary data |
$input.item | Item | The input item currently being processed |
$input.all() | Array\<Item\> | All input items from the current node |
$input.first() | Item | First input item |
$input.last() | Item | Last input item |
$input.params | Object | Node configuration settings and operation parameters |
| Variable | Returns | Description |
|---|---|---|
$("node-name").all() | Array\<Item\> | All items from named node output |
$("node-name").first() | Item | First item from named node |
$("node-name").last() | Item | Last item from named node |
$("node-name").item | Item | Linked item via paired items tracking |
$("node-name").itemMatching(index) | Item | Traces back to matching item (preferred in Code node) |
$("node-name").params | Object | Query settings/parameters of named node |
$("node-name").context | Object | Only available with Loop Over Items node |
$("node-name").isExecuted | Boolean | True if the node has executed |
| Variable | Returns | Description |
|---|---|---|
$workflow.id | String | Workflow ID |
$workflow.name | String | Workflow name |
$workflow.active | Boolean | Whether workflow is active |
$execution.id | String | Unique execution ID |
$execution.mode | String | "test", "production", or "evaluation" |
$execution.resumeUrl | String | Webhook URL to resume at a Wait node |
$execution.resumeFormUrl | String | URL for Wait node form |
$execution.customData | CustomData | Get/set custom execution data |
| Variable | Returns | Description |
|---|---|---|
$env | Object | Instance environment variables |
$vars | Object | Active environment variables (all strings) |
$secrets | Object | External secrets (NOT available in Code node) |
| Variable | Returns | Description |
|---|---|---|
$now | DateTime | Current moment, respects workflow timezone |
$today | DateTime | Midnight at start of current day |
| Variable | Returns | Description |
|---|---|---|
$prevNode.name | String | Name of previous node |
$prevNode.outputIndex | Number | Output connector index |
$prevNode.runIndex | Number | Run of previous node |
$runIndex | Number | How many times current node has executed (zero-based) |
$itemIndex | Number | Current item position (NOT available in Code node) |
$parameter | Object | Configuration settings of current node |
$nodeVersion | Number | Current node version |
$pageCount | Number | Results pages fetched (HTTP Request node only) |
| Function | Returns | Description |
|---|---|---|
$ifEmpty(value, fallback) | any | Returns value if non-empty, otherwise fallback |
$jmespath(object, searchString) | any | JMESPath query on a JSON object |
$getWorkflowStaticData(type) | Object | Persistent data. Type: "global" or "node" |
| Variable | Returns | Description |
|---|---|---|
$response.body | Object | Response body from last HTTP call |
$response.headers | Object | Response headers |
$response.statusCode | Number | HTTP status code |
$response.statusMessage | String | Optional status message |
NEVER use $itemIndex in the Code node -- it is NOT available there. Use a loop counter or items.indexOf() instead.
NEVER use $secrets in the Code node -- it is NOT available there. Pass secret values via node parameters or use $env / $vars instead.
NEVER reverse the parameter order of $jmespath() -- it is $jmespath(object, searchString), NOT $jmespath(searchString, object). The n8n implementation differs from the JMESPath specification's search(searchString, object) pattern.
NEVER use new Date() in expressions -- ALWAYS use Luxon's $now, $today, DateTime.fromISO(), or DateTime.fromFormat() for consistent timezone handling.
NEVER rely on $getWorkflowStaticData() during manual test executions -- static data requires an active workflow with a trigger or webhook.
NEVER store large data objects in workflow static data -- keep it small. Large data causes performance degradation.
ALWAYS use $json as the primary way to access current item data -- it is shorthand for $input.item.json.
ALWAYS use $("Node Name").item in expressions for linked item access. In Code nodes, use $("Node Name").itemMatching(index) instead.
ALWAYS use $ifEmpty(value, fallback) for safe null/undefined handling -- it covers "", [], {}, null, and undefined.
ALWAYS use bracket notation for dynamic property access: $json["field-with-dashes"].
{{ $json.name }}
{{ $json.address.city }}
{{ $json["field-with-dashes"] }}{{ $("HTTP Request").item.json.data }}
{{ $("Get Users").first().json.email }}
{{ $("Webhook").all().length }}{{ $json.status === "active" ? "Yes" : "No" }}
{{ $ifEmpty($json.nickname, $json.fullName) }}{{ $jmespath($json.data, "[*].name") }}
{{ $jmespath($("Code").all(), "[?json.active==`true`].json.id") }}{{ $now.format("yyyy-MM-dd HH:mm:ss") }}
{{ $today.minus({days: 7}).toISO() }}
{{ $now.toRelative() }}// In Code node — data persists across executions
const staticData = $getWorkflowStaticData('global');
const lastRun = staticData.lastExecution;
staticData.lastExecution = new Date().toISOString();
// n8n saves automatically on successful execution{{ (function() { const x = $json.price * 1.21; return x.toFixed(2); })() }}// Set data
$execution.customData.set("user_email", "[email protected]");
$execution.customData.setAll({"key1": "val1", "key2": "val2"});
// Get data
const email = $execution.customData.get("user_email");
const all = $execution.customData.getAll();Every output item links back to the input items that produced it. This enables:
$("Node").item — access the linked item in expressions$("Node").itemMatching(index) — trace item origin in Code nodeWhen a node processes multiple items, {{ $json.fruit }} dynamically resolves to each item's value during iteration.
$ifEmpty(value, fallback) returns fallback when value is any of:
| Value | Considered Empty |
|---|---|
"" | Yes |
[] | Yes |
{} | Yes |
null | Yes |
undefined | Yes |
0 | No |
false | No |
| Context | Available Variables |
|---|---|
| Node parameter fields | ALL $ variables |
| Code node (JavaScript) | ALL except $itemIndex and $secrets |
| Code node (Python) | _ prefix variants (e.g., _json, _env, _vars) |
| Credential fields | $credentials object only |
| HTTP Request node | ALL plus $response and $pageCount |
| Loop Over Items | ALL plus $("Loop Node").context |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.