n8n-mcp-community 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -1
- package/CONTRIBUTING.md +12 -4
- package/README.md +28 -23
- package/SECURITY.md +6 -7
- package/THIRD_PARTY_NOTICES.md +6 -5
- package/dist/introspect/contracts.js +42 -17
- package/dist/security/redaction.js +93 -17
- package/dist/tools/common.js +0 -29
- package/dist/tools/credentials.js +85 -31
- package/dist/tools/definition.js +67 -25
- package/dist/tools/executions.js +39 -20
- package/dist/tools/output-contracts.js +490 -0
- package/dist/tools/response-contracts.js +238 -0
- package/dist/tools/schemas.js +15 -4
- package/dist/tools/tags.js +20 -12
- package/dist/tools/users.js +39 -18
- package/dist/tools/utilities.js +89 -24
- package/dist/tools/workflows.js +372 -64
- package/dist/version.js +1 -1
- package/docs/architecture.md +10 -2
- package/docs/assets/demo.gif +0 -0
- package/docs/clients.md +8 -7
- package/docs/compatibility.md +13 -10
- package/docs/demo-transcript.md +5 -4
- package/docs/faq.md +5 -4
- package/docs/getting-started.md +4 -4
- package/docs/installation.md +17 -17
- package/docs/provenance.md +4 -2
- package/docs/security-model.md +26 -6
- package/docs/tools.md +42 -17
- package/docs/troubleshooting.md +4 -3
- package/package.json +10 -3
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,90 @@ All notable changes to this project will be documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.3] - Frozen release candidate
|
|
10
|
+
|
|
11
|
+
Version 0.1.3 is a frozen release candidate. Availability is established only
|
|
12
|
+
by matching npm provenance, GitHub Release assets and signatures, and MCP
|
|
13
|
+
Registry readbacks; this immutable entry is not rewritten after publication.
|
|
14
|
+
|
|
15
|
+
### Security
|
|
16
|
+
|
|
17
|
+
- MCP SDK 1.30.0 now permits the patched `@hono/node-server` 2.x line in a
|
|
18
|
+
clean standalone downstream install, so the temporary package-root override
|
|
19
|
+
is removed. The no-override consumer gate now rejects every non-zero
|
|
20
|
+
production audit and every `@hono/node-server` release below 2.0.5 or outside
|
|
21
|
+
major version 2.
|
|
22
|
+
- Published 0.1.2 installations remain pinned to MCP SDK 1.29.0 and do not gain
|
|
23
|
+
this corrected downstream resolution. Its former clean-consumer closure path
|
|
24
|
+
is superseded: upgrade to 0.1.3 only after npm, GitHub, and MCP Registry
|
|
25
|
+
readbacks all verify that release.
|
|
26
|
+
- The reviewed lockfile resolves `fast-uri` 3.1.5 for
|
|
27
|
+
[GHSA-7p8r-x3mc-p8w7](https://github.com/advisories/GHSA-7p8r-x3mc-p8w7)
|
|
28
|
+
(high), `hono` 4.13.0 for
|
|
29
|
+
[GHSA-8j4g-w8fx-2239](https://github.com/advisories/GHSA-8j4g-w8fx-2239)
|
|
30
|
+
(moderate), and `ip-address` 10.4.0 for
|
|
31
|
+
[GHSA-mwp4-54f8-5fhr](https://github.com/advisories/GHSA-mwp4-54f8-5fhr)
|
|
32
|
+
(high),
|
|
33
|
+
[GHSA-4xrf-jv44-h6hh](https://github.com/advisories/GHSA-4xrf-jv44-h6hh)
|
|
34
|
+
(moderate), and
|
|
35
|
+
[GHSA-22jq-vg5j-6vgg](https://github.com/advisories/GHSA-22jq-vg5j-6vgg)
|
|
36
|
+
(moderate). Development-only `brace-expansion` 5.0.9 closes
|
|
37
|
+
[GHSA-mh99-v99m-4gvg](https://github.com/advisories/GHSA-mh99-v99m-4gvg)
|
|
38
|
+
and
|
|
39
|
+
[GHSA-rgw5-rvv9-x895](https://github.com/advisories/GHSA-rgw5-rvv9-x895),
|
|
40
|
+
both high severity.
|
|
41
|
+
- The published 0.1.2 MCPB vendors the earlier advisory-flagged lockfile and
|
|
42
|
+
should be upgraded only after the 0.1.3 external readbacks agree. The npm
|
|
43
|
+
tarball does not bundle transitives, so a fresh 0.1.2 npm install currently
|
|
44
|
+
re-resolves the three runtime packages to patched releases; development-only
|
|
45
|
+
`brace-expansion` is absent from that consumer graph. Its separate MCP SDK
|
|
46
|
+
1.29.0 / `@hono/node-server` residual remains until the upgrade.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- Downstream dependency verification now fails closed on any advisory instead
|
|
51
|
+
of retaining the former exact 1.x backport exception.
|
|
52
|
+
- The public synthetic demo now renders 0.1.3 in every version-bearing frame;
|
|
53
|
+
a version history anchored to the previous annotated release tag must extend
|
|
54
|
+
without deletion or rewriting and use a unique GIF digest per release. It
|
|
55
|
+
joins the frame-review, timing, dimension, transcript, and digest checks to
|
|
56
|
+
reject a version-only bump that silently leaves the animation stale.
|
|
57
|
+
|
|
58
|
+
## [0.1.2] - Frozen release candidate
|
|
59
|
+
|
|
60
|
+
Version 0.1.2 is a frozen release candidate. Availability is established only
|
|
61
|
+
by matching npm provenance, GitHub Release assets and signatures, and MCP
|
|
62
|
+
Registry readbacks; this immutable entry is not rewritten after publication.
|
|
63
|
+
|
|
64
|
+
### Security
|
|
65
|
+
|
|
66
|
+
- The transitive `fast-uri` dependency is updated past
|
|
67
|
+
GHSA-v2hh-gcrm-f6hx, and `@hono/node-server` is overridden to the patched
|
|
68
|
+
2.x line for GHSA-frvp-7c67-39w9. The affected HTTP adapter is unused by
|
|
69
|
+
this stdio-only server. The project and bundled MCPB trees audit clean, but
|
|
70
|
+
npm does not inherit dependency-package overrides: a disposable downstream
|
|
71
|
+
consumer resolves the reviewed 1.19.15 backport through MCP SDK 1.29.0.
|
|
72
|
+
The gate accepts only the exact known advisory or a fully clean advisory
|
|
73
|
+
readback while registry metadata converges, and closure still requires a clean
|
|
74
|
+
no-override audit of the published 0.1.2 consumer.
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
77
|
+
|
|
78
|
+
- All 44 MCP-visible tool definitions now state their specific purpose, safe
|
|
79
|
+
usage boundary, named sibling alternatives, behavior, and return semantics.
|
|
80
|
+
- Every top-level input and output field now publishes a meaningful JSON Schema
|
|
81
|
+
description, including the dedicated `n8n_introspect` result contract.
|
|
82
|
+
- Every generic tool now publishes its own required `data` result contract, and
|
|
83
|
+
an exact normalized `tools/list` metadata fingerprint prevents semantic drift.
|
|
84
|
+
- Workflow create/update now express the two-number node position as a bounded
|
|
85
|
+
homogeneous JSON Schema array. Runtime validation is unchanged, while Codex
|
|
86
|
+
can expose both tools instead of rejecting the tuple-form `items` array.
|
|
87
|
+
- Fork pull requests use a complete keyless contributor gate. Only trusted
|
|
88
|
+
maintainer pushes can authenticate and approve an exact artifact baseline.
|
|
89
|
+
- Installation documentation no longer treats a source-tree version as proof
|
|
90
|
+
that npm, GitHub, signatures, provenance, or Registry publication occurred.
|
|
91
|
+
- Private Glama review captures are excluded from the public Git boundary.
|
|
92
|
+
|
|
9
93
|
## [0.1.1] - 2026-07-21
|
|
10
94
|
|
|
11
95
|
### Security
|
|
@@ -130,6 +214,8 @@ All notable changes to this project will be documented here. The format follows
|
|
|
130
214
|
credentials, keys, and generated release artifacts cannot survive in that
|
|
131
215
|
publishable history; authenticated public release receipts remain supported.
|
|
132
216
|
|
|
133
|
-
[Unreleased]:
|
|
217
|
+
[Unreleased]: #unreleased
|
|
218
|
+
[0.1.3]: #013---frozen-release-candidate
|
|
219
|
+
[0.1.2]: #012---frozen-release-candidate
|
|
134
220
|
[0.1.1]: https://github.com/drzamarian/n8n-mcp-community/releases/tag/v0.1.1
|
|
135
221
|
[0.1.0]: https://github.com/drzamarian/n8n-mcp-community/releases/tag/v0.1.0
|
package/CONTRIBUTING.md
CHANGED
|
@@ -21,14 +21,22 @@ Node.js 22 and 24 are the supported development lines.
|
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
npm ci
|
|
24
|
-
npm run
|
|
24
|
+
npm run verify:contributor
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
The
|
|
28
|
-
TypeScript, the full automated test suite,
|
|
29
|
-
production
|
|
27
|
+
The keyless contributor gate covers formatting, dependency licenses and notices,
|
|
28
|
+
strict TypeScript, the full automated test suite, real stdio inventories, package
|
|
29
|
+
and MCPB construction, a disposable downstream-consumer audit, and the production
|
|
30
|
+
build. It deliberately does not authenticate or approve the maintainer's exact
|
|
31
|
+
artifact baseline.
|
|
30
32
|
Run `npm run sbom > sbom.cdx.json` when reviewing dependency changes.
|
|
31
33
|
|
|
34
|
+
Pull requests from forks run only this keyless gate; GitHub does not expose the
|
|
35
|
+
operator-held artifact-approval secret to fork workflows. After review, the
|
|
36
|
+
maintainer regenerates the exact baseline, approves it interactively, and runs
|
|
37
|
+
`npm run verify`. Release and publication workflows accept only that keyed path;
|
|
38
|
+
the contributor gate cannot authorize an artifact, tag, or release.
|
|
39
|
+
|
|
32
40
|
## Branch and commit conventions
|
|
33
41
|
|
|
34
42
|
- Create a focused branch from `dev`: `feat/<topic>`, `fix/<topic>`,
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**A security-focused Model Context Protocol server for self-hosted n8n Community Edition.**
|
|
4
4
|
|
|
5
|
-
Connect an MCP client to
|
|
5
|
+
Connect an MCP client to a **bounded 44-tool n8n Community Edition management surface**: create, edit,
|
|
6
6
|
activate, and delete workflows, update individual nodes surgically, and manage
|
|
7
7
|
executions, credentials, tags, users, diagnostics, and instance metadata — 44
|
|
8
8
|
carefully bounded tools in total. Safety here is **progressive, not
|
|
@@ -11,12 +11,12 @@ and `unsafe` unlocks every tool with an exact per-call confirmation phrase for
|
|
|
11
11
|
each destructive operation. It starts offline, uses the supported n8n Public
|
|
12
12
|
API, and never sends workflow data to an external AI provider.
|
|
13
13
|
|
|
14
|
-
> **
|
|
15
|
-
>
|
|
16
|
-
>
|
|
17
|
-
>
|
|
18
|
-
>
|
|
19
|
-
> [Installation](docs/installation.md).
|
|
14
|
+
> **Release evidence:** install only a version that appears consistently on
|
|
15
|
+
> [npm](https://www.npmjs.com/package/n8n-mcp-community), the
|
|
16
|
+
> [latest GitHub release](https://github.com/drzamarian/n8n-mcp-community/releases/latest),
|
|
17
|
+
> and the MCP Registry entry `io.github.drzamarian/n8n-mcp-community`. A version
|
|
18
|
+
> in the source tree remains a candidate until those external readbacks agree.
|
|
19
|
+
> See [Installation](docs/installation.md).
|
|
20
20
|
|
|
21
21
|
[Read the synthetic terminal demo transcript](docs/demo-transcript.md). It shows
|
|
22
22
|
the exact-version startup, 44-tool inventory, and local Introspect
|
|
@@ -47,17 +47,21 @@ diagnostics using only synthetic identifiers and documented output shapes.
|
|
|
47
47
|
The current source candidate has been verified with:
|
|
48
48
|
|
|
49
49
|
- exactly **44 tools**, **5 resources**, and **4 prompts** over real stdio;
|
|
50
|
-
- **
|
|
50
|
+
- **292 passing tests** and the complete verification gate on Node.js 22.23.1
|
|
51
51
|
and 24.18.0;
|
|
52
|
-
- zero findings from Gitleaks, Semgrep, Trivy, and both
|
|
53
|
-
`npm audit` runs; all three source scanners are reproduced in
|
|
54
|
-
immutable scanner/action identities;
|
|
52
|
+
- zero findings from Gitleaks, Semgrep, Trivy, and both project-root
|
|
53
|
+
production/full `npm audit` runs; all three source scanners are reproduced in
|
|
54
|
+
CI, with immutable scanner/action identities;
|
|
55
|
+
- a separate no-override consumer install that resolves the patched
|
|
56
|
+
`@hono/node-server` 2.x line, requires a zero-finding production audit, and
|
|
57
|
+
proves the stdio runtime and exact 44/5/4 inventory;
|
|
55
58
|
- a reproducible dependency-license gate covering 224 installed package paths;
|
|
56
59
|
- bounded same-origin HTTP contracts and zero-request policy-denial tests;
|
|
57
60
|
- all 44 compiled tool lifecycles on disposable n8n Community 2.30.5 and 2.30.7
|
|
58
61
|
instances with egress isolation, revoked keys, cleanup, and zero residue; and
|
|
59
|
-
- a
|
|
60
|
-
|
|
62
|
+
- a reviewed offline npm-tarball installation under the project's committed
|
|
63
|
+
dependency policy, plus a byte-reproducible unsigned MCPB with the same
|
|
64
|
+
compiled runtime and complete production dependency notices.
|
|
61
65
|
|
|
62
66
|
Publication proceeds only through the reviewed release procedure: the
|
|
63
67
|
repository-pinned MCPB signing identity, a human artifact-baseline receipt,
|
|
@@ -69,14 +73,15 @@ handoff verified byte for byte against the reviewed unsigned candidate.
|
|
|
69
73
|
Requirements: Node.js 22 or 24, npm, a self-hosted n8n Community Edition
|
|
70
74
|
instance, and an n8n Public API key with only the permissions you need.
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
First identify a version whose npm provenance and GitHub release assets you have
|
|
77
|
+
verified, then replace `<VERIFIED_VERSION>` below. Keep the pin exact:
|
|
73
78
|
|
|
74
79
|
```json
|
|
75
80
|
{
|
|
76
81
|
"mcpServers": {
|
|
77
82
|
"n8n-community": {
|
|
78
83
|
"command": "npx",
|
|
79
|
-
"args": ["--yes", "n8n-mcp-community
|
|
84
|
+
"args": ["--yes", "n8n-mcp-community@<VERIFIED_VERSION>"],
|
|
80
85
|
"env": {
|
|
81
86
|
"N8N_API_URL": "https://n8n.example.com",
|
|
82
87
|
"N8N_API_KEY": "replace-with-a-dedicated-api-key",
|
|
@@ -96,8 +101,7 @@ per-call confirmation phrase. See [Safety modes](#safety-modes).
|
|
|
96
101
|
|
|
97
102
|
Restart the MCP client and confirm exactly 44 tools, 5 resources, and 4
|
|
98
103
|
prompts. Compatible desktop clients can instead install the signed MCPB from
|
|
99
|
-
the
|
|
100
|
-
[v0.1.1 release](https://github.com/drzamarian/n8n-mcp-community/releases/latest);
|
|
104
|
+
the [latest verified release](https://github.com/drzamarian/n8n-mcp-community/releases/latest);
|
|
101
105
|
verify its checksum against the release `SHA256SUMS` first.
|
|
102
106
|
|
|
103
107
|
## Quick start from source
|
|
@@ -106,7 +110,7 @@ Requirements: Node.js 22 or 24, npm, and a Git checkout of this repository.
|
|
|
106
110
|
|
|
107
111
|
```bash
|
|
108
112
|
npm ci
|
|
109
|
-
npm run
|
|
113
|
+
npm run verify:contributor
|
|
110
114
|
```
|
|
111
115
|
|
|
112
116
|
Configure your MCP client to run the compiled stdio entry point:
|
|
@@ -131,7 +135,7 @@ Restart the MCP client and list tools. The server can initialize and expose its
|
|
|
131
135
|
inventory without n8n credentials; connected tools validate the URL and API key
|
|
132
136
|
only when called.
|
|
133
137
|
|
|
134
|
-
|
|
138
|
+
Verified releases provide both a signed MCPB for compatible clients and
|
|
135
139
|
exact-version `npx` configuration for portability. `@latest`, global installs,
|
|
136
140
|
and `curl | shell` are not reproducible defaults. See
|
|
137
141
|
[Installation](docs/installation.md) for the release policy and client-specific
|
|
@@ -283,12 +287,13 @@ Read the complete [security model](docs/security-model.md) and use
|
|
|
283
287
|
|
|
284
288
|
```bash
|
|
285
289
|
npm ci
|
|
286
|
-
npm run
|
|
290
|
+
npm run verify:contributor
|
|
287
291
|
npm run sbom > sbom.cdx.json
|
|
288
292
|
```
|
|
289
293
|
|
|
290
|
-
`npm run
|
|
291
|
-
|
|
294
|
+
`npm run verify:contributor` runs the complete keyless contributor gate,
|
|
295
|
+
including formatting, dependency licenses/notices, strict TypeScript, all
|
|
296
|
+
tests, the compiled runtime, and documentation inventory parity.
|
|
292
297
|
See [CONTRIBUTING.md](CONTRIBUTING.md) before opening a change.
|
|
293
298
|
|
|
294
299
|
## Scope and non-affiliation
|
|
@@ -299,7 +304,7 @@ API. The project does not redistribute the `n8n-nodes-base` catalog and does not
|
|
|
299
304
|
use browser cookies, interactive session routes, or runtime package downloads to
|
|
300
305
|
construct one.
|
|
301
306
|
|
|
302
|
-
The v0.1.
|
|
307
|
+
The v0.1.3 candidate surface intentionally excludes arbitrary workflow execution,
|
|
303
308
|
credential/workflow transfer, folders, data tables, beta evaluation endpoints,
|
|
304
309
|
and execution annotations. See the [roadmap](ROADMAP.md) for the annotation
|
|
305
310
|
proposal retained outside the release target.
|
package/SECURITY.md
CHANGED
|
@@ -6,16 +6,15 @@ before public disclosure.
|
|
|
6
6
|
|
|
7
7
|
## Supported versions
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
asked to upgrade when a safe backport is impractical.
|
|
9
|
+
The latest maintained minor line — currently 0.1.x — receives security fixes.
|
|
10
|
+
Older lines may be asked to upgrade when a safe backport is impractical.
|
|
12
11
|
|
|
13
12
|
## Report a vulnerability
|
|
14
13
|
|
|
15
|
-
Use GitHub's **Security → Report a vulnerability** form
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
Use GitHub's **Security → Report a vulnerability** form; private vulnerability
|
|
15
|
+
reporting is enabled and verified for this repository. Do not open a public
|
|
16
|
+
issue with exploit details, credentials, workflow payloads, or identifying
|
|
17
|
+
instance data.
|
|
19
18
|
|
|
20
19
|
If the private form is unavailable, open a public issue containing only the
|
|
21
20
|
sentence “Private security contact requested.” Do not include technical details
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -11,13 +11,13 @@ LICENSE or NOTICE file. They are exact-pinned in the lockfile, are not present
|
|
|
11
11
|
in the runtime package or MCPB, and are accepted only at the listed version,
|
|
12
12
|
license, repository, and integrity hash:
|
|
13
13
|
|
|
14
|
-
| Package | License |
|
|
14
|
+
| Package | License | Source or immutable package record |
|
|
15
15
|
| ----------------------- | ------------ | ----------------------------------------------------------------------------------------------- |
|
|
16
16
|
| `@humanfs/types@0.15.0` | Apache-2.0 | [humanwhocodes/humanfs](https://github.com/humanwhocodes/humanfs) |
|
|
17
17
|
| `esrecurse@4.3.0` | BSD-2-Clause | [estools/esrecurse](https://github.com/estools/esrecurse) |
|
|
18
18
|
| `flora-colossus@2.0.0` | MIT | [MarshallOfSound/flora-colossus](https://github.com/MarshallOfSound/flora-colossus/tree/v2.0.0) |
|
|
19
19
|
| `imurmurhash@0.1.4` | MIT | [jensyt/imurmurhash-js](https://github.com/jensyt/imurmurhash-js) |
|
|
20
|
-
| `keyv@4.5.4` | MIT | [
|
|
20
|
+
| `keyv@4.5.4` | MIT | [npm registry record](https://registry.npmjs.org/keyv/4.5.4) |
|
|
21
21
|
| `natural-compare@1.4.0` | MIT | [litejs/natural-compare-lite](https://github.com/litejs/natural-compare-lite) |
|
|
22
22
|
|
|
23
23
|
Their exact SHA-512 integrity values are enforced in
|
|
@@ -26,9 +26,10 @@ or notice-file change fails the gate and requires a new review. A package with a
|
|
|
26
26
|
missing notice file can never use this exception when it is a runtime dependency
|
|
27
27
|
or bundled into a release artifact.
|
|
28
28
|
|
|
29
|
-
Complete license texts are
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
Complete license texts are identified by the SPDX expressions above. The linked
|
|
30
|
+
source repositories or immutable package records preserve provenance for the
|
|
31
|
+
reviewed versions. The final MCPB must independently preserve the license and
|
|
32
|
+
notice files for every dependency it actually bundles.
|
|
32
33
|
|
|
33
34
|
`minimatch@10.2.5` is another development-only ESLint dependency and is licensed
|
|
34
35
|
under the permissive Blue Oak Model License 1.0.0. Its installed package includes
|
|
@@ -219,17 +219,28 @@ const DurationMetricsSchema = z.object({
|
|
|
219
219
|
p99Ms: z.number().nonnegative().nullable(),
|
|
220
220
|
});
|
|
221
221
|
export const IntrospectResultSchema = z.object({
|
|
222
|
-
schemaVersion: z
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
222
|
+
schemaVersion: z
|
|
223
|
+
.literal(INTROSPECT_SCHEMA_VERSION)
|
|
224
|
+
.describe("Version of the stable structured introspect result contract."),
|
|
225
|
+
engineVersion: z
|
|
226
|
+
.literal(INTROSPECT_ENGINE_VERSION)
|
|
227
|
+
.describe("Version of the deterministic local analysis engine that produced this result."),
|
|
228
|
+
status: z
|
|
229
|
+
.enum(["complete", "partial"])
|
|
230
|
+
.describe("Whether all requested bounded evidence was collected and analyzed."),
|
|
231
|
+
profile: z
|
|
232
|
+
.enum(["quick", "deep"])
|
|
233
|
+
.describe("Effective quick or deep analysis profile used for this result."),
|
|
234
|
+
workflow: z
|
|
235
|
+
.object({
|
|
227
236
|
id: z.string(),
|
|
228
237
|
label: z.string().optional(),
|
|
229
238
|
active: z.boolean(),
|
|
230
239
|
nodeCount: z.number().int().nonnegative(),
|
|
231
|
-
})
|
|
232
|
-
|
|
240
|
+
})
|
|
241
|
+
.describe("Sanitized identity and basic structure of the inspected workflow."),
|
|
242
|
+
sample: z
|
|
243
|
+
.object({
|
|
233
244
|
lookbackHours: z.number().int().positive(),
|
|
234
245
|
maxExecutions: z.number().int().positive(),
|
|
235
246
|
metadataExecutions: z.number().int().nonnegative(),
|
|
@@ -249,8 +260,10 @@ export const IntrospectResultSchema = z.object({
|
|
|
249
260
|
]),
|
|
250
261
|
oldestStartedAt: z.string().nullable(),
|
|
251
262
|
newestStartedAt: z.string().nullable(),
|
|
252
|
-
})
|
|
253
|
-
|
|
263
|
+
})
|
|
264
|
+
.describe("Exact execution-history scope, ordering confidence, requests, and byte budget used."),
|
|
265
|
+
summary: z
|
|
266
|
+
.object({
|
|
254
267
|
findingCounts: z.object({
|
|
255
268
|
critical: z.number().int().nonnegative(),
|
|
256
269
|
high: z.number().int().nonnegative(),
|
|
@@ -271,8 +284,10 @@ export const IntrospectResultSchema = z.object({
|
|
|
271
284
|
omittedCount: z.number().int().nonnegative(),
|
|
272
285
|
truncated: z.boolean(),
|
|
273
286
|
}),
|
|
274
|
-
})
|
|
275
|
-
|
|
287
|
+
})
|
|
288
|
+
.describe("Counts of findings, rule outcomes, and any details omitted by output bounds."),
|
|
289
|
+
metrics: z
|
|
290
|
+
.object({
|
|
276
291
|
statusCounts: StatusCountsSchema,
|
|
277
292
|
successRate: z.number().min(0).max(1).nullable(),
|
|
278
293
|
duration: DurationMetricsSchema,
|
|
@@ -296,14 +311,24 @@ export const IntrospectResultSchema = z.object({
|
|
|
296
311
|
sampledErrors: z.number().int().nonnegative(),
|
|
297
312
|
}))
|
|
298
313
|
.max(100),
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
|
|
314
|
+
})
|
|
315
|
+
.describe("Factual status, duration, node-timing, and value-free error-cluster measurements."),
|
|
316
|
+
findings: z
|
|
317
|
+
.array(FindingSchema)
|
|
318
|
+
.max(1_000)
|
|
319
|
+
.describe("Ordered deterministic findings retained within the configured output limit."),
|
|
320
|
+
ruleCoverage: z
|
|
321
|
+
.array(RuleCoverageSchema)
|
|
322
|
+
.max(100)
|
|
323
|
+
.describe("Per-rule outcome and completeness evidence, including omitted finding counts."),
|
|
302
324
|
limitations: z
|
|
303
325
|
.array(z.object({ code: z.enum(INTROSPECT_LIMITATION_CODES), message: z.string() }))
|
|
304
|
-
.max(100)
|
|
305
|
-
|
|
326
|
+
.max(100)
|
|
327
|
+
.describe("Collection or analysis limitations that constrain interpretation of this result."),
|
|
328
|
+
guidance: z
|
|
329
|
+
.object({
|
|
306
330
|
instanceSecurityTool: z.literal("n8n_audit_generate"),
|
|
307
331
|
message: z.string(),
|
|
308
|
-
})
|
|
332
|
+
})
|
|
333
|
+
.describe("Boundary guidance pointing to the separate instance-level n8n security audit tool."),
|
|
309
334
|
});
|
|
@@ -38,6 +38,8 @@ const VALUE_REDACTIONS = [
|
|
|
38
38
|
[/\bpix[\s:-]*[\w.@-]{8,}/gi, "[PIX]"],
|
|
39
39
|
[/\b(?:[A-Z0-9]+_)*(?:SECRET|TOKEN|API_KEY)(?:_[A-Z0-9]+)+\b/gi, "[SECRET]"],
|
|
40
40
|
];
|
|
41
|
+
const OPAQUE_PATH_CHARACTERS = /^[A-Za-z0-9._~+/=-]+$/;
|
|
42
|
+
const OPAQUE_HEXADECIMAL_PATH = /^[A-Fa-f0-9]{24,64}$/;
|
|
41
43
|
const INJECTION_PATTERNS = [
|
|
42
44
|
[
|
|
43
45
|
/\b(?:ignore|disregard|forget|override)\s+(?:all\s+)?(?:previous|prior|above)\s+instructions?\b/gi,
|
|
@@ -46,6 +48,12 @@ const INJECTION_PATTERNS = [
|
|
|
46
48
|
[/\b(?:system|assistant|developer)\s*:\s/gi, "[FILTERED-ROLE]: "],
|
|
47
49
|
[/<\/?(?:system|assistant|developer|prompt|instructions?)>/gi, "[FILTERED-TAG]"],
|
|
48
50
|
];
|
|
51
|
+
export class OutputLimitError extends Error {
|
|
52
|
+
constructor() {
|
|
53
|
+
super("The sanitized result exceeds the output limit. Use narrower filters or pagination.");
|
|
54
|
+
this.name = "OutputLimitError";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
49
57
|
function compareStrings(left, right) {
|
|
50
58
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
51
59
|
}
|
|
@@ -77,6 +85,24 @@ function redactUrlCredential(value) {
|
|
|
77
85
|
// quantifier is bounded so a long delimiter-free run cannot force quadratic backtracking.
|
|
78
86
|
return value.replace(/([A-Za-z][A-Za-z0-9+.-]{0,31}:\/\/)[^/?#\s]+@/g, "$1[REDACTED]@");
|
|
79
87
|
}
|
|
88
|
+
function looksLikeOpaquePathSegment(value) {
|
|
89
|
+
if (value.length < 24 || !OPAQUE_PATH_CHARACTERS.test(value))
|
|
90
|
+
return false;
|
|
91
|
+
// Encoding-aware detection closes the entropy blind spot for fixed-width hexadecimal IDs:
|
|
92
|
+
// repeated hex digits can carry 96+ bits while scoring below the generic Shannon threshold.
|
|
93
|
+
if (OPAQUE_HEXADECIMAL_PATH.test(value))
|
|
94
|
+
return true;
|
|
95
|
+
const frequencies = new Map();
|
|
96
|
+
for (const character of value) {
|
|
97
|
+
frequencies.set(character, (frequencies.get(character) ?? 0) + 1);
|
|
98
|
+
}
|
|
99
|
+
let entropy = 0;
|
|
100
|
+
for (const count of frequencies.values()) {
|
|
101
|
+
const probability = count / value.length;
|
|
102
|
+
entropy -= probability * Math.log2(probability);
|
|
103
|
+
}
|
|
104
|
+
return entropy >= (value.length >= 32 ? 3.5 : 4);
|
|
105
|
+
}
|
|
80
106
|
function sanitizeString(value, structuralKey) {
|
|
81
107
|
const boundedInput = value.length > 131_072 ? value.slice(0, 131_072) : value;
|
|
82
108
|
const normalizedInput = boundedInput.normalize("NFKC");
|
|
@@ -150,10 +176,11 @@ function sanitizeString(value, structuralKey) {
|
|
|
150
176
|
}
|
|
151
177
|
return { value: output, redacted };
|
|
152
178
|
}
|
|
153
|
-
function sanitizeValue(value, state, depth, structuralKey, preserveChildKeys = false, sensitiveDescriptor = false) {
|
|
179
|
+
function sanitizeValue(value, state, depth, structuralKey, preserveChildKeys = false, sensitiveDescriptor = false, treatSensitiveKeyAsStructural = false) {
|
|
154
180
|
state.nodes += 1;
|
|
155
181
|
if (state.nodes > 20_000 || depth > 20) {
|
|
156
182
|
state.redacted = true;
|
|
183
|
+
state.structurallyReduced = true;
|
|
157
184
|
return "[TRUNCATED]";
|
|
158
185
|
}
|
|
159
186
|
if (typeof value === "string") {
|
|
@@ -165,18 +192,22 @@ function sanitizeValue(value, state, depth, structuralKey, preserveChildKeys = f
|
|
|
165
192
|
return value;
|
|
166
193
|
if (Array.isArray(value)) {
|
|
167
194
|
const limit = Math.min(value.length, 1_000);
|
|
168
|
-
if (limit < value.length)
|
|
195
|
+
if (limit < value.length) {
|
|
169
196
|
state.redacted = true;
|
|
197
|
+
state.structurallyReduced = true;
|
|
198
|
+
}
|
|
170
199
|
return value
|
|
171
200
|
.slice(0, limit)
|
|
172
201
|
.map((item) => sanitizeValue(item, state, depth + 1, structuralKey, false, sensitiveDescriptor));
|
|
173
202
|
}
|
|
174
203
|
if (typeof value === "bigint") {
|
|
175
204
|
state.redacted = true;
|
|
205
|
+
state.structurallyReduced = true;
|
|
176
206
|
return value.toString();
|
|
177
207
|
}
|
|
178
208
|
if (typeof value !== "object") {
|
|
179
209
|
state.redacted = true;
|
|
210
|
+
state.structurallyReduced = true;
|
|
180
211
|
return "[UNSUPPORTED]";
|
|
181
212
|
}
|
|
182
213
|
const prototype = Reflect.getPrototypeOf(value);
|
|
@@ -185,6 +216,7 @@ function sanitizeValue(value, state, depth, structuralKey, preserveChildKeys = f
|
|
|
185
216
|
value instanceof Set ||
|
|
186
217
|
(prototype !== Object.prototype && prototype !== null)) {
|
|
187
218
|
state.redacted = true;
|
|
219
|
+
state.structurallyReduced = true;
|
|
188
220
|
return "[UNSUPPORTED_OBJECT]";
|
|
189
221
|
}
|
|
190
222
|
const output = Object.create(null);
|
|
@@ -192,50 +224,75 @@ function sanitizeValue(value, state, depth, structuralKey, preserveChildKeys = f
|
|
|
192
224
|
const sensitiveSibling = entries.some(([key, candidate]) => (key === "key" || key === "name") &&
|
|
193
225
|
typeof candidate === "string" &&
|
|
194
226
|
SENSITIVE_KEY.test(candidate));
|
|
195
|
-
|
|
227
|
+
const originalKeys = new Set(entries.map(([key]) => key));
|
|
228
|
+
for (const [entryIndex, [key, child]] of entries.slice(0, 1_000).entries()) {
|
|
196
229
|
if (PROTOTYPE_KEY.test(key)) {
|
|
197
230
|
state.redacted = true;
|
|
198
231
|
continue;
|
|
199
232
|
}
|
|
200
|
-
|
|
201
|
-
|
|
233
|
+
const sanitizedKey = sanitizeString(key);
|
|
234
|
+
let outputKey = key;
|
|
235
|
+
if (sanitizedKey.redacted) {
|
|
236
|
+
let placeholderIndex = entryIndex + 1;
|
|
237
|
+
do {
|
|
238
|
+
outputKey = `[REDACTED_KEY_${placeholderIndex}]`;
|
|
239
|
+
placeholderIndex += 1;
|
|
240
|
+
} while (originalKeys.has(outputKey) || Object.hasOwn(output, outputKey));
|
|
241
|
+
state.redacted = true;
|
|
242
|
+
state.structurallyReduced = true;
|
|
243
|
+
}
|
|
244
|
+
if (!treatSensitiveKeyAsStructural &&
|
|
245
|
+
!preserveChildKeys &&
|
|
246
|
+
SENSITIVE_KEY.test(key) &&
|
|
247
|
+
!STRUCTURAL_IDENTIFIER_KEY.test(key)) {
|
|
248
|
+
output[outputKey] = "[REDACTED]";
|
|
202
249
|
state.redacted = true;
|
|
203
250
|
continue;
|
|
204
251
|
}
|
|
205
252
|
if (sensitiveDescriptor && SENSITIVE_DESCRIPTOR_VALUE_KEY.test(key)) {
|
|
206
|
-
output[
|
|
253
|
+
output[outputKey] = "[REDACTED]";
|
|
207
254
|
state.redacted = true;
|
|
208
255
|
continue;
|
|
209
256
|
}
|
|
210
257
|
if (sensitiveDescriptor && child !== null && typeof child === "object") {
|
|
211
|
-
output[
|
|
258
|
+
output[outputKey] = "[REDACTED]";
|
|
212
259
|
state.redacted = true;
|
|
213
260
|
continue;
|
|
214
261
|
}
|
|
215
262
|
if (key === "value" && sensitiveSibling) {
|
|
216
|
-
output[
|
|
263
|
+
output[outputKey] = "[REDACTED]";
|
|
217
264
|
state.redacted = true;
|
|
218
265
|
continue;
|
|
219
266
|
}
|
|
220
|
-
if (preserveChildKeys &&
|
|
267
|
+
if (preserveChildKeys &&
|
|
268
|
+
(SENSITIVE_KEY.test(key) || (structuralKey === "properties" && sanitizedKey.redacted))) {
|
|
221
269
|
if (structuralKey === "properties" && child !== null && typeof child === "object") {
|
|
222
|
-
output[
|
|
270
|
+
output[outputKey] = sanitizeValue(child, state, depth + 1, key, false, true);
|
|
223
271
|
}
|
|
224
272
|
else {
|
|
225
|
-
output[
|
|
273
|
+
output[outputKey] = "[REDACTED]";
|
|
226
274
|
state.redacted = true;
|
|
227
275
|
}
|
|
228
276
|
continue;
|
|
229
277
|
}
|
|
230
|
-
output[
|
|
278
|
+
output[outputKey] = sanitizeValue(child, state, depth + 1, key, STRUCTURAL_MAP_KEY.test(key), false);
|
|
231
279
|
}
|
|
232
|
-
if (entries.length > 1_000)
|
|
280
|
+
if (entries.length > 1_000) {
|
|
233
281
|
state.redacted = true;
|
|
282
|
+
state.structurallyReduced = true;
|
|
283
|
+
}
|
|
234
284
|
return output;
|
|
235
285
|
}
|
|
236
|
-
export function
|
|
237
|
-
const state = { nodes: 0, redacted: false };
|
|
238
|
-
|
|
286
|
+
export function sanitizeForOutputDetailed(value, options = {}) {
|
|
287
|
+
const state = { nodes: 0, redacted: false, structurallyReduced: false };
|
|
288
|
+
const data = sanitizeValue(value, state, 0, undefined, false, false, options.preserveValidatedRootRecordValues ?? false);
|
|
289
|
+
return {
|
|
290
|
+
output: { data, redacted: state.redacted, untrusted: true },
|
|
291
|
+
structurallyReduced: state.structurallyReduced,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
export function sanitizeForOutput(value, options = {}) {
|
|
295
|
+
return sanitizeForOutputDetailed(value, options).output;
|
|
239
296
|
}
|
|
240
297
|
export function sanitizeLabelForOutput(value, maxLength = 120) {
|
|
241
298
|
const normalized = value.slice(0, 4_096).normalize("NFKC").slice(0, 4_096);
|
|
@@ -244,10 +301,29 @@ export function sanitizeLabelForOutput(value, maxLength = 120) {
|
|
|
244
301
|
? sanitized
|
|
245
302
|
: `${sanitized.slice(0, Math.max(0, maxLength - 1))}\u2026`;
|
|
246
303
|
}
|
|
304
|
+
export function sanitizePathSegmentForOutput(value, maxLength = 64) {
|
|
305
|
+
const normalized = value.slice(0, 4_096).normalize("NFKC").slice(0, 4_096);
|
|
306
|
+
if (SENSITIVE_KEY.test(normalized) ||
|
|
307
|
+
PROTOTYPE_KEY.test(normalized) ||
|
|
308
|
+
looksLikeOpaquePathSegment(normalized)) {
|
|
309
|
+
return { value: "[REDACTED]", redacted: true };
|
|
310
|
+
}
|
|
311
|
+
const sanitized = sanitizeString(normalized);
|
|
312
|
+
const compact = sanitized.value.replace(/\s+/g, " ").trim();
|
|
313
|
+
if (sanitized.redacted || compact.length === 0) {
|
|
314
|
+
return { value: "[REDACTED]", redacted: true };
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
value: compact.length <= maxLength
|
|
318
|
+
? compact
|
|
319
|
+
: `${compact.slice(0, Math.max(0, maxLength - 1))}\u2026`,
|
|
320
|
+
redacted: compact.length > maxLength,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
247
323
|
export function boundedJson(value, maxBytes = 256 * 1024) {
|
|
248
324
|
const text = JSON.stringify(value, null, 2);
|
|
249
325
|
if (Buffer.byteLength(text) > maxBytes) {
|
|
250
|
-
throw new
|
|
326
|
+
throw new OutputLimitError();
|
|
251
327
|
}
|
|
252
328
|
return text;
|
|
253
329
|
}
|
package/dist/tools/common.js
CHANGED
|
@@ -23,32 +23,3 @@ export function requireSafeAscii(value, label, maximumLength = 256) {
|
|
|
23
23
|
}
|
|
24
24
|
return value;
|
|
25
25
|
}
|
|
26
|
-
export function projectMetadata(value) {
|
|
27
|
-
const source = z.record(z.unknown()).parse(value);
|
|
28
|
-
const allowed = [
|
|
29
|
-
"id",
|
|
30
|
-
"name",
|
|
31
|
-
"type",
|
|
32
|
-
"active",
|
|
33
|
-
"isArchived",
|
|
34
|
-
"createdAt",
|
|
35
|
-
"updatedAt",
|
|
36
|
-
"versionId",
|
|
37
|
-
"workflowId",
|
|
38
|
-
"status",
|
|
39
|
-
"startedAt",
|
|
40
|
-
"stoppedAt",
|
|
41
|
-
"mode",
|
|
42
|
-
"finished",
|
|
43
|
-
"retryOf",
|
|
44
|
-
"retrySuccessId",
|
|
45
|
-
"role",
|
|
46
|
-
"disabled",
|
|
47
|
-
];
|
|
48
|
-
const output = {};
|
|
49
|
-
for (const key of allowed) {
|
|
50
|
-
if (Object.hasOwn(source, key))
|
|
51
|
-
output[key] = source[key];
|
|
52
|
-
}
|
|
53
|
-
return output;
|
|
54
|
-
}
|