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:
| Scope | What cnspec reads with it |
|---|---|
workflow:list | Workflows, their nodes, and their settings |
credential:list | Stored credentials |
user:list | Users, their role, and their multi-factor status |
tag:list | Tags |
communityPackage:list | Installed community node packages |
securityAudit:generate | The 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_KEYEnvironment 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_KEYWhen these are set, you can omit the URL and the --api-key flag from the commands below.
Connection options
| Option | Description |
|---|---|
[url] | URL of the n8n instance, for example https://n8n.example.com |
--url | URL of the n8n instance, an alternative to passing it as an argument |
--api-key | n8n 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_KEYcnspec> 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_KEYThe 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_KEYList 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: trueEnsure no public chat is open to anyone
cnspec> n8n.workflows.where(active == true).all(nodes.none(public == true && authentication == "none"))
[ok] value: trueEnsure HTTP requests verify TLS certificates
cnspec> n8n.workflows.all(nodes.none(allowUnauthorizedCerts == true))
[ok] value: trueEnsure every active user has multi-factor authentication
cnspec> n8n.users.where(isPending == false).all(mfaEnabled == true)
[ok] value: trueEnsure every credential is in use
cnspec> n8n.credentials.all(workflows.length > 0)
[ok] value: trueEnsure community packages can't be installed
cnspec> n8n.instanceSettings.communityPackagesEnabled == false
[ok] value: falseEnsure 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: trueEnsure installed community packages are current
cnspec> n8n.communityPackages.all(updateAvailable == "")
[ok] value: trueEnsure every installed community package loads
cnspec> n8n.communityPackages.none(failedLoading == true)
[ok] value: trueEnsure 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: trueLearn more
- n8n Resource Pack Reference: every n8n resource and field cnspec can query
- Write Effective MQL: guide to authoring checks and queries