n8n-syntax-node-types — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited n8n-syntax-node-types (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.
| Member | Signature | Required | Purpose | |
|---|---|---|---|---|
description | INodeTypeDescription | YES | Node metadata, properties, credentials | |
execute | (this: IExecuteFunctions) => Promise<INodeExecutionData[][]> | For regular nodes | Process input items | |
trigger | (this: ITriggerFunctions) => Promise<ITriggerResponse> | For event triggers | Emit data on events | |
poll | `(this: IPollFunctions) => Promise<INodeExecutionData[][] \ | null>` | For polling triggers | Periodically check for data |
webhook | (this: IWebhookFunctions) => Promise<IWebhookResponseData> | For webhook nodes | Handle HTTP requests | |
supplyData | (this: ISupplyDataFunctions, itemIndex: number) => Promise<SupplyData> | For AI sub-nodes | Provide data to AI agents | |
methods | { loadOptions, listSearch, credentialTest, resourceMapping, actionHandler } | No | Dynamic methods | |
webhookMethods | { [name]: { checkExists, create, delete } } | No | External webhook lifecycle |
| Field | Type | Required | Notes | |
|---|---|---|---|---|
displayName | string | YES | Human-readable name in UI | |
name | string | YES | Internal identifier (e.g., 'myNode') | |
icon | `string \ | { light, dark }` | No | 'fa:icon-name' or 'file:icon.svg' |
group | NodeGroupType[] | YES | 'input', 'output', 'transform', 'trigger', 'schedule' | |
version | `number \ | number[]` | YES | Supported versions |
description | string | YES | Short description | |
defaults | { name: string } | YES | Default node name | |
inputs | `NodeConnectionType[] \ | ExpressionString` | YES | Input connections |
outputs | `NodeConnectionType[] \ | ExpressionString` | YES | Output connections |
credentials | INodeCredentialDescription[] | No | Required credential types | |
properties | INodeProperties[] | YES | Node parameters | |
polling | true | No | Marks node as polling trigger | |
webhooks | IWebhookDescription[] | No | Webhook registrations | |
requestDefaults | HttpRequestOptions | No | Declarative base URL/headers | |
usableAsTool | true | No | Enable as AI agent tool | |
subtitle | string | No | Dynamic expression for UI subtitle |
| Type | UI Element | Default Value Type |
|---|---|---|
'boolean' | Toggle switch | boolean |
'button' | Action button | string |
'collection' | Group of optional fields | {} |
'color' | Color picker | string |
'dateTime' | Date/time picker | string |
'fixedCollection' | Group with fixed structure | {} |
'hidden' | Hidden value | string |
'icon' | Icon selector | string |
'json' | JSON editor | string |
'callout' | Info/warning callout | string |
'notice' | Notice/info text | string |
'multiOptions' | Multi-select dropdown | string[] |
'number' | Number input | number |
'options' | Single-select dropdown | string |
'string' | Text input | string |
'credentialsSelect' | Credential selector | string |
'resourceLocator' | Resource locator (ID/URL/list) | object |
'curlImport' | cURL import | string |
'resourceMapper' | Resource field mapper | object |
'filter' | Filter/condition builder | object |
'assignmentCollection' | Field assignment collection | object |
'workflowSelector' | Workflow selector | string |
'credentials' | Credentials property | string |
| Method | Signature | Purpose |
|---|---|---|
getInputData | (inputIndex?: number) => INodeExecutionData[] | Get input items |
getNodeParameter | (name: string, itemIndex: number) => any | Read parameter value |
getCredentials | (type: string) => Promise<ICredentialDataDecryptedObject> | Get decrypted credentials |
continueOnFail | () => boolean | Check continue-on-fail setting |
executeWorkflow | (workflowInfo, inputData?) => Promise<ExecuteWorkflowData> | Call sub-workflow |
putExecutionToWait | (waitTill: Date) => Promise<void> | Pause execution until date |
sendMessageToUI | (message: any) => void | Send message to editor UI |
helpers.httpRequest | (options) => Promise<any> | Make HTTP requests |
helpers.getBinaryDataBuffer | (itemIndex, propertyName) => Promise<Buffer> | Read binary data |
helpers.prepareBinaryData | (buffer, fileName?, mimeType?) => Promise<IBinaryData> | Create binary data |
helpers.normalizeItems | (items) => INodeExecutionData[] | Normalize item format |
ALWAYS return INodeExecutionData[][] from execute() -- the outer array represents output indices (for multi-output nodes like IF/Switch), the inner array contains items. Single-output nodes return [returnData].
ALWAYS iterate over input items using this.getInputData() and call getNodeParameter(name, i) with the item index i -- parameters can contain expressions that resolve differently per item.
ALWAYS handle continueOnFail() in the catch block of your item loop -- when enabled, push an error item with pairedItem instead of throwing.
NEVER use &str or borrowed types in node parameters -- n8n uses IDataObject (plain objects) for all parameter values. Cast with as string, as number, etc.
NEVER create an execute() method in declarative nodes -- n8n handles HTTP requests automatically based on routing configuration in properties.
NEVER define inputs on trigger nodes -- trigger nodes ALWAYS have inputs: [] (empty array) because they start workflow execution.
NEVER use this binding in the Node base class -- the Node class passes context as a parameter. Use context.getInputData() instead of this.getInputData().
ALWAYS set noDataExpression: true on resource and operation selector properties -- these properties control node behavior and MUST NOT be expression-dependent.
Is this a trigger/starting node?
├── YES: Does it respond to HTTP requests?
│ ├── YES → Webhook pattern (webhook() + webhooks config)
│ └── NO: Does it poll an external service?
│ ├── YES → Poll pattern (poll() + polling: true)
│ └── NO → Trigger pattern (trigger() + emit())
└── NO: Is this a simple REST API wrapper?
├── YES → Declarative pattern (routing in properties, NO execute())
└── NO → Programmatic pattern (execute() with custom logic)What data does the user provide?
├── True/false → 'boolean'
├── Free text → 'string' (add rows for textarea, editor for code)
├── A number → 'number' (set minValue/maxValue in typeOptions)
├── One choice from list → 'options'
├── Multiple choices from list → 'multiOptions'
├── A set of optional fields → 'collection'
├── A structured group of fields → 'fixedCollection'
├── Raw JSON → 'json'
├── Date/time → 'dateTime'
├── A color → 'color'
├── Filter conditions → 'filter'
├── Field mapping → 'resourceMapper'
├── Field assignments → 'assignmentCollection'
├── Resource ID/URL/name → 'resourceLocator'
├── Another workflow → 'workflowSelector'
└── Display-only info → 'notice' or 'callout'import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
export class MyNode implements INodeType {
description: INodeTypeDescription = {
displayName: 'My Node',
name: 'myNode',
icon: 'file:myIcon.svg',
group: ['transform'],
version: 1,
description: 'Processes data',
defaults: { name: 'My Node' },
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
properties: [/* see references/methods.md */],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
try {
const value = this.getNodeParameter('myParam', i) as string;
returnData.push({ json: { result: value } });
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: (error as Error).message },
pairedItem: { item: i },
});
continue;
}
throw error;
}
}
return [returnData]; // Single output
}
}import { Node } from 'n8n-workflow';
import type {
IExecuteFunctions,
INodeExecutionData,
INodeTypeDescription,
} from 'n8n-workflow';
export class MyNode extends Node {
description: INodeTypeDescription = { /* same as above */ };
async execute(
context: IExecuteFunctions // context parameter, NOT this
): Promise<INodeExecutionData[][]> {
const items = context.getInputData(); // Use context, not this
const returnData: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
const value = context.getNodeParameter('myParam', i) as string;
returnData.push({ json: { result: value } });
}
return [returnData];
}
}export class ApiNode implements INodeType {
description: INodeTypeDescription = {
displayName: 'API Node',
name: 'apiNode',
group: ['input'],
version: 1,
description: 'Interacts with an API',
defaults: { name: 'API Node' },
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [{ name: 'myApi', required: true }],
requestDefaults: {
baseURL: 'https://api.example.com',
headers: { Accept: 'application/json' },
},
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Get Many',
value: 'getAll',
action: 'Get many items',
routing: {
request: { method: 'GET', url: '/items' },
},
},
],
default: 'getAll',
},
],
};
// NO execute() method -- n8n handles requests via routing
}// Show 'limit' only when operation is 'getAll' AND returnAll is false
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 50,
typeOptions: { minValue: 1 },
displayOptions: {
show: {
operation: ['getAll'],
returnAll: [false],
},
},
}
// Rich conditions with operators
{
displayName: 'Advanced Field',
name: 'advancedField',
type: 'string',
default: '',
displayOptions: {
show: {
'@version': [{ _cnd: { gte: 2 } }], // Version 2+
status: [{ _cnd: { not: 'archived' } }], // Not archived
},
},
}import type { IVersionedNodeType, INodeType } from 'n8n-workflow';
export class MyNodeVersioned implements IVersionedNodeType {
currentVersion = 2;
description = { /* INodeTypeBaseDescription */ };
nodeVersions: { [key: number]: INodeType } = {
1: new MyNodeV1(),
2: new MyNodeV2(),
};
getNodeType(version?: number): INodeType {
return this.nodeVersions[version ?? this.currentVersion];
}
}methods = {
loadOptions: {
async getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const credentials = await this.getCredentials('myApi');
const users = await this.helpers.httpRequest({ /* ... */ });
return users.map((u: any) => ({ name: u.name, value: u.id }));
},
},
listSearch: {
async searchUsers(
this: ILoadOptionsFunctions,
filter?: string,
paginationToken?: string,
): Promise<INodeListSearchResult> {
// Return { results: [...], paginationToken?: string }
},
},
credentialTest: {
async testMyApi(this: ICredentialTestFunctions): Promise<INodeCredentialTestResult> {
// Return { status: 'OK', message: 'Success' }
// or { status: 'Error', message: 'Invalid credentials' }
},
},
resourceMapping: {
async getFields(this: ILoadOptionsFunctions): Promise<ResourceMapperFields> {
// Return field definitions for resource mapper
},
},
};interface INodeExecutionData {
json: IDataObject; // REQUIRED -- the main data payload
binary?: IBinaryKeyData; // Optional binary/file data
error?: NodeApiError; // Error info if item errored
pairedItem?: IPairedItemData | number; // Source item linking
}
// Binary data keyed by property name
interface IBinaryKeyData {
[key: string]: IBinaryData; // e.g., { data: {...}, attachment: {...} }
}ALWAYS include a json property on every output item -- it is the only required field. Binary data is ALWAYS secondary.
The return type INodeExecutionData[][] is a 2D array:
// Single output (most nodes):
return [returnData];
// Two outputs (like IF node):
return [trueItems, falseItems];
// Three outputs:
return [output1Items, output2Items, output3Items];
// Empty output (no items):
return [[]];~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.