n8n-syntax-credentials — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited n8n-syntax-credentials (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.
| Property | Type | Required | Description |
|---|---|---|---|
name | string | YES | Internal identifier (e.g., 'myServiceApi') |
displayName | string | YES | GUI label shown to users |
documentationUrl | string | NO | Link to credentials setup docs |
properties | INodeProperties[] | YES | Array of credential input fields |
authenticate | IAuthenticate | NO | How credentials inject into requests |
test | ICredentialTestRequest | NO | Endpoint for credential validation |
extends | string[] | NO | Inherit from other credential types |
icon | Icon | NO | Credential icon in GUI |
iconUrl | string | NO | URL-based icon |
| Type | Use Case | Example |
|---|---|---|
'string' | API keys, tokens, URLs | { type: 'string', typeOptions: { password: true } } |
'number' | Port numbers, numeric IDs | { type: 'number', default: 443 } |
'boolean' | Feature toggles | { type: 'boolean', default: false } |
'options' | Region selectors, auth method | { type: 'options', options: [...] } |
'hidden' | Internal values, computed tokens | { type: 'hidden', typeOptions: { expirable: true } } |
| Method | Target | Key in properties |
|---|---|---|
| Query string | URL parameters | qs |
| Header | HTTP headers | headers |
| Request body | POST body | body |
| Basic auth | Authorization header | auth |
ALL methods require type: 'generic' as the authenticate type.
Access credential values inside authenticate using: ={{$credentials.fieldName}}
For header values with prefixes: '=Bearer {{$credentials.apiKey}}'
ALWAYS set typeOptions: { password: true } on sensitive fields (API keys, tokens, passwords) -- n8n masks these in the GUI and encrypts them at rest.
ALWAYS register credentials in package.json under the n8n.credentials array pointing to compiled .js files in dist/.
ALWAYS use type: 'generic' in the authenticate property -- no other type value is supported for the generic authenticate pattern.
ALWAYS provide a test property with a lightweight API endpoint to validate credentials during setup.
NEVER store credentials in plain text or workflow JSON -- n8n encrypts all credential data using an encryption key.
NEVER hardcode credential values in node code -- ALWAYS read them via this.getCredentials('credentialTypeName') in the node's execute method.
NEVER use authenticate AND manually inject credentials in the node's execute() method for the same credential -- pick one approach.
NEVER forget the = prefix in authenticate expressions -- '={{$credentials.key}}' is correct, '{{$credentials.key}}' silently passes a literal string.
Need authentication?
├── API key in URL parameter?
│ └── Use authenticate.properties.qs
├── API key or token in header?
│ └── Use authenticate.properties.headers
├── Username + password (Basic Auth)?
│ └── Use authenticate.properties.auth
├── Credentials in request body?
│ └── Use authenticate.properties.body
├── OAuth2 flow?
│ └── Extend 'oAuth2Api' base credential
└── Complex/custom auth logic?
└── Skip authenticate, handle in node execute() via this.getCredentials()import type { ICredentialType, INodeProperties, IAuthenticate } from 'n8n-workflow';
export class ExampleApi implements ICredentialType {
name = 'exampleApi';
displayName = 'Example API';
documentationUrl = 'https://docs.example.com/auth';
properties: INodeProperties[] = [
{
displayName: 'API Key',
name: 'apiKey',
type: 'string',
typeOptions: { password: true },
default: '',
},
];
authenticate: IAuthenticate = {
type: 'generic',
properties: {
headers: {
Authorization: '=Bearer {{$credentials.apiKey}}',
},
},
};
test: ICredentialTestRequest = {
request: {
baseURL: 'https://api.example.com',
url: '/me',
},
};
}authenticate: IAuthenticate = {
type: 'generic',
properties: {
qs: {
api_key: '={{$credentials.apiKey}}',
},
},
};export class MyBasicAuthApi implements ICredentialType {
name = 'myBasicAuthApi';
displayName = 'My Basic Auth API';
properties: INodeProperties[] = [
{
displayName: 'Username',
name: 'username',
type: 'string',
default: '',
},
{
displayName: 'Password',
name: 'password',
type: 'string',
typeOptions: { password: true },
default: '',
},
];
authenticate: IAuthenticate = {
type: 'generic',
properties: {
auth: {
username: '={{$credentials.username}}',
password: '={{$credentials.password}}',
},
},
};
}authenticate: IAuthenticate = {
type: 'generic',
properties: {
body: {
username: '={{$credentials.username}}',
password: '={{$credentials.password}}',
},
},
};import type { ICredentialType, INodeProperties } from 'n8n-workflow';
export class MyServiceOAuth2Api implements ICredentialType {
name = 'myServiceOAuth2Api';
displayName = 'My Service OAuth2 API';
extends = ['oAuth2Api'];
documentationUrl = 'https://docs.myservice.com/oauth';
properties: INodeProperties[] = [
{
displayName: 'Grant Type',
name: 'grantType',
type: 'hidden',
default: 'authorizationCode',
},
{
displayName: 'Authorization URL',
name: 'authUrl',
type: 'hidden',
default: 'https://myservice.com/oauth/authorize',
},
{
displayName: 'Access Token URL',
name: 'accessTokenUrl',
type: 'hidden',
default: 'https://myservice.com/oauth/token',
},
{
displayName: 'Scope',
name: 'scope',
type: 'hidden',
default: 'read write',
},
{
displayName: 'Auth URI Query Parameters',
name: 'authQueryParameters',
type: 'hidden',
default: '',
},
{
displayName: 'Authentication',
name: 'authentication',
type: 'hidden',
default: 'body',
},
];
}test: ICredentialTestRequest = {
request: {
baseURL: '={{$credentials?.baseUrl}}',
url: '/api/v1/verify',
method: 'GET',
skipSslCertificateValidation:
'={{$credentials?.allowUnauthorizedCerts}}' as unknown as boolean,
},
};n8n calls this endpoint when users click "Test" in the credentials dialog. A 2xx response validates the credentials.
{
"name": "n8n-nodes-myservice",
"n8n": {
"n8nNodesApiVersion": 1,
"credentials": [
"dist/credentials/MyServiceApi.credentials.js",
"dist/credentials/MyServiceOAuth2Api.credentials.js"
],
"nodes": [
"dist/nodes/MyService/MyService.node.js"
]
}
}Rule: The n8n.credentials array MUST point to compiled .js files in dist/, NEVER to .ts source files.
export class MyServiceApi implements ICredentialType {
name = 'myServiceApi';
displayName = 'My Service API';
extends = ['oAuth2Api']; // Inherits all OAuth2 properties
properties: INodeProperties[] = [
// Only define ADDITIONAL properties here
// Base OAuth2 properties (clientId, clientSecret, etc.) are inherited
{
displayName: 'Region',
name: 'region',
type: 'options',
options: [
{ name: 'US', value: 'us' },
{ name: 'EU', value: 'eu' },
],
default: 'us',
},
];
}async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const credentials = await this.getCredentials('exampleApi');
// credentials.apiKey, credentials.baseUrl, etc.
const response = await this.helpers.httpRequestWithAuthentication.call(
this, 'exampleApi', {
method: 'GET',
url: `${credentials.baseUrl}/api/data`,
}
);
return [this.helpers.returnJsonArray(response as IDataObject[])];
}Rule: When using authenticate on the credential, use httpRequestWithAuthentication -- it automatically applies the authenticate configuration. When NOT using authenticate, manually inject credentials into request options.
Link credentials to nodes via the credentials array in INodeTypeDescription:
credentials: [
{
name: 'myServiceApi',
required: true,
},
{
name: 'myServiceOAuth2Api',
required: true,
displayOptions: {
show: { authentication: ['oAuth2'] },
},
},
],Use displayOptions to conditionally show credentials based on an authentication method selector.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.