# n8n Security and Workflow Hygiene Audit (`fractionalhqforyou/n8n-instance-hygiene-auditor`) Actor

Find hard-coded secrets, open webhooks, duplicates and missing error paths in n8n. Get ranked findings through its read-only API. Secret values stay out of results. Free demo; $0.005 per workflow audited.

- **URL**: https://apify.com/fractionalhqforyou/n8n-instance-hygiene-auditor.md
- **Developed by:** [Jessy Mariau](https://apify.com/fractionalhqforyou) (community)
- **Categories:** Developer tools, Automation, Integrations
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 workflows

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## n8n Security and Workflow Hygiene Audit

Find the n8n workflows that need attention before the next handover or production change. This read-only audit returns findings by workflow, their severity and the five fixes to tackle first.

### What the audit returns

![Dated audit of Fractional HQ own n8n instance with workflow counts and prioritised fixes](https://fractionalhq.uk/shots/n8n-instance-hygiene.webp)

Own-instance example, **6 September 2026**: 88 workflows, score 64/100, no secret findings in that run. This earlier result does not describe the current state of that instance or yours.

The free demo exercises the checks against **six fictional workflows**. Selected fields from one demo finding:

| Field | Demo value |
|---|---|
| workflow | Lead scraper (copy) |
| check | secrets |
| severity | high |
| node | Score leads |
| parameter | jsCode |
| pattern | openai\_style\_key |

No matched secret is included. The full row contains the finding under `findings`, alongside other checks.

**Start free:** leave `n8nApiKey` empty. For a live audit, add your instance URL and a key with read access. Set `maxWorkflows` to cap the number charged.

| Setup detail | What to expect |
|---|---|
| API access | Workflows and executions; credentials where that endpoint is available |
| Missing credential endpoint | Credential checks are skipped; other checks continue |
| Node versions | Compared with versions in your instance, not a global catalogue |
| Network | Your instance must be reachable from Apify; localhost is not your computer |

### What it does

You give it your instance URL and an n8n API key. It reads every workflow through the official n8n public API, never writes anything, and hands back one flat row per workflow plus an instance summary row.

Each workflow row carries the name, id, active flag, node count, trigger type, tags, last execution, and a list of findings. Every finding has a severity of high, medium or low and one plain sentence saying what is wrong. These are the checks:

| Check | What it catches |
|---|---|
| `secrets` | credential-shaped strings sitting in node parameters: sk- and Stripe keys, Slack tokens, GitHub tokens, AWS key ids, bearer tokens, JWTs, database connection URIs with a password in them, and any `api_key` or `password` field assigned a long literal. A JWT is classified from its own claims, not its shape. |
| `webhook_auth` | Webhook trigger nodes with authentication set to none, and whether the path is a guessable word or a random one. |
| `duplicates` | identical names, names that differ only by a copy or version suffix, and workflows whose node graph is identical to another one. |
| `dormant` | workflows switched on with no executions at all, workflows switched on that have not run in `staleDays`, and workflows switched off and untouched for over a year. |
| `error_path` | no error workflow set in the settings and no node handling a failure, so a break tells nobody. |
| `error_swallowing` | nodes set to carry on past a failure with nothing running after them, so the failure vanishes without trace. |
| `pinned_data` | pinned editor data still attached to a live workflow, the classic works-in-the-editor-lies-in-production fault. |
| `node_versions` | community node types, and nodes running an older typeVersion than the same node type elsewhere on your instance. |
| `credentials` | at instance level: how many credentials you hold, by type, how many are shared with nobody, and how many no workflow references. |

#### A JWT gets read, not guessed at

A JWT payload is base64url, so anyone can decode it without a key, and it tells you what the token is for. I had the scanner guessing from the shape instead. It does not any more. When it matches a JWT it decodes the payload and classifies the token from the claims. The severity comes from what those claims say, not from the string starting with `eyJ`.

| What the claims say | Reported as | Severity |
|---|---|---|
| `role: anon` with a project `ref` | Supabase anon key | low, and the finding says plainly that this key is public by design and fenced in by row-level security. |
| `role: service_role` | Supabase service-role key | high, because it bypasses row-level security completely. |
| `iss: n8n` or `aud: public-api` | n8n public API key | high, because it grants read and write across the instance, which includes every other workflow and every secret sitting in one. |
| anything else | unrecognised JWT | medium, and it says the claims name no service rather than inventing one |

I read `exp` as well. An expired token is reported as expired and drops to low, because it opens nothing. A token with no expiry claim at all gets that said out loud, since a full-privilege key that never lapses on its own is worth knowing about.

The classification comes back on the finding as `pattern`, with `role`, `iss`, `aud` and the expiry state under `claims`. Those four are the only things I let out of the decoder, and the token itself is dropped the moment the classification is made.

#### The secret check never prints your secret

This is the part I want you to be able to trust before you point it at a production instance. When the scanner matches something, the finding it emits carries four things: the workflow, the node name, the parameter path, and the class of pattern that matched. On a JWT it adds the four claims above. The matched text is never captured, never returned and never logged. There is no truncated preview and no first-six-characters hint, and decoding a JWT to classify it does not change that: the payload is read, the classification is kept, the token is dropped.

The Actor ships with a test that plants a fake key of every shape it knows into a workflow, runs the full audit, and fails if any fragment of any planted value appears anywhere in the output. The free demo lets you inspect the redacted findings without connecting your instance.

### What you need

An n8n API key with read access. Create one in n8n under Settings, then n8n API. Nothing else: no proxy, no browser, no second tool. Apify stores the key as a secret and it never appears in the log or the dataset.

Run it with no key and you get a demo audit of six sample workflows, nothing charged, so you can see the output shape before you connect anything real.

### How to run it

A full audit of everything:

```json
{
  "n8nBaseUrl": "https://n8n.example.com",
  "n8nApiKey": "your-n8n-api-key",
  "staleDays": 90,
  "includeInactive": true,
  "maxWorkflows": 500
}
```

Just the security half, on live workflows only:

```json
{
  "n8nBaseUrl": "https://n8n.example.com",
  "n8nApiKey": "your-n8n-api-key",
  "checks": ["secrets", "webhook_auth", "error_swallowing", "pinned_data"],
  "includeInactive": false
}
```

Leave `includeInactive` on for a first run. Switched-off workflows are exactly where the duplicates and the forgotten keys accumulate, and mine were no exception.

The `OUTPUT` record in the key-value store holds the instance summary: the score, the totals per check and per severity, the credential picture and the top 5 things to fix first.

### Output fields

| Field | What it holds |
|---|---|
| `workflow`, `workflow_id` | as named in n8n. |
| `active`, `archived` | live flag, and whether n8n has it archived. |
| `created_at`, `updated_at` | from the workflow record. |
| `node_count` | executable nodes, sticky notes excluded. |
| `trigger` | schedule / webhook / form / error / sub-workflow / manual / chat / app-event / none. |
| `tags` | the workflow's tags. |
| `last_execution` | id, status and start time of the most recent run, or null when none is retained. |
| `findings` | every finding: `check`, `severity`, a one-line `reason`, plus `node`, `parameter` and `pattern` where they apply. |
| `findings_count`, `highest_severity` | the row's totals, for sorting. |
| `duplicate_group_id` | workflows sharing a group id are copies of each other. |
| `row_type` | `instance_summary` on the last row, absent on workflow rows. |
| `hygiene_score`, `score_band` | on the summary row. |
| `findings_by_check`, `findings_by_severity` | on the summary row. |
| `credentials` | on the summary row: total, by type, shared with nobody, referenced by no workflow. |
| `error_trigger_workflow_exists` | on the summary row: whether the instance has an Error Trigger workflow anywhere. |
| `top_fixes` | on the summary row: the 5 things to do first, in plain sentences. |

### The score

The score is a rate, not a count, so a large instance is not condemned for being large. It starts at 100 and takes off a share of each weight based on what proportion of your workflows carry a finding at that severity.

| Component | Weight | How it is applied |
|---|---|---|
| workflows with a high finding | 55 | multiplied by the share of audited workflows that have one |
| workflows with a medium finding | 25 | same |
| workflows with a low finding | 10 | same |
| no Error Trigger workflow anywhere | 8 | flat, once, at instance level |
| credentials no workflow uses | 5 | multiplied by their share of all credentials |

| Band | Score |
|---|---|
| HEALTHY | 80 to 100 |
| NEEDS WORK | 50 to 79 |
| AT RISK | below 50 |

The score helps sort configuration findings. It does not certify that an instance is secure. Review high-severity findings even when the overall score is healthy.

### Pricing

Pay per event: one `workflow-audited` charge per workflow actually audited, and nothing else. Every check runs inside that one charge, so switching more of them on does not cost more. A demo run charges nothing. `maxWorkflows` is your ceiling: set it to 100 and you are charged for at most 100 workflows however many the instance holds.

At $0.005 per workflow, 100 workflows cost $0.50 in Actor events. Auditing those same workflows four times costs $2. Each scheduled audit is charged again.

These examples describe Actor event charges. Any additional platform or proxy charges shown by Apify are separate. Review run costs before scheduling repeated checks.

### Limits

- It is read-only. It never changes, activates, deactivates or deletes anything on your instance.
- It audits what the n8n API exposes, and different n8n versions expose different things. `GET /credentials` in particular is not on every version. Where it is missing, the credential section is skipped and the rest of the audit runs as normal.
- Secret detection is pattern based everywhere except JWTs. It finds shapes, not meanings. A key in a format I have never seen will slip through. So will one split across two fields, or built at runtime. It misses in the other direction too: a long random string that merely looks like a token gets flagged. Treat a finding as a place to look rather than a verdict.
- A JWT is the exception, because its payload is readable. The claims are whatever the issuer wrote, so I report a token that names no service as unclassified rather than guessing at it. I never check a signature and never call the issuer to confirm anything.
- It is not a penetration test. It reads configuration; it does not send a request to your webhooks, try your credentials or probe anything from outside.
- It is not legal or compliance advice.
- Outdated nodes are judged against the newest version of that same node type in use on your own instance, because the API does not publish the version catalogue. A node type you only use once cannot be judged that way and is not flagged.
- By default it refuses a loopback or private-range URL, which would be pointing it at Apify's own network rather than yours. Set `allowPrivateNetwork` if you genuinely mean it.

### Pair it with a workflow

Run this quarterly and the [Silent-Success Watchdog](https://apify.com/fractionalhqforyou/n8n-silent-success-auditor) daily. This one tells you the estate is built badly; that one tells you a workflow stopped producing while still reporting success. Between them you have the two failures that never show up in the executions list.

The reasoning behind the checks, and what they cannot see, is at https://fractionalhq.uk/use-cases/n8n-instance-hygiene

### Use it from an AI agent

Run this audit from an AI agent by adding the Actor as an MCP server, the way I do from mine. The endpoint is `https://fractionalhqforyou--n8n-instance-hygiene-auditor.apify.actor/mcp`, with one tool, `audit_n8n_instance`, that connects to your instance and returns the same findings the dataset would hold. The per-event price stays the same for an agent call. Clients that only accept the Apify MCP server can use `apify/actors-mcp-server` with `fractionalhqforyou/n8n-instance-hygiene-auditor` named in its Actor list.

### Related Actors I publish

An instance audit is one layer of an ops check, and these Actors cover the layers around it.

- [Silent-Success Watchdog for n8n](https://apify.com/fractionalhqforyou/n8n-silent-success-auditor): catches n8n workflows that report success while producing nothing.
- [Vendor Policy and Subprocessor Change Watch](https://apify.com/fractionalhqforyou/vendor-policy-change-watch): tells you when a vendor quietly changes its terms or its subprocessor list.
- [E-Invoice Validator](https://apify.com/fractionalhqforyou/eu-einvoice-validator): validates XRechnung, ZUGFeRD and Peppol invoices before they bounce.

### Help and maintenance

Built and maintained by [Jessy at Fractional HQ](https://fractionalhq.uk). For a bug, open this Actor's Issues tab with the run ID and expected result. Remove keys and private data before sharing an input.

Need the results connected to your process? [Describe the workflow](https://fractionalhq.uk/brief).

# Actor input Schema

## `n8nBaseUrl` (type: `string`):

The base URL of your n8n instance, e.g. https://n8n.example.com (no trailing path). Leave the example value and no API key to get a demo dataset.

## `n8nApiKey` (type: `string`):

Create one in n8n under Settings > n8n API. Read access to workflows, executions and credentials is enough. Without it the Actor runs in demo mode and charges nothing.

## `staleDays` (type: `integer`):

How many days an active workflow can go without running before it is reported as dormant.

## `checks` (type: `array`):

Leave all of them selected for a full audit, or narrow it down.

## `includeInactive` (type: `boolean`):

Audit workflows that are switched off as well. They are where duplicates and archive candidates hide, so this is on by default.

## `maxWorkflows` (type: `integer`):

Stop after this many workflows. One charge per workflow audited, so this is also your cost ceiling.

## `allowPrivateNetwork` (type: `boolean`):

By default the Actor refuses to point at localhost or a private IP range. Switch this on only if you are deliberately auditing an instance on such an address.

## Actor input object example

```json
{
  "n8nBaseUrl": "https://your-n8n.example.com",
  "staleDays": 90,
  "checks": [
    "secrets",
    "webhook_auth",
    "duplicates",
    "dormant",
    "error_path",
    "error_swallowing",
    "pinned_data",
    "node_versions",
    "credentials"
  ],
  "includeInactive": true,
  "maxWorkflows": 500,
  "allowPrivateNetwork": false
}
```

# Actor output Schema

## `findings` (type: `string`):

One row per workflow: name, id, active, node count, trigger, tags, last execution, duplicate group, and every hygiene finding with its severity and a plain one-line reason. The last row is the instance summary.

## `summary` (type: `string`):

The OUTPUT record: hygiene score out of 100, score band, totals per check and per severity, credential hygiene, and the top 5 things to fix first.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "n8nBaseUrl": "https://your-n8n.example.com"
};

// Run the Actor and wait for it to finish
const run = await client.actor("fractionalhqforyou/n8n-instance-hygiene-auditor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "n8nBaseUrl": "https://your-n8n.example.com" }

# Run the Actor and wait for it to finish
run = client.actor("fractionalhqforyou/n8n-instance-hygiene-auditor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "n8nBaseUrl": "https://your-n8n.example.com"
}' |
apify call fractionalhqforyou/n8n-instance-hygiene-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fractionalhqforyou/n8n-instance-hygiene-auditor"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/u9vq2Y40lNoQfjanX/builds/QuMDEVFBhfosr8MnY/openapi.json
