n8n Workflow Auditor - Linter & Security Review avatar

n8n Workflow Auditor - Linter & Security Review

Pricing

from $0.20 / workflows audited

Go to Apify Store
n8n Workflow Auditor - Linter & Security Review

n8n Workflow Auditor - Linter & Security Review

Audit n8n workflows for hardcoded credentials, unauthenticated webhooks, expression-injection exposure, missing error handling, deprecated nodes, and unreachable branches. Paste workflow JSON or connect an n8n instance and get one ranked finding per row, plus a health score out of 100 per workflow.

Pricing

from $0.20 / workflows audited

Rating

0.0

(0)

Developer

Mediocre_Interest

Mediocre_Interest

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

n8n Workflow Auditor — Linter & Security Review for Workflow JSON

Paste an n8n workflow — or point the Actor at your own n8n instance — and get a ranked list of security, reliability, and correctness findings, one row per issue, plus a health score out of 100 per workflow.

It catches the things that only show up in production: an API key typed into a node parameter instead of a credential, a webhook anyone can call, a typeVersion two majors behind, a branch that no trigger can reach, and a workflow with no error handling anywhere.

  • Batch. Audit one pasted workflow, a list of exports, or every workflow on an instance in a single run.
  • Read-only. Against an instance it calls GET /api/v1/workflows and nothing else, and n8n returns credentials as name references without their secret values.

Rules, not a model

Every finding comes from a deterministic rule written in TypeScript — there is no LLM anywhere in the audit path. The same workflow produces the same findings, in the same order, on every run: nothing drifts between runs, nothing needs re-prompting, and no finding is a judgment call you have to take on faith.

That determinism is what makes the Actor safe to gate a build on — a run that passes today passes tomorrow on the same input.

How to audit an n8n workflow

Give the Actor workflows in any one of three ways — pasted JSON, a URL, or a live instance — or combine all three in one run. Leave Rule set and Minimum severity at their defaults for the first run; narrowing before you have seen the output usually hides the finding you were looking for.

Audit pasted JSON

  1. In n8n, open the workflow and use the Workflow menu → Download. To audit only part of it, select those nodes on the canvas and press Ctrl+C instead — n8n copies them as JSON.
  2. Paste it into Workflow JSON. For several workflows at once, wrap them as {"workflows": [ ... ]} — an n8n export:workflow bundle already has that shape.
  3. Click Start. A single workflow takes a few seconds.

Audit workflows by URL

  1. Put one or more links in Workflow JSON URLs — a raw file in a Git repo, a Gist, or an n8n template link. See Auditing workflows by URL for the exact URL shapes accepted.
  2. Click Start. Each URL is fetched and audited independently; one that returns several workflows is audited — and billed — as several.

Audit a live n8n instance

  1. Create an n8n API key under Settings → n8n API — see Auditing a live instance for the required scopes and n8n plan requirements.
  2. Fill in n8n instance URL and n8n API key. Optionally set Workflow IDs to audit only specific workflows, or turn on Active workflows only to skip deactivated ones.
  3. Click Start. The Actor only reads: it calls GET /api/v1/workflows and nothing else. An instance sweep takes about as long as its slowest page of workflows.

Reading the results

  1. Open the report from the run's Output tab for the whole audit on one page, or read the Workflow scores table first if you would rather work in the data — one row per workflow, sorted by health score.
  2. Work the Findings table top down. It is sorted most severe first, and every row carries the exact node, the parameter path, and a remediation.
  3. Suppress what you have decided to live with. Copy the rule value from any row into Ignore rules and the next run will not report it again.

What it checks

Twenty rules across six categories. Every finding carries its rule ID, so you can suppress it with ignoreRules on the next run.

Secrets and credentials

RuleSeverityWhat it catches
secret-in-parameterscriticalAWS, Slack, OpenAI, Anthropic, GitHub, Google, Stripe, SendGrid, Twilio keys, JWTs and private-key blocks typed into node parameters, plus high-entropy values under names like apiKey or password.
manual-auth-headerhighAn Authorization or X-API-Key header set literally on an HTTP Request node instead of through a credential.

Security

RuleSeverityWhat it catches
expression-sandbox-escapecriticalconstructor.constructor, process.env, require(), globalThis inside an expression (CVE-2025-68613, CVE-2026-1470).
form-expression-injectionhighUnauthenticated Form input interpolated into a downstream expression (CVE-2026-27493, patched in n8n 1.123.22 / 2.9.3 / 2.10.1).
webhook-no-authhighA Webhook node with no authentication and no validator node wired directly to it.
sql-expression-injectionhighAn expression interpolated into an Execute Query field instead of using query parameters.
code-node-network-egressmediumfetch, axios, require, or a hardcoded URL inside a Code node — traffic no credential governs.

Reliability

RuleSeverityWhat it catches
no-error-handlingmediumNo error workflow, no Error Trigger, and no node with retry or on-error set, while nodes call external services. Reported once per workflow.
loop-without-exit-branchmediumA Loop Over Items node on a cycle whose "done" output is not connected.
http-no-timeoutlowAn HTTP Request node with no timeout.
deprecated-continue-on-faillowThe continueOnFail flag n8n replaced with onError.
missing-error-workflowmedium / infoNo error workflow set. medium for a workflow on an instance; info for pasted JSON.
http-no-paginationinfoA call to what looks like a list endpoint with no pagination configured.

Correctness

RuleSeverityWhat it catches
unreachable-nodemediumA node no trigger can reach, so it never runs.
no-trigger-nodemediumA workflow with no trigger at all.
dangling-connectionmediumA connection pointing at a node that is not in the workflow.
disabled-node-on-critical-pathlowA disabled node sitting between two connected nodes.

Versions and supply chain

RuleSeverityWhat it catches
deprecated-nodehighA node type n8n has hidden from the panel — cron, function, readBinaryFile and 42 others.
stale-type-versionlow / mediumA node behind the current major type version. One major behind is low, two or more is medium.
unknown-node-typemedium / highA community node dependency. Escalates to high when the package is unverified or abandoned.

Input

Ten fields, all optional. Give it workflows in any one of three ways — you can combine them:

{
// 1. Paste a workflow. For several, wrap them: { "workflows": [ ... ] }
"workflowJson": { "name": "My workflow", "nodes": [], "connections": {} },
// 2. Fetch exports from URLs - a Gist, a file in a Git repo
"workflowUrls": ["https://example.com/workflow.json"],
// 3. Pull from your own n8n instance
"n8nBaseUrl": "https://n8n.example.com",
"n8nApiKey": "<your n8n public API key>",
"workflowIds": [], // empty = every workflow on the instance
"activeOnly": true, // only what is actually running
// Tuning
"ruleSet": "all", // all | security | reliability | maintainability
"minSeverity": "low", // info | low | medium | high | critical
"ignoreRules": ["http-no-timeout"],
}

There is nothing else to configure. The node index refreshes itself from n8n on every run, and community-node trust signals are looked up for whichever packages your workflows actually use.

Auditing workflows by URL

workflowUrls takes any URL that returns workflow JSON — the response does not have to be served as application/json, so a raw file in a Git repo works as-is. These are all accepted directly:

URLWhat it returns
https://raw.githubusercontent.com/<owner>/<repo>/<branch>/<path>.jsonA workflow exported from the n8n editor, committed to a repo.
https://gist.githubusercontent.com/<user>/<id>/raw/<file>.jsonThe same, from a Gist. Use the Raw URL, not the Gist page.
https://api.n8n.io/api/workflows/templates/<id>One workflow from n8n's public template library — the endpoint n8n's own Import from URL uses.
https://api.n8n.io/api/templates/workflows/<id>The same template with its listing metadata around it.
https://<host>/api/v1/workflowsA saved response from an n8n instance. Prefer n8nBaseUrl for a live one — it paginates and unlocks the instance-only rules.

A URL that returns several workflows — an n8n export:workflow --all --output bundle, or a { "workflows": [...] } array — is fine too; each one is audited and billed separately. A URL that fetches but contains no workflow is logged and skipped rather than failing the run.

The three sources are additive. If the same workflow arrives from more than one, it is audited once — the instance copy wins over a URL, which wins over pasted JSON — and every skipped duplicate is named in the run log. You are never billed twice for one workflow.

Auditing a live instance

Create the API key under Settings → n8n API; it needs the workflow:list and workflow:read scopes. The public API is a paid n8n feature and is unavailable during the free trial.

n8nApiKey is marked secret: stored encrypted, redacted from the run log, and never written to a dataset.

Output

Findings — one row per issue

Default dataset, most severe first:

{
"fingerprint": "8db3cb38c79e",
"workflowId": "customer-intake",
"workflowName": "Customer intake",
"workflowSource": "json",
"scope": "node",
"nodeId": "3",
"nodeName": "Enrich",
"nodeType": "n8n-nodes-base.httpRequest",
"nodeTypeVersion": 3,
"nodeDisabled": false,
"rule": "secret-in-parameters",
"ruleTitle": "Credential hardcoded in node parameters",
"category": "secrets",
"severity": "critical",
"severityRank": 4,
"message": "\"headerParameters.parameters[0].value\" on \"Enrich\" holds what looks like an OpenAI API key.",
"remediation": "Move the value into an n8n credential and reference it from the node…",
"evidence": "Bear…<redacted, 63 chars>",
"parameterPath": "headerParameters.parameters[0].value",
"docsUrl": null,
"auditedAt": "2026-08-31T10:13:50.621Z"
}
  • fingerprint is a stable id for the finding, built from the workflow, the rule, the node and the parameter — not from the message or the evidence. Diff two runs on this field to see only what changed.
  • severityRank is severity as a number, 4 down to 0. Sort on it rather than on severity, whose names sort alphabetically.
  • scope is node or workflow. Three rules report against the workflow as a whole — no error handling, no error workflow, no trigger — and their node fields are all null.
  • nodeDisabled marks a finding against a node that is switched off.

Workflow scores — one row per workflow

summaries dataset, with a health score out of 100, node and credential counts, and counts by severity. Read its Workflow scores view first on a multi-workflow run to see which workflow to open in the findings table.

credentialCount and credentialNames answer "what does this workflow have access to?" — names only, never a secret and never the credential id.

Every row carries the runId that produced it, along with the ruleSet and minSeverity the run used. This table is retained across runs, so check those two before comparing scores: a run narrowed to critical scores higher than a full one on the same workflow.

Run totals

Key-value store — summary-run: workflows audited, skipped and deduplicated; average score; findings by severity and by rule; the rule set and severity floor; whether the node index came from n8n live or from the bundled snapshot; any rule that failed; any warning the input raised; reportRequested, reportGenerated and reportUrl; and instanceError if the n8n instance could not be read.

HTML report — optional

Switch on HTML audit report to get report.html in the key-value store: the whole run as one page, every workflow's score followed by the findings grouped by severity. It appears as Audit report (HTML) on the run's Output tab, and its link is also on the run summary as reportUrl and in the run log.

It is billed as its own event, once per run, on top of the per-workflow charge, so it is off unless you ask for it. A run that leaves it off writes no report at all; every finding and every score still lands in the datasets.

How the score works

A workflow starts at 100 and loses points for each finding, weighted by severity:

SeverityPoints off
critical30
high15
medium6
low2
info0

100 means nothing above info was found, and 0 is the floor. info findings never cost anything, and no single rule can deduct more than 45 points however many times it fires. Use the score to rank workflows against each other and to track one over time; for the precise picture, read the finding count and severity breakdown next to it.

Evidence is always masked

Every value that could be a credential is reduced to a short prefix and its length (sk-p…<redacted, 51 chars>) before it reaches the output — enough to find the key in your workflow, never enough to use it.

Pricing

The Actor is pay per event, and there are two events:

EventChargedWhen
workflow-auditedOnce per workflowAfter that workflow's findings are written
report-generatedOnce per run, only if you switched on HTML audit reportAfter the report reaches the key-value store

The cost of a run is the number of workflows in it, plus one if you asked for the report: auditing one pasted workflow is one charge; sweeping an instance of 40 is 40. Current per-event prices are on this Actor's Store page.

Running it from n8n

Install the official Apify node, add a Run Actor step, and pass the workflow JSON as input. Paired with a Schedule Trigger, that gives you a recurring audit of an entire instance — with results landing back in n8n through the Actor Run Finished trigger.

Calling it from the API

A single audit takes a few seconds, so the Actor can run synchronously — one request in, findings out, no polling:

curl -X POST "https://api.apify.com/v2/acts/mediocre_interest~n8n-workflow-auditor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"workflowJson": {
"name": "Incoming leads",
"nodes": [
{ "name": "Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2,
"position": [0, 0], "parameters": { "path": "incoming" } }
],
"connections": {}
},
"minSeverity": "medium"
}'

The response body is a JSON array of the finding rows shown under Output. Add &format=csv to get a spreadsheet instead, or &fields=severity,rule,nodeName,message to trim the columns.

For a longer sweep of a whole instance, start the run asynchronously with POST /v2/acts/.../runs and read the dataset when the run finishes, or let the Apify node inside n8n do both for you.

For a CI gate built on this endpoint, see Using this as a CI gate.

Using this as a CI gate

Every finding comes from a deterministic rule, so a build that passes today passes tomorrow on the same input — there is no model behind it to drift, and no run-to-run variance to chase.

Call the sync endpoint on every pull request with the changed workflow's JSON, and fail the build if anything comes back critical. $WORKFLOW_FILE below is the workflow JSON exported for the pull request; $APIFY_TOKEN is your Apify API token:

RESULT=$(curl -s -X POST "https://api.apify.com/v2/acts/mediocre_interest~n8n-workflow-auditor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H 'Content-Type: application/json' \
-d "$(jq -n --slurpfile wf "$WORKFLOW_FILE" '{workflowJson: $wf[0]}')")
if echo "$RESULT" | jq -e 'any(.[]; .severity == "critical")' > /dev/null; then
echo "$RESULT" | jq '[.[] | select(.severity == "critical") | {rule, nodeName, message}]'
exit 1
fi

To fail only on new findings instead of every pre-existing critical — useful once a workflow already carries findings the team has accepted — cache the previous run's fingerprint values and gate on any that are not in that cache:

NEW=$(comm -13 <(sort previous-fingerprints.txt) <(echo "$RESULT" | jq -r '.[].fingerprint' | sort))
if [ -n "$NEW" ]; then
echo "New finding(s) since the last run: $NEW"
exit 1
fi
echo "$RESULT" | jq -r '.[].fingerprint' | sort > previous-fingerprints.txt

For a scheduled sweep of a whole instance instead of a per-PR check, use Apify's own scheduler to point the Actor at your instance — see Auditing a live instance.

FAQ

Does this send my workflows to an LLM?

No. Every finding comes from a deterministic rule, which is why the same workflow produces identical findings on every run.

Can it see my credentials?

No. n8n's public API returns per-node credentials as { id, name } references with the secret values stripped, so they never reach the Actor. Your n8nApiKey is stored encrypted, redacted from the run log, and never written to a dataset. Evidence for a key found hardcoded in a parameter is masked to a prefix and a length before it reaches the output.

Does it change my workflows?

No. The Actor only ever reads. Against an instance it calls GET /api/v1/workflows and nothing else.

How is this different from n8n's built-in n8n audit?

n8n's audit is instance-scoped, needs owner credentials, and cannot read a JSON file, so it cannot run in CI or across a portfolio of client exports. It covers five categories; this Actor covers twenty rules across six, adding hardcoded secrets, error handling, typeVersion staleness, graph reachability, HTTP hygiene, and the Form-node expression-injection CVE. Where the two overlap, this Actor mirrors n8n's own definitions so the verdicts agree.

Why did the run finish with no findings?

Three possibilities. Your minimum severity is above anything the run could report — reliability has no rule more severe than medium and maintainability none above high, so pairing either with high or critical filters out everything, and the run log warns you when that happens. Or your ignore rules cover what was found. Or the workflow really is clean, in which case the summaries row records a score of 100.

Can I run it in CI or on a schedule?

Yes — see Using this as a CI gate for CI. On a schedule, use Apify's scheduler to point the Actor at your instance; the summaries table keeps a row per workflow per run, so a score moving down is a regression you can act on.

What happens if n8n's API is down?

The audit still runs and its verdicts do not change. Deprecation and version rules use n8n's node index, which the Actor fetches fresh at the start of every run and otherwise falls back to a bundled snapshot of all 572 built-in nodes. The run log says which one was used.

I switched on the report but the Output tab shows nothing

The findings and scores are already in the datasets by the time the run finishes — the report can take a little longer to reach the key-value store. Refresh the run's page in Console. If it is still missing after that, check reportGenerated and reportUrl in the run summary to confirm whether it was written at all.

What other Actors work with this one?

ActorWhat it does
n8n Node Catalog & Community Package ScraperCatalogs every n8n node — built-in, verified, and community — with npm provenance, license, weekly downloads, and maintenance risk for each package.
Ad Library Scraper — Google, Meta & LinkedInScrapes ads from the Google Ads Transparency Center, Meta Ad Library and LinkedIn Ad Library in one run — ad copy, creatives, impressions and run dates.
Multi-Platform Job ScraperCollects job listings from LinkedIn, Indeed, Glassdoor, ZipRecruiter, Greenhouse, Lever and more into a single dataset.

The node catalog above is a natural companion if you also want an inventory of every node in play across your instance. The other two are the kind of Actor an n8n workflow calls on a schedule through the Apify node — and this one audits the workflow doing the calling.

Support

Found a false positive, a rule that should exist, or a workflow shape the parser mishandles? Open an issue on the Actor's Issues tab in Apify Console.

When reporting a finding you disagree with, include the rule ID from the row and the smallest workflow JSON that reproduces it.