n8n-agents-review — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited n8n-agents-review (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.
Run this checklist against ANY n8n workflow JSON, custom node code, or deployment configuration to catch errors before they reach production.
| Area | Critical Checks | Reference |
|---|---|---|
| Workflow JSON | IConnections 3-level nesting, unique node names, required fields | methods.md |
| Connection Wiring | Type matching, correct indices, no orphan nodes | methods.md |
| Expressions | Valid variable refs, context restrictions, JMESPath order | methods.md |
| Credentials | ICredentialType completeness, authenticate method, test endpoint | methods.md |
| Node Types | INodeType interface, execute return type, property types | methods.md |
| Error Handling | Error workflow, continueOnFail, retry config | methods.md |
| Deployment | Encryption key, queue mode, volume mounts, PostgreSQL | methods.md |
| Code Node | Return format, restricted variables, sandbox limits | methods.md |
| Security | No hardcoded secrets, encryption, task runners | methods.md |
| Anti-Patterns | Consolidated list from all skill areas | anti-patterns.md |
START: What are you reviewing?
├─ Workflow JSON file (.json)
│ ├─ Run: Workflow JSON checks (Section 1)
│ ├─ Run: Connection Wiring checks (Section 2)
│ ├─ Run: Expression checks on all parameter values (Section 3)
│ ├─ Run: Error Handling checks (Section 6)
│ └─ Run: Anti-Pattern scan (Section 10)
│
├─ Custom node code (.node.ts)
│ ├─ Run: Node Type checks (Section 5)
│ ├─ Run: Credential checks if node uses credentials (Section 4)
│ ├─ Run: Error Handling checks (Section 6)
│ └─ Run: Anti-Pattern scan (Section 10)
│
├─ Credential definition (.credentials.ts)
│ └─ Run: Credential checks (Section 4)
│
├─ Code node content
│ ├─ Run: Code Node checks (Section 8)
│ └─ Run: Anti-Pattern scan (Section 10)
│
├─ Deployment config (docker-compose.yml / env vars)
│ ├─ Run: Deployment checks (Section 7)
│ └─ Run: Security checks (Section 9)
│
└─ Full project audit
└─ Run ALL sections sequentiallyALWAYS verify these required fields on every node in nodes[]:
| Field | Type | Rule |
|---|---|---|
id | string | MUST be unique UUID |
name | string | MUST be unique within the workflow |
type | string | MUST match a registered node type (e.g., n8n-nodes-base.httpRequest) |
typeVersion | number | MUST be a valid version for the node type |
position | [number, number] | MUST be [x, y] coordinate array |
parameters | object | MUST exist (can be empty {}) |
ALWAYS verify the workflow root object contains:
id (string)name (string)active (boolean)nodes (array)connections (object)NEVER accept a workflow where two nodes share the same name — connections reference nodes by name, so duplicates break wiring.
IConnections uses 3-level nesting:
connections[sourceNodeName][connectionType][outputIndex] = IConnection[]ALWAYS verify:
connections matches a name in nodes[]IConnection.node value matches a name in nodes[]IConnection.type is a valid NodeConnectionType (usually "main")IConnection.index does not exceed the destination node's input countgroup: ['trigger']) have NO incoming connectionsIF node pattern: connections["IF"].main MUST have exactly 2 arrays — index 0 for true, index 1 for false.
ALWAYS verify expressions ({{ ... }}) use correct variable references:
| Context | Available | NOT Available |
|---|---|---|
| Any expression | $json, $binary, $input, $execution, $workflow, $now, $today, $env, $vars, $prevNode, $runIndex, $parameter | — |
| Code node | All $ vars except $itemIndex and $secrets | $itemIndex, $secrets |
| Python Code node | _ prefix versions (_json, _items) | $ prefix, dot notation on items |
ALWAYS verify $jmespath(object, searchString) parameter order — object FIRST, search string SECOND. This differs from the JMESPath spec.
NEVER allow $("<NodeName>") to reference a node name that does not exist in the workflow.
ALWAYS verify ICredentialType implementations include:
| Property | Required | Rule |
|---|---|---|
name | YES | Internal identifier, matches node's credential reference |
displayName | YES | Human-readable label |
properties | YES | Array of INodeProperties[] defining input fields |
authenticate | YES | Method with type: 'generic' and properties object |
test | RECOMMENDED | ICredentialTestRequest with test endpoint |
ALWAYS verify authenticate.type is 'generic' — other values are not supported.
ALWAYS verify credential expressions use $credentials prefix: ={{$credentials.apiKey}}.
NEVER allow credentials to be hardcoded in node parameters — ALWAYS use credential references.
ALWAYS verify INodeType implementations:
| Check | Expected | Common Failure |
|---|---|---|
description property | INodeTypeDescription with all required fields | Missing inputs, outputs, or properties |
execute() return type | Promise<INodeExecutionData[][]> | Returning single array [] instead of [[]] |
| Trigger nodes | inputs: [] and group: ['trigger'] | Having inputs on trigger nodes |
Property type values | Valid NodePropertyTypes | Using invalid type strings |
displayOptions | References existing property names/values | Referencing non-existent parameters |
credentials array | Each entry has name matching a credential type | Credential name mismatch |
ALWAYS verify execute() returns [returnData] (wrapped in outer array), NOT just returnData.
ALWAYS verify each item in the return array has a json property: { json: { ... } }.
ALWAYS verify:
settings.errorWorkflow is set in production workflowsthis.continueOnFail() include error data in output: returnData.push({ json: { error: error.message }, pairedItem: { item: i } });retryOnFail: true, maxTries >= 2, waitBetweenTries >= 1000'continueErrorOutput', 'continueRegularOutput', or 'stopWorkflow'NEVER allow a production workflow without an error workflow — silent failures are unacceptable.
ALWAYS verify for production deployments:
| Check | Expected | Consequence of Missing |
|---|---|---|
N8N_ENCRYPTION_KEY | Explicitly set and backed up | Key regeneration locks out all credentials |
NODE_ENV | production | Missing production optimizations |
N8N_PROTOCOL | https | Credentials transmitted in cleartext |
WEBHOOK_URL | Set to public URL | Webhooks unreachable externally |
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS | true | Settings file readable by other users |
N8N_RUNNERS_ENABLED | true | Code runs in main process (security risk) |
Volume: /home/node/.n8n | Mounted to persistent volume | Data lost on container restart |
Queue mode additional requirements:
| Check | Expected | Consequence of Missing |
|---|---|---|
DB_TYPE | postgresdb | SQLite does NOT support queue mode |
EXECUTIONS_MODE | queue | Workers will not process jobs |
| Redis configured | QUEUE_BULL_REDIS_HOST + port | Queue has no broker |
Shared N8N_ENCRYPTION_KEY | Same key on main + all workers | Credential decryption fails |
| S3 binary storage | Configured for shared access | Binary data inaccessible across instances |
ALWAYS verify Code node content:
| Check | Rule |
|---|---|
| Return format (all items) | MUST return [{json: {...}}, ...] — array of objects with json key |
| Return format (each item) | MUST return {json: {...}} — single object with json key |
No $itemIndex | NEVER use $itemIndex in Code node — it is not available |
No $secrets | NEVER use $secrets in Code node — it is not available |
| No HTTP requests | NEVER make HTTP calls in Code node — use HTTP Request node |
| No file system access | NEVER access files directly — use Read/Write Files nodes |
| Python bracket notation | ALWAYS use item["json"]["field"], NEVER item.json.field in Python |
| Binary data access | ALWAYS use this.helpers.getBinaryDataBuffer(), NEVER direct buffer access |
ALWAYS verify:
N8N_ENCRYPTION_KEY is explicitly configured (not auto-generated)N8N_RUNNERS_ENABLED=true (isolates Code node execution)N8N_RESTRICT_FILE_ACCESS_TO limits filesystem pathsN8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=trueN8N_BLOCK_ENV_ACCESS_IN_NODE=true if env vars contain secretsN8N_PROTOCOL=https with valid TLS terminationN8N_SECURE_COOKIE=true in HTTPS deploymentsScan for ALL anti-patterns listed in anti-patterns.md. Key categories:
new Date() instead of LuxonAfter completing all applicable checks, produce a report:
## n8n Review Report
**Target**: [filename or description]
**Type**: [Workflow JSON | Custom Node | Credential | Deployment Config | Code Node]
**Date**: [date]
### Summary
- Total checks: [N]
- Passed: [N]
- Failed: [N]
- Warnings: [N]
### Critical Failures
1. [Area] — [What failed] — [Expected state] — [How to fix]
### Warnings
1. [Area] — [What to improve] — [Recommendation]
### Anti-Patterns Detected
1. [AP-XXX] — [Description] — [Location in code/config]~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.