SaaS

Secure n8n with cnspec

Scan n8n instances for unauthenticated webhooks, insecure HTTP requests, unused credentials, risky community packages, and users without multi-factor authentication with cnspec.

An n8n instance connects your business systems to each other, so it holds credentials for many of them and runs workflows that anyone who can reach a webhook may be able to trigger. cnspec reads an n8n instance through its Public API and evaluates the workflows and their nodes, the webhooks and chat triggers that accept outside requests, the stored credentials and the workflows that use them, the users and their role and multi-factor status, the installed community node packages, the instance security settings, and the findings of n8n's built-in security audit.

cnspec never reads credential secrets. The Public API returns only each credential's name, type, and timestamps.

If you're new to cnspec, start with the Quickstart. For an overview of every SaaS platform cnspec can scan, see the SaaS scanning overview.

Prerequisites

To scan n8n with cnspec, you must have:

  • cnspec installed on your workstation
  • An n8n instance, self-hosted or on n8n Cloud, with the Public API enabled (it's enabled unless the instance sets N8N_PUBLIC_API_DISABLED)
  • An n8n API key

Authenticate

In n8n, go to Settings > n8n API and select Create an API key. If your plan lets you choose the key's scopes, grant the read scopes cnspec uses:

ScopeWhat cnspec reads with it
workflow:listWorkflows, their nodes, and their settings
credential:listStored credentials
user:listUsers, their role, and their multi-factor status
tag:listTags
communityPackage:listInstalled community node packages
securityAudit:generateThe built-in security audit and instance settings

If the key lacks a scope, queries for that data return an access denied error that names the missing scope, rather than an empty result.

Pass the instance URL and the key to cnspec:

cnspec shell n8n https://n8n.example.com --api-key YOUR_API_KEY

Environment variables

N8N_URL supplies the instance URL and N8N_API_KEY supplies the key:

export N8N_URL=https://n8n.example.com
export N8N_API_KEY=YOUR_API_KEY

When these are set, you can omit the URL and the --api-key flag from the commands below.

Connection options

OptionDescription
[url]URL of the n8n instance, for example https://n8n.example.com
--urlURL of the n8n instance, an alternative to passing it as an argument
--api-keyn8n API key

Verify with a quick n8n check

Confirm that cnspec can reach your instance by opening a cnspec shell:

cnspec shell n8n https://n8n.example.com --api-key YOUR_API_KEY
cnspec> n8n { url apiKeyScopes }
n8n: {
  url: "https://n8n.example.com"
  apiKeyScopes: [
    0: "workflow:list"
    1: "credential:list"
    2: "user:list"
    3: "tag:list"
    4: "communityPackage:list"
    5: "securityAudit:generate"
  ]
}

If cnspec connects and lists the key's scopes, you're ready to scan.

Scan n8n

cnspec scan n8n https://n8n.example.com --api-key YOUR_API_KEY

The instance is a single asset, identified by its host. There are no child assets to discover.

When a scan completes, cnspec prints a summary of all the checks it ran, grouped by policy, along with a risk score from 0 (no risk) to 100 (highest risk). Failed checks include remediation guidance to help you fix issues. To learn more about reading scan results, read Understand cnspec Results.

Mondoo doesn't yet ship an out-of-the-box n8n policy, so use the checks below as a starting point and create your own policies to meet your specific requirements.

Explore and test checks interactively

Open a cnspec shell to discover resources and try out checks:

cnspec shell n8n https://n8n.example.com --api-key YOUR_API_KEY

List workflows and whether they're active

active is true when a published version of the workflow runs on its triggers:

cnspec> n8n.workflows { name active isArchived triggerCount updatedAt }

Review the nodes in a workflow

cnspec> n8n.workflows.where(name == "Sync CRM") { nodes { name type disabled } }

Find workflows with a webhook that requires no authentication

A webhook left on its default authentication counts too, since the provider reports the node's default when the workflow doesn't set one:

cnspec> n8n.workflows.where(nodes.any(type == "n8n-nodes-base.webhook" && authentication == "none")) { name active }

Find workflows with a public chat anyone can use

cnspec> n8n.workflows.where(nodes.any(public == true && authentication == "none")) { name }

Find HTTP requests that skip TLS certificate verification

requestHost is null when the URL is built from an expression, since it's only known when the workflow runs:

cnspec> n8n.workflows { name nodes.where(allowUnauthorizedCerts == true) { name requestHost } }

Review which workflows each credential reaches

cnspec> n8n.credentials { name type workflows { name } }

Find credentials no workflow uses

An unused credential is a stored secret nobody needs:

cnspec> n8n.credentials.where(workflows.length == 0) { name type updatedAt }

List users, their role, and their multi-factor status

cnspec> n8n.users { email role isPending mfaEnabled }

Review the installed community node packages

Community nodes aren't vetted by n8n and run with full access to the host:

cnspec> n8n.communityPackages { name installedVersion updateAvailable nodeTypes }

Review the instance security settings

These come from the instance's environment, for example N8N_COMMUNITY_PACKAGES_ENABLED and NODES_EXCLUDE:

cnspec> n8n.instanceSettings {
    communityPackagesEnabled
    publicApiEnabled
    templatesEnabled
    versionNotificationsEnabled
    diagnosticsEnabled
    excludedNodes
    includedNodes
  }

Review the findings of the built-in security audit

n8n computes the audit when cnspec reads it. Each report covers one risk category: credentials, database, filesystem, instance, or nodes:

cnspec> n8n.securityAudit.reports { risk sections { title recommendation } }

Example security checks

Ensure no webhook accepts unauthenticated requests

cnspec> n8n.workflows.where(active == true).all(nodes.none(type == "n8n-nodes-base.webhook" && authentication == "none"))
[ok] value: true

Ensure no public chat is open to anyone

cnspec> n8n.workflows.where(active == true).all(nodes.none(public == true && authentication == "none"))
[ok] value: true

Ensure HTTP requests verify TLS certificates

cnspec> n8n.workflows.all(nodes.none(allowUnauthorizedCerts == true))
[ok] value: true

Ensure every active user has multi-factor authentication

cnspec> n8n.users.where(isPending == false).all(mfaEnabled == true)
[ok] value: true

Ensure every credential is in use

cnspec> n8n.credentials.all(workflows.length > 0)
[ok] value: true

Ensure community packages can't be installed

cnspec> n8n.instanceSettings.communityPackagesEnabled == false
[ok] value: false

Ensure the Execute Command node stays excluded

Setting NODES_EXCLUDE replaces n8n's default exclusions rather than adding to them, so check the list itself:

cnspec> n8n.instanceSettings.excludedNodes.contains("n8n-nodes-base.executeCommand")
[ok] value: true

Ensure installed community packages are current

cnspec> n8n.communityPackages.all(updateAvailable == "")
[ok] value: true

Ensure every installed community package loads

cnspec> n8n.communityPackages.none(failedLoading == true)
[ok] value: true

Ensure active workflows have an error workflow

An error workflow is how a failed run gets noticed:

cnspec> n8n.workflows.where(active == true).all(errorWorkflow != null)
[ok] value: true

Learn more

On this page