n8n Security & Engineering Audit

You shipped it.
Is it safe to operate?

We audited n8n across 9 engineering areas - 60 findings, 11 of them critical or high. Here's the picture.

https://github.com/n8n-io/n8n

watch - Not production-ready without remediation6.3 of 10

Share and export

Send this report to a teammate or turn the findings into implementation work.

Export CSV
0critical
11high
30medium
19low
Area Scores

9 areas evaluated

Each area scored 0-10 by an AnchorStack engineer.

Code Quality

7/10

n8n's codebase is well-structured and enforces strong quality standards, with a comprehensive CI pipeline gating every PR on lint, typecheck, format, and tests. The main risks are concentrated in the execution engine: workflow-execute.ts is a 2874-line monolith with four broad file-level eslint-disable directives suppressing unsafe-member-access and related rules, making it both hard to change safely and easy to introduce regressions. Type safety is excellent in most of the codebase but degrades in the critical execution layer. No test coverage thresholds are configured, so new code can be merged without adding tests as long as existing gates pass.

watchWeight x1

Architecture

7.2/10

n8n is a mature, dual-mode workflow automation platform built as a TypeScript monorepo. Its architecture cleanly separates a modular Express API/UI server from an optionally distributed execution engine via a Bull/Redis queue, giving operators a simple regular mode (single process) or a scaled queue mode (main + worker + webhook processes). Core strengths are a clear DI-based separation of concerns, typed FE/BE API contracts, well-defined execution lifecycle hooks, and thoughtful queue recovery for dangling executions. Key risks are in-process ActiveExecutions state that is not globally distributed across multiple mains, manual executions defaulting to run on the main process in queue mode (deprecated but still default unless an env flag is set), local-filesystem binary data breaking multi-process deployments, and WaitTracker timer loss on leader failover creating a latency window for execution resumption.

watchWeight x1

Data

5.9/10

n8n's data layer is TypeORM-backed and supports SQLite (default, single-process) and PostgreSQL (recommended for production/queue mode). The migration discipline is exemplary — 200+ timestamped, transactional migrations with a purpose-built DSL, most offering reversible `down` methods. Schema design is clean, with consistent string/UUID PKs, composite PKs for join tables, and cascade deletes modeled correctly for the tenant/project ownership hierarchy. The three material risks are: (1) API keys are JWT tokens stored in plaintext — any DB read grants immediate API access; (2) credential encryption defaults to AES-256-CBC with MD5-based key derivation for most deployments (the stronger GCM path is behind a feature flag); (3) no backup/restore tooling or runbooks exist in the repository, leaving recovery entirely to operator initiative. The data layer is safe to evolve under normal conditions but would not withstand a direct database breach without significant credential and API-key exposure.

watchWeight x1.5

Security

6/10

n8n is a workflow automation platform with a broadly solid security foundation: JWT-based authentication with token revocation, typed scope-based authorization enforced at API boundaries, credential encryption at rest, and hardened Docker images with Trivy scanning and VEX triage. The most significant risk for self-hosted deployments is that SSRF protection is disabled by default, making every instance an open SSRF proxy until the operator explicitly enables it. The JavaScript Code node (non-task-runner path) relies on vm2, a deprecated sandbox with known escape vulnerabilities, creating code-execution risk in the main process. Vulnerability scanning coverage has meaningful gaps: no SAST, no SCA/dependency audit, and the Trivy image scan does not block releases (exit-code 0), so Critical/High container vulnerabilities can ship without operator intervention.

watchWeight x1.5

AI Engineering

6.7/10

n8n has strong AI engineering foundations: the AGENTS.md/CLAUDE.md hierarchy is specific, actionable, and hierarchical, covering TypeScript patterns, migration rules, security fix hygiene, and testing conventions. A rich library of 15+ domain-specific Claude Code skills (developer agent, migration guide, protect-endpoints, mutant-diff, reproduce-bug, create-pr, etc.) provides meaningful development leverage. Validation gates are well-enforced through required CI checks and documented pre-commit requirements. The primary gaps are: spec-driven development is aspirational (the skill exists but no specs are found in .claude/specs/), planning artifacts are gitignored and ephemeral, and there is no documented knowledge lifecycle for compounding learnings from AI-assisted work — leaving the team with strong day-to-day AI leverage but limited institutional memory about where AI approaches succeed or fail.

watchWeight x1

Infrastructure

6/10

n8n's infrastructure posture is strong where the project has direct control: Docker images are hardened (multi-stage Alpine, non-root, tini, pinned digests), reproducible, and attested with SLSA L3 provenance, SBOM, and VEX triage; CI/CD pipelines are comprehensive with typecheck, lint, unit, E2E, DB, and security gates enforced through a required-checks branch-protection gate. The main risks are in two areas: (1) defaults that leave self-hosted deployments insecure out of the box — health check endpoints are disabled, Redis defaults to no auth and no TLS, and benchmark example Compose files ship hardcoded weak credentials that operators may copy directly; and (2) documentation gaps — no backup/restore tooling or runbooks, no rollback procedures for Docker upgrades, and no disaster recovery documentation exist in the repository. n8n's own cloud infrastructure is not present in this repository, making those controls unauditable from the source.

watchWeight x1

Observability

3.9/10

n8n ships a mature observability framework — Sentry backend/frontend error tracking, Prometheus metrics with 15+ collectors, OpenTelemetry distributed tracing (including UI configuration), structured Winston logging, an internal event bus with typed audit events, and `/healthz` + `/healthz/readiness` endpoints — but every major tool is disabled or left unconfigured by default. A self-hosted instance in default state produces text-format console logs only: no error tracking, no metrics endpoint, no external alerts, no uptime monitoring. Operators must discover and configure each layer independently, without a production-readiness guide, recommended alert rules, runbooks, or SLO targets in the repository. No request correlation IDs are injected into access logs, making per-request debugging difficult. The highest risk is that most self-hosted operators will run n8n with zero production visibility and learn about failures only from user complaints.

failWeight x1

Delivery

7.4/10

n8n has a mature, fully automated CI/CD pipeline built on GitHub Actions with SHA-pinned actions, path-filtered smart gating, Renovate dependency management, SLSA L3 provenance, SBOM, and automated multi-track release scheduling. The primary gap is that LLM evaluation jobs are explicitly non-blocking in required-checks despite AI being a central product capability. Feature flag lifecycle management is PostHog-backed but lacks in-repo cleanup discipline. Release traceability is best-in-class for an open-source project at this scale.

watchWeight x1

Documentation

6.6/10

n8n's documentation is well-organized and mature for an open-source monorepo, with a clear layered structure: README for end-users, CONTRIBUTING.md for contributors, AGENTS.md as the canonical AI-agent context file, and deep technical docs inside key packages. AI-agent context files are a standout strength — AGENTS.md is comprehensive, CLAUDE.md cascades correctly through sub-packages, and the .claude/plugins/n8n/ plugin layer is carefully namespaced and maintained. The main weaknesses are: absence of operational runbooks (deployment, rollback, DB restore, secret rotation) anywhere in-repo; an env-var surface that is explicitly incomplete with no consolidated reference; stale package-path references in AGENTS.md that point to `packages/editor-ui` instead of the actual `packages/frontend/editor-ui`; no formal decision records; and no in-repo privacy disclosure for the diagnostics and telemetry that are on by default. A new contributor can install and build the project from CONTRIBUTING.md, but cannot respond to a production incident or confidently understand the full configuration surface without external resources.

watchWeight x1
Findings

Where to look first

01

highCOD-001Code Quality

Execution engine is a 2874-line God object with broad safety bypasses

workflow-execute.ts carries four file-level eslint-disable directives suppressing no-unsafe-member-access, no-unsafe-assignment, prefer-optional-chain, and prefer-nullish-coalescing across its entire 2874-line body.

Impact

The file blends orchestration, per-node execution, error handling, lifecycle hook dispatch, and event emission in one class. Any change risks unintended side effects in one of the other responsibilities, and the safety bypasses mean TypeScript cannot catch type errors introduced during edits. This is the single file most likely to be the blast radius of a production regression.

Remediation

Progressively decompose the class into focused collaborators (e.g., NodeRunner, ExecutionHookDispatcher, ExecutionErrorHandler). Re-enable the suppressed lint rules one at a time, fixing the underlying unsafe accesses rather than suppressing them. Use the existing test file as a safety net during each extraction.

02

highARC-001Architecture

Manual executions run on main process in queue mode by default

In queue mode, manual and evaluation executions bypass the Bull queue and run synchronously on the main process unless OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS=true is set; the deprecation service warns but does not enforce.

Impact

Under editorial load, manual executions compete with webhook processing, REST API handling, and event bus work on the main process event loop. The main can saturate, increase webhook response latency, and drop editor-UI push events. Memory pressure from large execution payloads on the main is a secondary risk. The deprecation warning makes this visible but does not eliminate the default risk path.

Remediation

Remove the OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS env flag guard in workflow-runner.ts (line 273-276) and always enqueue in queue mode. Follow the TODO comment '@TODO: Reduce to true branch once feature is stable'. Ensure worker concurrency limits are set appropriately and document the change in upgrade notes. Also remove the parallel guard in workflow-execution.service.ts line 235 and test-runner.service.ee.ts line 403.

03

highDAT-001Data

API keys stored as plaintext JWTs — database read grants immediate API access

API keys are JWT tokens generated and stored verbatim in the `user_api_keys.apiKey` column; no hashing or KDF is applied before storage.

Impact

Any attacker with read access to the database (misconfigured backup, SQL injection, compromised read replica, or leaked DB credentials) can immediately use all stored API keys without any additional cracking. JWT API keys carry full programmatic access to the n8n instance including credential read, workflow execution, and user management endpoints. Unlike bcrypt password hashes which require brute force, plain JWTs are ready to use instantly.

Remediation

Store only an HMAC-SHA256 or SHA-256 hash of the API key in the database. Return the plaintext key to the user only at creation time. For lookup during authentication, hash the incoming key and compare against the stored hash. A prefix-index scheme (store first 8 chars plaintext for fast lookup, full hash for verification) avoids full-table scans. A migration can hash existing stored keys, but users must be notified to re-create keys since the originals are irrecoverable.

04

highDAT-002Data

Default credential encryption uses MD5-based key derivation (AES-256-CBC legacy path)

The `Cipher.encrypt()` and `encryptV2()` fallback paths use AES-256-CBC with MD5-based EVP_BytesToKey derivation; the secure GCM path is only active when `N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION=true` AND the encryption-key proxy is configured.

Impact

For the vast majority of self-hosted deployments operating without the feature flag, all credential data at rest uses AES-256-CBC with MD5 key derivation. MD5 is computationally trivial, and the key derivation lacks a proper KDF (no PBKDF2, HKDF, or scrypt), making it vulnerable to brute-force attacks given the ciphertext and a weak or guessable instance encryption key. n8n credentials routinely contain OAuth tokens, database passwords, and third-party API keys. Additionally, CBC mode without an authentication tag cannot detect ciphertext tampering (padding oracle attacks are theoretically possible).

Remediation

Remove the double gate (`N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION` env var AND proxy configured check) and make `encryptV2` the default for all new writes. The AES-256-GCM implementation with HKDF-SHA256 derivation is already present and excellent. Provide an automatic migration that re-encrypts all CBC-encrypted credential rows to GCM on next read (lazy migration) or as a one-time admin operation. Update the instance encryption key derivation to use a proper KDF (PBKDF2 or HKDF) rather than raw SHA-256 for the DEK wrapping key.

05

highSEC-001Security

SSRF protection disabled by default on every deployment

The N8N_SSRF_PROTECTION_ENABLED flag defaults to false in SsrfProtectionConfig, leaving all HTTP-capable nodes able to reach internal network addresses, cloud metadata endpoints, and loopback interfaces without restriction.

Impact

Any workflow author (or attacker who triggers a workflow via public webhook) can make the n8n instance send requests to AWS/GCP/Azure metadata endpoints (169.254.169.254), internal services, or other hosts on the deployment's VPC. In cloud environments this routinely leads to credential theft and lateral movement.

Remediation

Change the default value of `enabled` to `true` in packages/@n8n/config/src/configs/ssrf-protection.config.ts. Provide a migration notice so self-hosted operators with intentional internal-network workflows can opt out via an explicit allow-list (N8N_SSRF_ALLOWED_IP_RANGES / N8N_SSRF_ALLOWED_HOSTNAMES) rather than the current insecure default.

06

highSEC-002Security

Deprecated vm2 sandbox used for in-process JavaScript Code node execution

When the task runner is not configured, the JavaScript Code node falls back to JavaScriptSandbox, which wraps user-supplied code in vm2's NodeVM — a library that has been officially abandoned and has multiple publicly disclosed sandbox-escape CVEs.

Impact

A workflow author (or attacker who can create/modify workflows) can execute arbitrary code outside the sandbox in the main n8n process, gaining full access to the filesystem, environment variables, credentials, and the database connection. This is particularly severe because it runs in the same process context as the credential cipher.

Remediation

Make the task runner the required execution path for JavaScript code (remove or disable the vm2 fallback). If a fallback must be preserved, replace vm2 with isolated-vm (already used in the agent runtime) to maintain consistent security properties. At minimum, add a startup warning when vm2 fallback is active and document the risk prominently.

07

highINF-001Infrastructure

No backup, restore, or disaster-recovery documentation for self-hosted deployments

No backup scripts, restore procedures, or DR runbooks exist anywhere in the repository for either SQLite or PostgreSQL deployments; operators have no recovery path documented.

Impact

A self-hosted operator who loses their database host, suffers disk failure, or needs to restore after a bad migration has no project-provided guidance. SQLite users have no guidance on WAL checkpointing or file snapshot frequency; PostgreSQL users have no guidance on pg_dump schedules, point-in-time recovery, or managed-DB snapshot policies. Data loss becomes the likely outcome of any infrastructure incident without operator-initiated external tooling. This is a material risk for any production deployment storing real user workflows and credentials.

Remediation

Add a docs/operations/backup-restore.md covering: (1) SQLite WAL backup strategy (copy .n8n/database.sqlite with WAL file, recommended backup frequency, restore procedure); (2) PostgreSQL backup via pg_dump or managed snapshot, point-in-time recovery steps; (3) encryption key backup separately from the database (loss of N8N_ENCRYPTION_KEY renders all credentials unrecoverable even with a good DB backup); (4) how to test a restore in a staging environment. A minimal Docker Compose restore example would be highly actionable.

08

highOBS-001Observability

All production observability tools disabled by default

Sentry DSNs, Prometheus metrics, and OpenTelemetry are all opt-in with empty/false defaults; a default self-hosted deployment has no error tracking, no metrics scraping, and no external alerting.

Impact

Self-hosted operators running the default configuration have zero visibility into production failures, error rates, or performance regressions. Incidents will be discovered through user complaints rather than automated detection. This affects the majority of community self-hosted deployments.

Remediation

Add a production observability quickstart guide (docs/operations/observability.md) listing minimum recommended configuration: (1) set N8N_SENTRY_DSN for error tracking, (2) set N8N_METRICS=true and point a Prometheus scraper at /metrics, (3) configure N8N_LOG_FORMAT=json for structured logs. Provide a docker-compose.observability.yml example bundling n8n + Prometheus + Grafana with pre-configured datasources. At startup, emit a structured WARNING log when the instance appears to be in production mode (non-localhost host, non-development NODE_ENV) but all three tools are unconfigured.

09

highOBS-002Observability

No alerting rules, runbooks, or incident response documentation

No Prometheus alert rules, Grafana alert examples, runbooks, escalation paths, or documented alert conditions exist anywhere in the repository for common production failure scenarios.

Impact

Even when Prometheus is enabled by an operator, there are no recommended alert thresholds for execution failure rate spikes, queue saturation, worker offline, database connection failures, or instance downtime. Operators must design their entire alerting strategy from scratch. Production incidents for self-hosted users will consistently go undetected or be detected late.

Remediation

Add docs/operations/alerts.md with recommended Prometheus AlertManager rules covering: (1) n8n instance down (up == 0), (2) execution failure rate > 10% over 5m, (3) queue depth > N for > 5m, (4) last_activity gauge not updated in > 10m. Add a minimal runbook template (docs/operations/runbook-template.md) with sections for detection, diagnosis (what to check in logs/metrics), mitigation, and post-incident review. Reference these from the main README.

10

highOBS-003Observability

Worker/queue health check endpoints disabled by default

QUEUE_HEALTH_CHECK_ACTIVE defaults to false in queue mode, disabling the /healthz endpoint on worker and webhook-processor processes and blocking Kubernetes liveness/readiness probe configuration.

Impact

Kubernetes deployments using workers cannot configure liveness or readiness probes without explicitly enabling the health check. Stuck or deadlocked workers will not be automatically restarted, causing silent execution failures and queue backlogs. This is a common deployment pattern for production n8n installations.

Remediation

Change QUEUE_HEALTH_CHECK_ACTIVE default to true, or document clearly in the worker startup documentation that Kubernetes operators must set QUEUE_HEALTH_CHECK_ACTIVE=true and QUEUE_HEALTH_CHECK_PORT. Add the health check configuration to the Helm chart values with the port and liveness probe pre-configured. Consider binding the health port to localhost-only by default to avoid unintended exposure.

11

highDOC-001Documentation

No operational runbooks for deployment, rollback, or incident response

The repository contains no runbooks for deploying, rolling back, restoring the database, rotating secrets, or responding to production incidents — operators must find docs.n8n.io during an incident.

Impact

Self-hosted operators and contributors who run production instances have no in-repo reference during failures. A database corruption, failed migration, or credential rotation under pressure would require external docs lookup at exactly the moment reliable in-repo context matters most.

Remediation

Add a `docs/operations.md` file with concise instructions for the most critical operational tasks: Docker deploy and rollback, database backup/restore commands, secret rotation steps (N8N_ENCRYPTION_KEY rotation procedure), and cross-links to docs.n8n.io for full detail. Even a one-page pointer guide reduces operational risk significantly.

12

mediumCOD-002Code Quality

No test coverage thresholds — new code can ship without tests

The root jest.config.js and all package-level configs collect coverage when COVERAGE_ENABLED=true but define no coverageThreshold block, so CI never rejects a PR for insufficient coverage.

Impact

Contributors can add new features or change critical paths without adding tests. Coverage metrics improve only as a side effect of voluntary effort, not enforcement. This is especially concerning for sensitive paths (auth, credentials, webhook execution) where a single missed branch can expose a vulnerability.

Remediation

Add a coverageThreshold to jest.config.js (e.g., global branches: 60, functions: 70, lines: 70) and set COVERAGE_ENABLED=true unconditionally in CI. Start with a threshold matching the current measured coverage and ratchet it upward quarterly.

13

mediumCOD-003Code Quality

Broad file-level eslint-disable blocks in the request execution layer

routing-node.ts (15 eslint-disable directives), node-execution-context.ts (11 directives), and related request-helper files carry blanket suppression of no-unsafe-member-access, no-unsafe-assignment, and prefer-nullish-coalescing.

Impact

TypeScript's type safety is effectively switched off in the code paths that execute node HTTP requests, handle credentials, and process response data. Regressions here are invisible to the type checker and only caught by tests — which lack coverage thresholds (see COD-002).

Remediation

Convert blanket file-level disables to narrower line-level suppressions with explanation comments, or better, fix the root cause by adding proper type assertions and null guards. Prioritise the request-helpers directory since it sits on the credential and OAuth execution path.

14

mediumCOD-004Code Quality

Critical helpers are oversized with suppressed complexity violations

node-helpers.ts (2059 lines, 2 complexity suppressions) and interfaces.ts (3933 lines as a single barrel file) are foundational to workflow execution but have known, un-addressed complexity.

Impact

node-helpers.ts mixes parameter resolution, node type introspection, and display logic. The complexity suppressions mean ESLint has explicitly acknowledged that specific functions exceed safe cognitive limits. Developers modifying these files face a high cognitive load and a high risk of unintended breakage across many callsites.

Remediation

Split node-helpers.ts by responsibility domain (parameter utilities, display utilities, node type utilities) and distribute them to narrower modules. Extract sub-interfaces from interfaces.ts into co-located domain files (e.g., execution interfaces near the execution engine, credential interfaces near credentials).

15

mediumARC-002Architecture

ActiveExecutions in-memory state is per-process and not globally consistent

ActiveExecutions tracks running executions as an in-memory map local to each process; in queue mode with multiple mains, each main only knows about executions it enqueued, and a main restart loses all response promises and streaming contexts.

Impact

If the main that enqueued a webhook execution restarts or is replaced by a load balancer, the webhook caller's response promise is orphaned. Streaming responses (SSE) and synchronous webhook responses (responseMode=lastNode) will time out or return a generic error rather than the actual workflow result. In multi-main deployments behind a load balancer without sticky routing, any main restart corrupts in-flight synchronous webhook responses.

Remediation

For synchronous webhook responses, implement sticky routing at the load balancer (route by execution ID or session) so the same main handles both enqueue and response delivery. Alternatively, for the webhook response path, store pending response metadata in Redis so any main can fulfill the response on job completion. Document this limitation prominently in the multi-main deployment guide.

16

mediumARC-003Architecture

Local filesystem execution and binary data storage breaks multi-process deployments

Execution data can be stored on the local filesystem (storedAt='fs' in FsStore) and binary data defaults to local filesystem; in queue mode deployments, workers and mains do not share local storage, making cross-process data reads fail silently.

Impact

A worker that receives a job cannot read binary data written by the triggering main to its local filesystem path, and vice versa. Executions that produce binary output may yield corrupted or missing results. This is a silent failure: the execution appears to succeed but data is inaccessible. Affects any queue-mode deployment that does not explicitly configure S3 or database binary data storage.

Remediation

Add a startup validation check: when executions.mode === 'queue' and binary data mode is 'filesystem' (or execution data storage is 'fs'), log an error and refuse to start (or emit a prominent warning). Document that queue mode requires either database binary storage or a shared storage volume / S3. Update the queue mode setup guide to make this a precondition.

17

mediumARC-004Architecture

WaitTracker timer state is lost on leader failover, delaying execution resumption

WaitTracker uses in-memory setTimeout handles that are cleared when leadership is lost; on leader takeover, it immediately polls the DB for waiting executions, but executions scheduled to fire during the failover window resume up to 60 seconds late.

Impact

Workflows using the Wait node or scheduled resumptions that fire during a leader failover event will resume late. The gap can be up to 60 seconds (the mainTimer interval). In time-sensitive automation workflows (SLA-driven integrations, retry loops with tight deadlines), this can violate business constraints. The 60s poll interval is not configurable through the standard config system.

Remediation

Reduce the WaitTracker poll interval or make it configurable via an environment variable (e.g., N8N_WAIT_TRACKER_POLL_INTERVAL). Alternatively, consider scheduling wait-resumptions as time-delayed Bull jobs on creation, which survive leader failover because they live in Redis. This would eliminate the in-memory timer dependency entirely.

18

mediumDAT-003Data

MFA secrets stored as plaintext strings — TOTP bypass on DB compromise

`User.mfaSecret` is a nullable `varchar` column with no encryption; `mfaRecoveryCodes` is stored as a `simple-array` string with no encryption either.

Impact

An attacker with database read access can extract TOTP secrets and recovery codes for all MFA-enrolled users. TOTP secrets allow generating valid 2FA codes, enabling account takeover even when the attacker does not have the user's password. Recovery codes, once read, allow irrevocable account access. This is especially serious because MFA-enrolled users are typically the most security-conscious (admins, owners).

Remediation

Encrypt `mfaSecret` and `mfaRecoveryCodes` before persistence using the instance encryption key (or the GCM key once that path is the default). Decrypt on read in `mfa.service.ts`. A migration should re-encrypt existing plaintext secrets for already-enrolled users at startup.

19

mediumDAT-004Data

UserRepository.createUserWithProject has an acknowledged race condition — missing transaction

The method explicitly comments `// TODO: use a transactions / This is blocked by TypeORM having concurrency issues with transactions` and proceeds without wrapping user + personal-project + project-relation creation in a single transaction.

Impact

Under concurrent user creation (LDAP sync, mass invitation, automated provisioning), two simultaneous calls can result in: a user with no personal project, a personal project with no project relation, or a second personal project for the same user. Downstream code that assumes every user has exactly one personal project will fail or behave incorrectly, potentially denying the user access to their workflows and credentials.

Remediation

Resolve the TypeORM concurrency issue (likely related to the SQLite pooled connection implementation) and wrap all three inserts in a single transaction. As an interim measure, add a unique partial index on `project.type = 'personal' AND project.creatorId IS NOT NULL` to prevent duplicate personal projects, and retry on constraint violation. Track the upstream TypeORM issue and remove the workaround once resolved.

20

mediumDAT-005Data

Postgres connection pool default of 2 causes exhaustion under modest production load

The default `DB_POSTGRESDB_POOL_SIZE` is 2 connections, shared across all concurrent queries from a given n8n process.

Impact

Any burst of concurrency — webhook fanout, execution pruning, active-workflow polling, bulk API requests — exhausts the two-connection pool immediately. TypeORM queues waiting connections with a 20-second acquire timeout, causing execution timeouts and cascading errors under load. Self-hosted users who copy default configuration hit this limit at very modest workflow volumes. Queue-mode deployments with multiple worker processes each holding 2 connections also risk exhausting Postgres's `max_connections`.

Remediation

Increase the default pool size to at least 10. Add a startup warning when `poolSize <= 2` and `DB_TYPE = postgresdb` outside of test environments. Document in the deployment guide that pool sizing should account for the number of n8n processes sharing the same Postgres server and Postgres's configured `max_connections`.

21

mediumDAT-006Data

Variables stored in plaintext — sensitive values exposed on database read

The `variables.value` column is a `TEXT` column with no encryption; there is no `isSecret` or sensitivity flag to selectively protect variable values.

Impact

Variables are frequently used to store semi-sensitive configuration values such as environment identifiers, internal base URLs, and occasionally API credentials or tokens. Any direct database read — from a backup file, read replica, SQL injection, or misconfigured admin access — exposes all variable values in plaintext. Users relying on variables as a lightweight secrets store (in the absence of an external secrets provider) are particularly exposed.

Remediation

Add an optional `isSecret` boolean column (default `false`) to Variables. When `isSecret = true`, encrypt the value before persistence using the same key as credentials. Surface a 'Secret variable' option in the UI with a warning that the value cannot be retrieved after saving (display-masked). Migration should leave existing variables unencrypted by default; users opt in.

22

mediumDAT-007Data

No backup/restore tooling or runbooks — recovery depends entirely on operator initiative

The repository contains no backup scripts, restore procedures, disaster recovery runbooks, or any automation for database backup across SQLite and PostgreSQL deployments.

Impact

For a self-hosted product, the absence of any backup tooling or documentation means most operators have no verified recovery path. SQLite WAL mode protects against write-crash corruption but does not constitute backup. PostgreSQL users must configure pg_dump, WAL archiving, or managed backup independently. A single misconfigured delete, failed migration with no rollback, or disk failure results in irrecoverable data loss. Given that n8n stores workflows, credentials, and execution history, full data loss would be catastrophic for any production deployment.

Remediation

Ship a `scripts/backup.sh` and `scripts/restore.sh` for both SQLite (via `.backup` command) and PostgreSQL (pg_dump with recommended options). Add a Docker Compose example with automated daily backups. Document recovery procedures for the most likely failure scenarios: failed migration, accidental deletion, and disk failure. Consider implementing pre-migration backup as a safety net in `base-command.ts` before calling `dbConnection.migrate()`.

23

mediumSEC-003Security

OAuth browser binding disabled by default, allowing OAuth token hijacking

N8N_OAUTH_BROWSER_BINDING defaults to false in AuthConfig, meaning the OAuth2 callback is not bound to the browser that initiated the flow, leaving it vulnerable to the OAuth authorization code injection attack.

Impact

An attacker who can craft a link (e.g., via phishing or CSRF) can inject their own OAuth authorization URL into a victim admin's browser and steal the resulting access token into the attacker's credential store. This gives the attacker persistent access to the victim's connected third-party services (GitHub, Google, Slack, etc.).

Remediation

Change the default value of `oauthBrowserBinding` to `true` in packages/@n8n/config/src/configs/auth.config.ts. This is already implemented (OAuthBrowserBindingService exists) — it just needs to be on by default. Provide a migration notice for deployments that may need to disable it temporarily for specific environments.

24

mediumSEC-004Security

No SAST, SCA, or secret-detection in CI pipeline

The sec-ci-reusable.yml file explicitly has placeholder comments for 'dependency-scan' and 'secret-detection' that have not been implemented; the only security scanning in PR CI is Poutine (workflow/action security), leaving code vulnerabilities and accidentally committed secrets undetected.

Impact

Vulnerable dependency versions, injected malicious packages, and accidentally committed credentials (API keys, tokens) reach the main branch and production releases without automated detection. The lefthook pre-commit configuration contains no secret-scanning step either.

Remediation

Add CodeQL or Semgrep for SAST, npm audit or Snyk/socket.dev for SCA, and gitleaks or truffleHog for secret detection. These can be added to the sec-ci-reusable.yml placeholder slots. For pre-commit protection, add a gitleaks hook to lefthook.yml targeting staged files.

25

mediumSEC-005Security

Trivy container scan does not block releases on Critical/High findings

The Trivy scan workflow runs with exit-code 0, meaning the job succeeds even when CRITICAL or HIGH vulnerabilities are found; findings are sent to Slack only, and release workflows are not gated on scan results.

Impact

Container images with known exploitable vulnerabilities can be published to production without automated blocking. An operator relying solely on CI gates would be unaware that a release contains a critical unpatched CVE.

Remediation

Change exit-code to '1' for CRITICAL severity in the Trivy action configuration, or add a separate step that fails the workflow when critical_count or high_count exceeds a threshold. Add the security scan as a required gate in the release workflow (release-publish.yml or docker-build-push.yml).

26

mediumSEC-006Security

Development CORS middleware reflects any origin with credentials, risking misconfigured production exposure

corsMiddleware in packages/cli/src/middlewares/cors.ts echoes any Origin header and sets Access-Control-Allow-Credentials: true with no whitelist; it is conditionally registered only in development, but the condition relies on the NODE_ENV / inDevelopment flag which can be misconfigured.

Impact

If n8n is ever started in a non-production classification (e.g., staging, integration environment, or misconfigured NODE_ENV) that is network-accessible, any origin can make credentialed cross-site requests, trivially bypassing the cookie-based CSRF protection and hijacking authenticated sessions.

Remediation

Replace the blanket reflective CORS with an explicit origin allowlist even in development mode (e.g., localhost on configurable ports). Remove Access-Control-Allow-Credentials from the dev middleware or scope it to the configured front-end URL. Apply the same treatment to the health-check CORS in AbstractServer.

27

mediumSEC-007Security

Content Security Policy disabled by default for the frontend application

SecurityConfig sets contentSecurityPolicy to '{}' (empty object), causing server.ts to call helmet with contentSecurityPolicy: false, meaning no CSP header is sent for the main n8n UI by default.

Impact

Without a CSP, any XSS vulnerability in the frontend (e.g., in node parameter rendering, workflow name display, or third-party script injection) has full browser-side impact — credential theft, session hijacking, and data exfiltration — with no browser-level mitigation.

Remediation

Define a restrictive default CSP in SecurityConfig (e.g., default-src 'self'; script-src 'self'; object-src 'none') and enable it by default. Allow operators to extend it via N8N_CONTENT_SECURITY_POLICY for their embed/iframe use cases. The existing CSP infrastructure in helmet is ready; it just needs a default value.

28

mediumAIE-001AI Engineering

Spec-driven development infrastructure exists but no specs are in use

The n8n:spec-driven-development skill is documented and ready, but .claude/specs/ is empty — all planning occurs through Linear tickets with gitignored plan files that are lost after each session.

Impact

Agents and contributors cannot reference a written spec before implementing non-trivial features or API changes, increasing the risk of scope drift, re-decided architecture choices, and implementation that does not match reviewed intent. Planning decisions in /n8n:plan sessions disappear after the session ends and do not accumulate as institutional context.

Remediation

Adopt spec files for new modules and API changes: (1) add a '.claude/specs/' subdirectory per feature with a short scope, API contract, and TODO checklist before implementation starts; (2) either stop gitignoring .claude/plans/ so plan files persist in the branch, or move plans into a docs/ or .claude/specs/ location that is committed; (3) update the developer agent and /n8n:plan command to write a starter spec file automatically when no spec exists for the feature.

29

mediumAIE-002AI Engineering

Planning artifacts are session-scoped and do not compound as institutional knowledge

The /n8n:plan command saves plans to .claude/plans/ which is gitignored, so implementation rationale, open questions, and design decisions are lost when the Claude Code session ends or the branch is deleted.

Impact

Reviewers and future contributors cannot inspect the planning context that drove a PR's design. Repeated similar features must re-derive decisions from scratch. When an AI-assisted implementation diverges from original intent, there is no written record to audit.

Remediation

Commit plan files alongside the feature branch: remove or narrow the .claude/plans/ gitignore entry to allow committing, or redirect /n8n:plan to write into a committed docs/design/ or .claude/specs/ path. Consider adding an 'Implementation plan' collapsible section to the PR body (the create-pr skill already supports this when the user opts in — make it the default for plans).

30

mediumINF-002Infrastructure

Worker health check endpoints disabled by default, blocking Kubernetes liveness probes

QUEUE_HEALTH_CHECK_ACTIVE defaults to false in ScalingModeConfig, meaning /healthz and /healthz/readiness are unavailable on workers unless explicitly configured, leaving Kubernetes unable to detect unresponsive workers.

Impact

Operators deploying n8n workers to Kubernetes cannot configure meaningful liveness or readiness probes without discovering and setting QUEUE_HEALTH_CHECK_ACTIVE=true through documentation. Without probes, Kubernetes will not restart crashed or deadlocked workers, and the service mesh will continue routing traffic to unhealthy pods. The worker health endpoint also binds to all interfaces (QUEUE_HEALTH_CHECK_PORT=5678, address='::' by default), which means that when enabled by an operator who hasn't read the full config, it may inadvertently bind to public-facing interfaces on bare-metal or VM deployments.

Remediation

Change QUEUE_HEALTH_CHECK_ACTIVE to default true in packages/@n8n/config/src/configs/scaling-mode.config.ts. Change the default bind address from '::' to '127.0.0.1' (or a configurable loopback) so health checks are not unintentionally exposed. Update Kubernetes deployment documentation to include readinessProbe and livenessProbe examples using /healthz/readiness and /healthz respectively.

31

mediumINF-003Infrastructure

Redis queue defaults to unauthenticated plaintext connections in queue mode

QUEUE_BULL_REDIS_TLS defaults to false and QUEUE_BULL_REDIS_PASSWORD defaults to an empty string, meaning any queue-mode deployment using default configuration connects to Redis without authentication or encryption.

Impact

In a cloud VM deployment with an improperly scoped security group, or in any Docker network where Redis port 6379 is accessible beyond the local bridge, job payloads (which contain workflow execution data, potentially including credential values passed as inputs), queue metadata, and pub/sub messages are readable and injectable by any network-adjacent party. Redis without authentication is also trivially exploitable via CONFIG SET to write to the filesystem if the Redis server has write permissions.

Remediation

Document Redis authentication and TLS as required for any non-local-only queue-mode deployment. Provide an example of setting QUEUE_BULL_REDIS_PASSWORD and QUEUE_BULL_REDIS_TLS=true in the deployment documentation and in a canonical production Compose example. Add a startup check: if EXECUTIONS_MODE=queue and Redis password is empty and TLS is off, emit a startup warning.

32

mediumINF-004Infrastructure

Benchmark Compose files ship hardcoded insecure credentials and disabled security controls

Compose files under packages/@n8n/benchmark/scripts/n8n-setups/ use password='password', task runner auth token='test', user: root:root for containers, and explicitly disable N8N_RESTRICT_FILE_ACCESS_TO and N8N_BLOCK_FILE_ACCESS_TO_N8N_FILES; these are discoverable by operators searching the repo for deployment examples.

Impact

An operator who finds these Compose files and uses them as a starting point for a production deployment will inadvertently run n8n as root, with unauthenticated task runner connections, an easily guessed Postgres password, and disabled file-access controls. The postgres container also runs as root. These are intended for internal benchmark use but are not labeled as such in a way that discourages production copying. Copying them disables multiple security layers simultaneously.

Remediation

Add a prominent warning comment at the top of each benchmark Compose file: 'FOR BENCHMARKING ONLY — NOT FOR PRODUCTION USE. Contains hardcoded credentials and disabled security controls.' Change the task runner auth token variable name to something clearly non-default (e.g. BENCHMARK_TASK_RUNNER_TOKEN) so it cannot be silently copied. Consider adding a separate docs/deployment/ directory with a production-safe Compose template that uses environment variable references without defaults.

33

mediumOBS-004Observability

Log format defaults to unstructured text instead of JSON

N8N_LOG_FORMAT defaults to 'text', producing human-readable output that cannot be reliably parsed by log aggregation tools such as Datadog, Loki, Splunk, or Elastic.

Impact

Operators using centralized log management must manually configure JSON format or accept unparsed log strings. Field extraction, log-based metrics, and alert conditions on log content are not possible with the default text format. This gap is especially significant in container environments where stdout is the only log sink.

Remediation

Change the default N8N_LOG_FORMAT to 'json'. Add a changelog entry and migration note for operators who rely on the current text format for human reading. Ensure the JSON format includes consistent required fields (timestamp, level, message, scope/component) documented so operators can write log parsers. Consider auto-detecting container environments (TTY absence) to default to JSON.

34

mediumOBS-005Observability

No request correlation ID in HTTP access logs

No middleware injects a request ID or trace ID into HTTP access logs or response headers, making per-request debugging in production require fragile timestamp-based log correlation.

Impact

When users report an error with a timestamp, operators cannot reliably find the corresponding log entries in multi-instance or high-concurrency deployments. Support incidents take longer to diagnose. Without request IDs, distributed traces cannot be correlated with application logs even when OTel is enabled.

Remediation

Add a middleware early in the Express pipeline that reads X-Request-ID from the incoming request (or generates a UUID), stores it in AsyncLocalStorage, injects it into all log messages for that request, and echoes it in the response via X-Request-ID header. Wire the request ID into OTel trace context so that logs and traces share the same identifier. This is a 20-30 line change using AsyncLocalStorage.

35

mediumOBS-006Observability

Sentry 6-week release expiration silently disables error reporting

ErrorReporter.init() schedules a timer that silently replaces the Sentry captureException handler with a default console logger after the release is 6 weeks old, with only a stdout warning and no metric or alert emitted.

Impact

Self-hosted operators running a release older than 6 weeks lose error tracking without a visible indicator. A Prometheus alert or Sentry alert for 'suddenly zero errors' is ambiguous and easy to miss. Operators may believe their instance is error-free when error reporting has silently stopped, missing critical issues that would have been caught by Sentry.

Remediation

When the expiration fires, emit a structured log at ERROR level (not just warn) with a distinct message that log-based monitors can detect. Additionally, increment a Prometheus gauge (e.g., n8n_error_reporter_active 0) so Prometheus alerts can fire when error reporting is disabled. Consider emitting one final Sentry event via the still-active SDK before disabling it, so a 'last heartbeat' is visible in the Sentry project.

36

mediumOBS-007Observability

No LLM/AI token usage or cost metrics

AI and LangChain node executions are captured as EventMessageAiNode events but no Prometheus metrics exist for token consumption, model-specific error rates, or LLM latency, leaving AI-heavy workflows unmonitorable from an operations perspective.

Impact

Operators running AI workflows cannot monitor token consumption growth, unexpected cost spikes, LLM provider error rates, or per-model latency without custom instrumentation. A runaway AI workflow consuming millions of tokens will not trigger any metric-based alert. This gap will worsen as AI node usage grows.

Remediation

Add Prometheus counters/histograms for AI node executions: n8n_ai_node_tokens_total{provider, model, type} (input/output token counts), n8n_ai_node_errors_total{provider, model}, n8n_ai_node_duration_seconds{provider, model} histogram. Wire these to the existing EventMessageAiNode event flow. Add token count attributes to OTel spans for AI node executions. Coordinate with the LangChain nodes package to extract token usage from LLM responses.

37

mediumDEL-001Delivery

LLM evaluation jobs are non-blocking required checks for an AI-centric product

The Instance AI workflow eval and discovery eval run in CI but are explicitly excluded from required-checks, meaning PRs touching AI code can merge without passing LLM behavioral tests.

Impact

AI capability regressions — broken tool calls, degraded orchestration, prompt failures — can reach release branches undetected. The comment in ci-pull-requests.yml acknowledges this is temporary ('Non-blocking initially; promote to required after stability'), but the promotion has not happened.

Remediation

Add instance-ai-discovery-evals to the required-checks job needs array. For the heavier workflow evals, set a pass threshold (e.g. ≥N cases must pass) and wire it into required-checks, or gate release merges on a nightly full-eval run result. Stabilize flaky cases in the pr tier first so the gate is reliable.

38

mediumDEL-002Delivery

Poutine supply-chain scan only runs on workflow file changes, not on all PRs

The sec-ci-reusable.yml Poutine scan is only triggered when the ci-filter detects changes to .github/**, leaving regular code PRs unscanned for supply-chain issues in workflow configs.

Impact

A PR that modifies a workflow indirectly (e.g. by adding a composite action component or updating a script called from a workflow) without touching .github/ directly will bypass the Poutine supply-chain security gate. An attacker or inadvertent contributor could introduce a workflow injection vector that passes all other checks.

Remediation

Change the security-checks job condition to run unconditionally (remove the `needs.install-and-build.outputs.workflows == 'true'` guard) or expand the workflows path filter to cover .github/actions/** and .github/scripts/** in addition to .github/workflows/**. Poutine runs quickly and the incremental cost is low.

39

mediumDOC-002Documentation

AGENTS.md lists stale package filesystem paths for frontend packages

The Package Structure section of AGENTS.md lists `packages/editor-ui` and `@n8n/design-system` as paths, but the actual filesystem locations are `packages/frontend/editor-ui` and `packages/frontend/@n8n/design-system`.

Impact

AI agents using AGENTS.md as the canonical context file will search for non-existent paths when navigating to frontend code, producing incorrect file references in suggestions and edits. The discrepancy between AGENTS.md and CONTRIBUTING.md (which uses the correct paths) reduces confidence in the context file's accuracy.

Remediation

Update the Package Structure section of AGENTS.md to use the correct filesystem paths: change `packages/editor-ui` to `packages/frontend/editor-ui` and `@n8n/design-system` to `packages/frontend/@n8n/design-system`. Audit remaining path references in the same section for similar issues.

40

mediumDOC-003Documentation

No consolidated environment variable reference for the full configuration surface

The root `.env.local.example` explicitly acknowledges that 'Many more variables exist — search for @Env() decorators in the codebase,' with no consolidated reference and 50+ config files spread across packages/@n8n/config/src/configs/.

Impact

New engineers configuring a development or production environment cannot discover the full set of available options without manually grepping the codebase. AI agents generating configuration guidance will produce incomplete answers. Operators may miss security-relevant variables (SSRF protection, auth settings) that have defaults but should be explicitly reviewed.

Remediation

Create a `docs/configuration.md` reference that lists all major configuration categories with links to the corresponding config files in packages/@n8n/config/src/configs/ and points to the official docs.n8n.io/hosting/configuration page. Alternatively, expand .env.local.example into a comprehensive template with all production-relevant variables grouped by category.

41

mediumDOC-004Documentation

No in-repo privacy disclosure for built-in diagnostics and telemetry

n8n ships with Posthog analytics and an opt-out diagnostics system (N8N_DIAGNOSTICS_ENABLED) enabled by default, but the README and in-repo docs contain no disclosure of what data is collected or how to opt out.

Impact

Self-hosted operators may unknowingly send instance metadata and workflow telemetry without understanding the data collection scope or knowing the opt-out mechanism. This creates a trust gap and may create compliance concerns for operators in regulated industries who deploy n8n without reading the external privacy policy.

Remediation

Add a brief 'Data collection' section to the README that explains what diagnostics n8n collects by default, references N8N_DIAGNOSTICS_ENABLED=false as the opt-out mechanism, and links to the full privacy policy at n8n.io. Alternatively, document this in docs/configuration.md.

42

lowCOD-005Code Quality

Dependency version fragmentation tracked but not yet resolved

The .code-health-baseline.json records 148 catalog violations: change-case in 5 packages across 3 versions, jsdom in 4 packages across 2 versions, tmp-promise in 4 packages across 2 versions, and similar fragmentation for 15+ other dependencies.

Impact

Multiple versions of the same package inflate the install footprint, can produce subtle behavioural differences between packages that use different versions, and increase the surface area for supply-chain compromises. The stale-overrides violations on the root package.json compound this by partially duplicating catalog entries.

Remediation

Work through the catalog violations in .code-health-baseline.json systematically: pin each flagged dependency to catalog: in pnpm-workspace.yaml, remove the stale overrides from the root package.json, then zero out the baseline entry. The baseline mechanism already enforces no new violations, so this is a cleanup-only effort.

43

lowCOD-006Code Quality

ts-jest isolatedModules disables cross-file type-checking in tests

The root jest.config.js sets isolatedModules: true in tsJestOptions.tsconfig to speed up test runs, which prevents ts-jest from catching cross-module type errors that the standalone pnpm typecheck step would catch.

Impact

Tests can pass while containing type errors that would fail the separate typecheck CI step, creating a false sense of type safety during local test runs. Developers who skip pnpm typecheck before committing are unprotected.

Remediation

This trade-off is well-documented in the config and acceptable given that typecheck is enforced in CI. No immediate action required, but document explicitly in AGENTS.md that local `pnpm test` does not substitute for `pnpm typecheck`.

44

lowARC-005Architecture

ScalingService couples core queue infrastructure to a node implementation package

ScalingService.setupQueue() imports McpServer, QueuedExecutionStrategy, and RedisSessionStore from @n8n/n8n-nodes-langchain/mcp/core, creating a hard dependency between execution infrastructure and a specific node-type package.

Impact

Any change to the MCP node package's public API can silently break the queue setup path. The dependency direction violates the clean architecture principle where infrastructure packages should not depend on feature packages. It also prevents deploying a queue-mode worker without including the langchain node package, increasing worker Docker image size and attack surface unnecessarily.

Remediation

Introduce an MCP-specific infrastructure service (e.g., McpSessionStoreSetup) in the CLI package that owns session store initialization and is conditionally registered. Inject it into ScalingService via an interface or leave the wiring in a dedicated MCP module initializer. Keep ScalingService.setupQueue() free of node-type imports.

45

lowARC-006Architecture

Inter-process job message protocol versioning is managed ad-hoc

The job-finished Bull progress message has an inline version===2 check in the main listener; backward compatibility between mains and workers is managed through scattered conditionals rather than a formal compatibility contract.

Impact

During a rolling deploy where mains and workers run different versions simultaneously, mismatched message formats can cause silently dropped or misrouted completion events. If a worker sends a version 2 message to an older main (or vice versa), the result is not stored in jobResults and the execution may get fetched from the DB unnecessarily or, in edge cases, finalization may be skipped.

Remediation

Define an explicit versioned message schema for the job completion protocol (e.g., using Zod). Add a parse-and-validate step at the message receiver boundary, and document the minimum compatible worker/main version combination. Consider a compatibility matrix in the deployment docs.

46

lowDAT-008Data

Execution data stored unencrypted — workflow outputs may contain sensitive information

`ExecutionData.data` is a `TEXT` column storing the full serialized execution run data including all node inputs and outputs in plaintext.

Impact

Workflow executions frequently process sensitive data: OAuth token responses, PII from CRM or marketing integrations, API responses containing secrets, and database query results. This data is stored in plaintext and appears in database backups, read replicas, and any direct DB access. For compliance-sensitive deployments (GDPR, HIPAA, SOC 2), unencrypted execution data is a significant liability.

Remediation

Document this limitation clearly in the security documentation. Add an opt-in execution data encryption setting (`N8N_ENCRYPT_EXECUTION_DATA`) that encrypts `ExecutionData.data` using the instance key before storage. Provide a data sanitization/redaction option for known sensitive field patterns. At minimum, ensure the existing redaction service (`execution-redaction.service.ts`) is applied before persistence for sensitive node types.

47

lowDAT-009Data

Credentials list eagerly loads three JOIN levels by default — amplifies query cost

The default `defaultRelations` in `CredentialsRepository.toFindManyOptions` always fetches `shared → shared.project → shared.project.projectRelations` for every credential list call.

Impact

On instances with many credentials and large team projects, each credential list request triggers a multi-level JOIN that grows with the number of project members. This can result in excessive memory allocation in the Node.js process for large result sets, and puts unnecessary load on the database. The pattern also prevents TypeORM from generating an efficient single query, often resulting in N+1 sub-queries for deeply nested relations.

Remediation

Load `projectRelations` lazily (only when needed for permission checks) rather than defaulting all list calls to include them. Provide a separate method or option that includes `projectRelations` only for ownership-resolution paths. Consider caching project membership lookups at the service layer.

48

lowDAT-010Data

Variables entity lacks unique constraint on key per project — duplicate variable keys possible

`Variables.key` is an unindexed `TEXT` column with no uniqueness constraint at the DB level, neither globally nor per-project.

Impact

Concurrent or sequential inserts can create multiple variables with the same key within the same project scope. Application code reading variables by key will receive an arbitrary row, leading to non-deterministic behavior in workflow execution. The absence of a DB-level unique index means the constraint can only be enforced in application code, which is not atomic under concurrent writes.

Remediation

Add a unique partial index on `(projectId, key)` WHERE `projectId IS NOT NULL`, and a separate unique partial index on `(key)` WHERE `projectId IS NULL` for global variables. This ensures uniqueness at the DB level and makes the enforcement atomic.

49

lowSEC-008Security

No pre-commit secret-scanning hook to prevent credential commits

lefthook.yml configures pre-commit hooks for formatting, styles, actionlint, workspace deps, and migration timestamps, but contains no secret-scanning step; credentials accidentally staged for commit are not caught before they enter git history.

Impact

API keys, database passwords, or JWT secrets accidentally pasted into configuration files or test fixtures will be committed and pushed to the public repository, requiring rotation even if removed in a later commit (git history retains them permanently).

Remediation

Add a gitleaks or truffleHog pre-commit hook to lefthook.yml targeting staged files. Example: run: gitleaks protect --staged --redact with the glob: '*'. This is cheap to run and catches the most damaging class of supply-chain secrets exposure.

50

lowSEC-009Security

SOC2 control documentation, incident response, and vendor risk management absent from repository

No security policy documents, incident response playbooks, vendor security review evidence, or SOC2 control mappings are present in the repository; change management relies entirely on CODEOWNERS and PR process without documented control objectives.

Impact

For operators seeking SOC2 Type II certification or enterprise compliance audits, the lack of documented controls, incident procedures, and evidence capture creates a significant audit gap. Security incidents also lack a defined response playbook, potentially extending mean-time-to-containment.

Remediation

Create a security/ or docs/security/ directory containing: (1) incident response playbook with escalation paths, (2) vulnerability disclosure and patching SLA, (3) access control and least-privilege policy, (4) data classification and retention policy, (5) vendor/third-party security review checklist. Even a lean version substantially improves SOC2 readiness.

51

lowAIE-003AI Engineering

Skill telemetry hook sends developer fingerprints without explicit opt-out documentation

track-skill-usage.mjs runs as a PostToolUse hook on every n8n-namespaced skill invocation and sends a SHA-256 fingerprint of (username+hostname+platform+arch+release) to telemetry.n8n.io silently in the background.

Impact

Developers who have not read the telemetry script may not be aware their machine identity is being hashed and transmitted. On contractor machines, air-gapped environments, or strict data-residency setups, silent telemetry from a developer toolchain hook is unexpected and may violate policy.

Remediation

Add a one-time disclosure in .claude/README.md or the onboarding docs stating that skill usage (skill name + anonymized device fingerprint) is sent to n8n telemetry. Optionally add an N8N_CLAUDE_TELEMETRY_DISABLED env-var check in track-skill-usage.mjs that allows engineers to opt out without modifying the shared settings file.

52

lowAIE-004AI Engineering

No knowledge lifecycle for documenting and compounding AI workflow learnings

The team has no retrospective practice, decision record format, or mechanism for recording when AI approaches work well or poorly — learnings from completed AI-assisted work are not fed back into AGENTS.md or skills.

Impact

Effective prompting patterns, recurring misunderstandings, and known AI failure modes in this codebase are held in individual heads rather than in the context files agents and new contributors will read. Over time, the gap between documented guidance and actual effective practice widens.

Remediation

Introduce a lightweight knowledge loop: (1) add a brief 'AI notes' section to the PR template for capturing surprising AI behaviors, agent misunderstandings, or prompting techniques that worked; (2) schedule a periodic review (quarterly or per release) of AGENTS.md to incorporate recurring corrections; (3) consider adding a .claude/retrospectives/ directory (committed) for freeform session notes.

53

lowAIE-005AI Engineering

AI delivery documentation lacks structured fields for AI-specific risks and limitations

The PR template and create-pr skill produce well-structured PRs with human accountability checkboxes and AI authorship labels, but no section captures AI-specific concerns such as confidence level, known edge-case gaps, or whether generated code was validated manually.

Impact

Reviewers have no signal about which parts of a PR were AI-generated versus hand-written, whether the AI's assumptions were validated, or what known limitations the author is aware of. This makes it harder to calibrate review depth for AI-assisted PRs.

Remediation

Add an optional 'AI assistance notes' section to .github/pull_request_template.md covering: (1) which parts were AI-generated, (2) how the output was validated (ran tests, reviewed manually, etc.), (3) any known limitations or assumptions not yet verified. The create-pr skill can prompt the author to fill this in when AI involvement is detected.

54

lowINF-005Infrastructure

Custom DHI base image (dhi.io/node) used in n8n-base is not publicly auditable

docker/images/n8n-base/Dockerfile builds FROM dhi.io/node:24.16.0-alpine3.22-dev, a proprietary internal image distribution; this base is not available on Docker Hub and cannot be independently inspected by operators or security researchers.

Impact

Third parties auditing the n8n Docker image supply chain cannot verify the contents of the DHI base layer independently. While the final published n8n image is scanned with Trivy and attested with SLSA L3 provenance, the DHI base layer is a trust boundary that requires operators to trust DHI without a public audit path. This complicates enterprise procurement security reviews and SOC2 vendor assessments.

Remediation

Document what DHI is (internal n8n image distribution) and why it is used instead of the public node:alpine image in the n8n-base Dockerfile or a linked document. Provide a verification path: the SBOM and Trivy scan results cover the final image layers and should include any DHI-sourced packages. If operators need a fully auditable build path, document how to rebuild n8n-base from the public node:24.16.0-alpine3.22 image.

55

lowOBS-008Observability

No SLO/SLA definitions or measurement infrastructure

No SLO targets, uptime commitments, error budgets, or measurement sources are defined in the repository; Prometheus metrics infrastructure exists to measure SLIs but nothing is wired up.

Impact

Without defined SLIs/SLOs, there is no objective basis for evaluating whether the service meets reliability expectations. Customer-facing commitments (if any exist for n8n Cloud) cannot be verified programmatically and have no alerting backstop. This makes proactive reliability engineering impossible.

Remediation

Define minimal SLIs using existing Prometheus metrics as measurement sources: availability (up == 1 ratio), success rate (workflow executions succeeded / total), and P95 request latency. Add Prometheus recording rules to precompute these. Document targets (e.g., 99.5% availability, <1% execution failure rate) in docs/operations/slo.md. For n8n Cloud, ensure a status page references these targets.

56

lowOBS-009Observability

PostHog analytics API key hardcoded in default configuration

DiagnosticsConfig has a hardcoded PostHog project API key and telemetry endpoint as default values, meaning all self-hosted instances send product analytics by default unless explicitly disabled.

Impact

Self-hosted operators who have not read the privacy documentation will unknowingly send product telemetry to n8n's PostHog instance. While N8N_DIAGNOSTICS_ENABLED=false can disable this, it requires explicit operator action and the hardcoded key is visible in the open-source repository, which may raise compliance concerns in regulated environments.

Remediation

Move the PostHog key and telemetry endpoint to environment variables with empty-string defaults. Require operators to explicitly opt-in to diagnostics by setting N8N_DIAGNOSTICS_ENABLED=true and providing their own or n8n's PostHog key. Add a clear notice in the startup logs when diagnostics are enabled, stating what data is collected and where it is sent. This aligns with privacy-by-default principles.

57

lowDEL-003Delivery

No operator-facing rollback runbook for self-hosted deployments

n8n ships as Docker images with immutable SHA tags and stable/beta/v1 track tags, but there is no documented rollback procedure for operators in the repo (e.g. how to pin to a previous image, how to handle migration rollback).

Impact

Operators who upgrade to a broken release may not know they can roll back by pinning the previous SHA-tagged image. If a migration has run, the absence of guidance on forward-fix vs schema rollback increases recovery time during incidents.

Remediation

Add a ROLLBACK.md or section in the existing docs describing: (1) how to identify the previous SHA-tagged image for each release, (2) the safe window before which rolling back is migration-safe, (3) the forward-fix path when a migration has already run, and (4) how to use the stable/beta track pointers for controlled upgrades. Link from the main README and CONTRIBUTING.md.

58

lowDEL-004Delivery

Feature flag system lacks in-repo lifecycle management and cleanup discipline

PostHog is the declared feature flag system (AGENTS.md), but there is no in-repo flag registry, no naming convention enforcement, no documented removal timelines, and no CI check for stale flags.

Impact

Flags accumulate without cleanup, making it harder to reason about which code paths are active. Dead flags guarded by stale PostHog checks become invisible technical debt, and operators relying on N8N_EXPERIMENT_OVERRIDES have no authoritative list of valid flag names.

Remediation

Create a feature-flags.md or add a flags section to AGENTS.md that lists active flags, their purpose, and expected removal milestone. Add a lint rule or CI check (e.g. a simple script) that fails if a flag name referenced in code has no corresponding entry in the registry. Establish a convention (e.g. flags older than two minor releases with no removal PR are flagged in code review).

59

lowDOC-005Documentation

No architecture decision records for major system choices

No ADR directory exists; decisions such as the SQLite/PostgreSQL dual-database strategy, fair-code license model, DI framework choice, and Bull/Redis queue architecture are not documented with rationale or tradeoff notes.

Impact

Contributors evaluating architectural changes cannot understand why existing approaches were chosen, making it harder to assess whether a proposed change challenges a deliberate decision or corrects an oversight. The agent-runtime-architecture.md pattern of inline design decisions is good but not systematically applied.

Remediation

Create a `docs/decisions/` directory and write ADRs for the highest-impact architectural choices. Start with: database strategy (SQLite default + PostgreSQL for production), queue architecture (Bull/Redis), the fair-code license, and the DI framework. The agent-runtime-architecture.md design-decisions section is a good template to follow.

60

lowDOC-006Documentation

README is user/marketing-focused with no contributor orientation

The root README.md contains product marketing copy, a quick-start via npx, and resource links, but no guidance on how to contribute, understand, or modify the codebase — contributors must discover CONTRIBUTING.md and AGENTS.md independently.

Impact

First-time contributors who land on the README have no immediate path to the contributor documentation. AI tools that consume the README as primary context will miss the architectural and development patterns documented in CONTRIBUTING.md and AGENTS.md.

Remediation

Add a 'Contributing / Development' section to README.md with direct links to CONTRIBUTING.md (setup), AGENTS.md (AI-agent context), docs.n8n.io (full docs), and a one-line summary of the monorepo structure. This requires minimal changes and significantly improves discoverability.

Book a Call