n8n-impl-security — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited n8n-impl-security (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 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 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.A bulleted imperative like {match} tells the agent to never reveal, disclose, or mention something to the user. Used adversarially it can instruct the agent to hide its tool calls or lie about what it did — stripping the transparency a user relies on to trust the agent.
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.
Harden n8n v1.x deployments: credential encryption, sandboxed execution, reverse proxy, SSL/TLS, Docker Secrets, audit, and data pruning.
| Variable | Default | MUST Set |
|---|---|---|
N8N_ENCRYPTION_KEY | random | YES - ALWAYS set explicitly and back up |
N8N_RUNNERS_ENABLED | false | YES - true for sandboxed Code node execution |
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS | false | YES - true to set 0600 on settings file |
NODE_ENV | — | YES - production for production deployments |
N8N_PROTOCOL | http | YES - https behind reverse proxy |
N8N_BLOCK_ENV_ACCESS_IN_NODE | false | YES - true to block env access in expressions |
# CLI audit
n8n audit
# API audit (returns JSON report)
curl -X POST '<N8N_HOST>:<N8N_PORT>/api/v1/audit' \
-H 'X-N8N-API-KEY: <key>' \
-H 'Content-Type: application/json'The audit checks for: abandoned workflows (default 90 days), insecure credential usage, risky node configurations, and workflow vulnerabilities.
Set N8N_ENCRYPTION_KEY explicitly?
├─ NO → n8n generates random key stored in .n8n/config
│ ├─ DANGER: Lose this file = lose ALL credentials
│ └─ ALWAYS set explicitly in production
└─ YES → Key encrypts all credentials in database
├─ Multi-instance? → ALL instances MUST share the SAME key
├─ Back up the key? → YES, ALWAYS, separately from database
└─ Using Docker? → Pass via _FILE suffix for Docker SecretsRunning in Docker?
├─ YES → Use Docker Secrets with _FILE suffix
│ Example: DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/db_password
│ ├─ NEVER put secrets in docker-compose.yml directly
│ └─ NEVER put secrets in Dockerfiles
└─ NO → Use OS-level secret management
├─ NEVER commit .env files to version control
└─ NEVER log environment variablesDirect SSL on n8n?
├─ YES → Set N8N_SSL_KEY and N8N_SSL_CERT paths
│ └─ Only for simple setups without reverse proxy
└─ NO (recommended) → Use reverse proxy for TLS termination
├─ Traefik → automatic Let's Encrypt
├─ Nginx → manual cert or certbot
└─ Caddy → automatic HTTPS
Set: N8N_PROTOCOL=https
Set: N8N_PROXY_HOPS=1 (or more)
Set: WEBHOOK_URL=https://your-domain/Task runners enabled?
├─ NO → Code node runs in main n8n process (UNSAFE)
│ └─ ALWAYS enable: N8N_RUNNERS_ENABLED=true
└─ YES → Code executes in sandboxed runner
├─ N8N_RUNNERS_MODE=internal (default, same machine)
├─ N8N_RUNNERS_MODE=external (separate process/container)
├─ N8N_RUNNERS_TASK_TIMEOUT=300 (5 min default)
└─ N8N_RUNNERS_MAX_CONCURRENCY=5ALWAYS set N8N_ENCRYPTION_KEY explicitly in production.
# Generate a secure encryption key
openssl rand -hex 32Rules:
ALWAYS use the _FILE suffix pattern for sensitive values in Docker:
# docker-compose.yml
services:
n8n:
image: docker.n8n.io/n8nio/n8n
secrets:
- n8n_encryption_key
- db_password
environment:
- N8N_ENCRYPTION_KEY_FILE=/run/secrets/n8n_encryption_key
- DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/db_password
secrets:
n8n_encryption_key:
file: ./secrets/encryption_key.txt
db_password:
file: ./secrets/db_password.txtRules:
_FILE suffix instead of inline secrets in compose files_FILE suffix works on most credential/connection variablesALWAYS configure execution data pruning to prevent unbounded database growth:
EXECUTIONS_DATA_PRUNE=true # Enable auto-pruning (default: true)
EXECUTIONS_DATA_MAX_AGE=336 # Max age in hours (default: 14 days)
EXECUTIONS_DATA_PRUNE_MAX_COUNT=10000 # Max execution count
EXECUTIONS_DATA_HARD_DELETE_BUFFER=1 # Hours before hard deletionRules:
EXECUTIONS_DATA_PRUNE=true)EXECUTIONS_DATA_MAX_AGE appropriate to your retention needsEXECUTIONS_DATA_SAVE_ON_SUCCESS=none if you only need error dataALWAYS enable task runners for Code node security:
N8N_RUNNERS_ENABLED=true
N8N_RUNNERS_MODE=internal # or 'external' for separate process
N8N_RUNNERS_TASK_TIMEOUT=300 # 5 minute timeout
N8N_RUNNERS_MAX_CONCURRENCY=5 # concurrent task limitRules:
N8N_RUNNERS_ENABLED=true in productionN8N_RUNNERS_AUTH_TOKENALWAYS use a reverse proxy for TLS termination in production. See references/examples.md for full Traefik and Nginx configurations.
Required environment variables behind a reverse proxy:
N8N_PROTOCOL=https
N8N_HOST=n8n.example.com
N8N_PROXY_HOPS=1 # Number of proxies in front of n8n
WEBHOOK_URL=https://n8n.example.com/
N8N_SECURE_COOKIE=true # HTTPS-only cookiesALWAYS restrict file and node access in production:
N8N_BLOCK_ENV_ACCESS_IN_NODE=true # Block env access in expressions
N8N_BLOCK_FILE_ACCESS_TO_N8N_FILES=true # Block .n8n directory access
N8N_RESTRICT_FILE_ACCESS_TO=/files # Limit file access to specific dirs
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true # 0600 on settings fileOptional node restrictions:
# Exclude dangerous nodes entirely
NODES_EXCLUDE=["n8n-nodes-base.executeCommand","n8n-nodes-base.localFileTrigger"]
# Restrict Code node module access
NODE_FUNCTION_ALLOW_BUILTIN=crypto,path # Only allow specific builtins
NODE_FUNCTION_ALLOW_EXTERNAL=lodash # Only allow specific externalsN8N_ENCRYPTION_KEY set explicitly and backed up separatelyNODE_ENV=productionN8N_RUNNERS_ENABLED=true (sandboxed Code execution)N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=trueN8N_PROTOCOL=https and WEBHOOK_URL set to HTTPS URL_FILE suffix) for all sensitive valuesN8N_BLOCK_ENV_ACCESS_IN_NODE=trueN8N_BLOCK_FILE_ACCESS_TO_N8N_FILES=trueN8N_RESTRICT_FILE_ACCESS_TO set to specific directoriesN8N_SECURE_COOKIE=trueN8N_SAMESITE_COOKIE=strict (if single-domain)NODES_EXCLUDE for unused dangerous nodesNODE_FUNCTION_ALLOW_BUILTIN restricted to needed modulesN8N_PUBLIC_API_SWAGGERUI_DISABLED=true (disable API playground)N8N_COMMUNITY_PACKAGES_ENABLED=false (if not needed)N8N_DIAGNOSTICS_ENABLED=false (disable telemetry)n8n audit or POST /audit regularlyN8N_SECURITY_AUDIT_DAYS_ABANDONED_WORKFLOW=90| Anti-Pattern | Risk | Fix |
|---|---|---|
No explicit N8N_ENCRYPTION_KEY | Lose key = lose ALL credentials | ALWAYS set and back up explicitly |
| Secrets in docker-compose.yml | Secrets in version control | Use Docker Secrets with _FILE suffix |
| SQLite in production | No queue mode, no scaling, corruption risk | Use PostgreSQL |
| No task runners | Code node runs in main process | N8N_RUNNERS_ENABLED=true |
| HTTP without TLS | Credentials transmitted in cleartext | Reverse proxy with HTTPS |
| No execution pruning | Unbounded database growth | EXECUTIONS_DATA_PRUNE=true |
NODE_FUNCTION_ALLOW_BUILTIN=* | Code node can access all Node.js modules | Whitelist specific modules |
See references/anti-patterns.md for detailed anti-patterns with examples.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.