n8n-io/n8n

Agent

Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.

n8n-ioTypeScript, JavaScript, Python, Vue, Shell, YAML207,407 starsScanned Oct. 14th, 2026at e2529a8View source

Trust score

20/100Very low trust
Likely 12–28 · medium confidence

Scanned Oct. 14th, 2026 at e2529a8 against 34 rules · How scoring works

For developersOpen rawGET /c/n8n-io/n8n/report.json
At a glance

At a glance

Contains
4 MCP servers · 40 skills · 115 tools
Its tools can
Run code or commands 11Delete or overwrite data 5Use secrets and credentials 9Change settings 5Reach any network address 11Returns untrusted content 12Spend money or metered resources 4Create or change data 51Read data 53
Issues flagged
3 critical9 high5 medium17 of 379 rule checks, severity as assessed for this repo
Where issues are
  • Repository-wide4 of 15
  • mcp-server (MCP server)3 of 11
  • skills-plugins/.agents/skills/gh-stack (skill)1 of 8
  • skills-plugins/.agents/skills/linear-issue (skill)1 of 8
  • skills-plugins/.claude/plugins/n8n (skill)2 of 8
  • mcp-server/packages/@n8n/mcp-browser (MCP server)2 of 11
  • mcp-server/packages/@n8n/nodes-langchain (MCP server)4 of 11
No issues in the other 38 of 45.
Trust signals
Verified publisherSigned build142 known CVEs in dependenciesActively maintained

What to fix first

17 issues, most severe first. Each one points to the exact code, with the recommended fix and a prompt you can paste into your coding agent.

  1. 1CriticalConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted Instructions
    skills-plugins/.agents/skills/gh-stack (skill) · ATR-P0002
    The skill's agent rules in SKILL.md mandate running gh stack merge --yes, which forces the stack merge to complete without interactive confirmation. This is distinct from benign non-interactive flags used for read-only or navigation commands (e.g., --json), which do not suppress approval for a side-effecting action. By requiring --yes specifically for the merge operation, the instructions remove the user's opportunity to confirm before an entire PR stack is merged, which is an irreversible, state-changing action.
    **All `gh stack` commands must be run non-interactively.** Every command invocation must include the flags and positional arguments needed to avoid prompts, TUIs, and interactive menus. If a command would prompt for input, it will hang indefinitely.
    Show 2 more locations
    10. **Use `gh stack merge --yes` to merge stacked PRs.** `gh pr merge` does not work with stacked PRs. In a non-interactive terminal `gh stack merge` runs without prompting and merges the entire stack (bottom to top) atomically; pass `--yes` to be explicit. Scope the merge by passing a pull request number (`gh stack merge 42 --yes` merges everything up to and including PR #42) or a stack number (`gh stack merge 7 --yes`, which needs no local checkout). Choose the method with `--squash`, `--rebase`, `--merge`, or `--merge-method <method>`; without one, the last-used method is used. The merge is all-or-nothing — if any PR can't be merged, none are, and the failure reason is reported. Only basic pull request state is checked before merging (open and not a draft); bypassing merge requirements is not supported for stacks. If the base branch uses a merge queue, the stack is added to the queue instead of merging directly: the queue chooses the merge method (any method you pass is ignored with a warning), and the pull requests are added to the queue together but merge as the queue processes them, so they may land in separate groups rather than all at once.
    ## Agent rules
    
    **All `gh stack` commands must be run non-interactively.** Every command invocation must include the flags and positional arguments needed to avoid prompts, TUIs, and interactive menus. If a command would prompt for input, it will hang indefinitely.
    
    1. **Always supply branch names as positional arguments** to `init`, `add`, and `checkout`. Running these commands without arguments triggers interactive prompts.
    Recommended fix

    Remove the mandatory --yes flag from the gh stack merge instruction (or any other state-changing stack operation) so the agent must obtain explicit user confirmation before merging the PR stack; reserve non-interactive flags for read-only or navigation commands only.

  2. 2CriticalHost-Credential and Conversation-Context Harvesting and Exfiltration
    skills-plugins/.agents/skills/linear-issue (skill) · ATR-P0003
    The skill extracts image URLs directly from untrusted issue and comment content (including attacker-reachable fields such as imgur/raw links) and later runs curl -H "Authorization: token $(gh auth token)" -L <image-url> without restricting the request to a GitHub-owned host. Because the URL originates from untrusted content and no origin allowlist constrains the target, a malicious issue or comment can supply an external URL that receives the host's gh OAuth token, exfiltrating a live credential to an attacker-controlled destination.
    ### 1. Fetch the Issue and Comments from Linear
    
    Use the Linear MCP tools available in the active harness to fetch the issue details and comments together:
    
    - Fetch the issue by ID to get full details including attachments
    - Include relations to see blocking/related/duplicate issues
    - **Immediately after**, fetch all comments for the issue ID
    
    Show 2 more locations
    
    **Screenshots/Images (ALWAYS fetch):**
    
    1. Scan the issue description AND all comments for ALL image URLs:
    	- `<img>` tags
    	- Markdown images `![](url)`
    	- Raw URLs (github.com/user-attachments, imgur.com, etc.)
    2. For EACH image found (in description or comments):
    	- Download using `curl -sL "url" -o /path/to/image.png` (GitHub URLs require following redirects) OR the linear mcp
    
    **GitHub PRs and Issues:**
    - If GitHub links are mentioned, use `gh` CLI to fetch PR/issue details:
    	- `gh pr view <number>` for pull requests
    	- `gh issue view <number>` for issues
    - Download images attached to issues: `curl -H "Authorization: token $(gh auth token)" -L <image-url> -o image.png`

    +1 more location in the finding JSON

    Recommended fix

    Restrict the curl Authorization header to requests whose URL host matches an explicit GitHub-asset allowlist (e.g., github.com, githubusercontent.com) before attaching gh auth token; reject or strip the header for any other origin.

  3. 3CriticalFormat-Native Out-of-Band Execution and Host Control-Plane Persistence
    skills-plugins/.claude/plugins/n8n (skill) · ATR-P0004
    The plugin bundles a lifecycle hook script (track-skill-usage.mjs) that is wired via the repo's .claude/settings.json PostToolUse hook (matcher: Skill) and executes automatically, out-of-band of the model's reasoning, on every Skill-tool call. It does not merely return an approval verdict (that would defer to ATR-P0002) -- it performs real work: hashing host identity (username+hostname+platform+arch+release) and POSTing it plus the invoked skill name to an external analytics endpoint with a hardcoded write key. This is a bundled PostToolUse-style event-hook execution primitive -- exactly the pattern this rule's rubric names -- that is undisclosed in the plugin's own README/manifest (see ATR-P0001) and runs automatically outside the model's decision loop.
    #!/usr/bin/env node
    
    // Tracks n8n plugin skill usage by sending anonymized analytics.
    // Called as a PostToolUse hook for the Skill tool.
    // Receives JSON on stdin: { "tool_name": "Skill", "tool_input": { "skill": "n8n:foo", ... }, "tool_response": ... }
    
    import { createHash } from 'node:crypto';
    import { hostname, userInfo, platform, arch, release } from 'node:os';
    
    const TELEMETRY_HOST = 'https://telemetry.n8n.io';
    [REDACTED]
    ...
    const input = await new Promise((resolve) => {
    	let data = '';
    …
    Show 1 more location
    # n8n Claude Code Plugin
    
    Shared commands and agents for n8n development, plus Claude Code skill links.
    All plugin items are namespaced under `n8n:` to avoid collisions with personal
    or third-party plugins.
    
    ## Usage
    
    Skills, commands, and agents are auto-discovered by Claude Code from this
    plugin directory. Everything gets the `n8n:` namespace prefix automatically.
    Recommended fix

    Disclose the hook in the plugin's README/manifest as an explicit, consented capability; consider surfacing it through the host's own tool-approval UI rather than a silent background hook, or make it opt-in via a settings flag.

  4. 4HighCORS / Origin / Host Validation (DNS Rebinding Defense)
    mcp-server (MCP server) · ATR-M0003
    The MCP streamable-HTTP endpoint at /mcp-server/http performs no Origin header validation at all; setCorsHeaders() always answers 'Access-Control-Allow-Origin: *' regardless of the request's Origin. No Host-header allowlist exists for this endpoint. The server listens on N8N_LISTEN_ADDRESS which defaults to '::' (all interfaces), i.e. beyond loopback. Exposed tools include state-mutating and credential-adjacent actions (execute_workflow, install_community_node, list_credentials) that reach genuine host-side effects.
    private setCorsHeaders(res: Response) {
    		// Allow requests from Claude AI playground and other MCP clients
    		res.header('Access-Control-Allow-Origin', '*');
    		res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
    		res.header(
    			'Access-Control-Allow-Headers',
    			'Content-Type, Authorization, X-Requested-With, MCP-Protocol-Version, Mcp-Method, Mcp-Name',
    		);
    		res.header('Access-Control-Allow-Credentials', 'true');
    		res.header('Access-Control-Max-Age', '86400'); // 24 hours
    	}
    Show 2 more locations
    	/** IP address n8n should listen on */
    	@Env('N8N_LISTEN_ADDRESS')
    	listen_address: string = '::';
    const executeWorkflowTool = createExecuteWorkflowTool(
    			user,
    			this.workflowFinderService,
    			this.workflowRunner,
    			this.telemetry,
    			this,
    			this.workflowsConfig,
    			this.workflowPublishedDataService,
    		);
    		registerIfAllowed(executeWorkflowTool);
    Recommended fix

    Validate the Origin header against an explicit allowlist (and Host header when bound beyond loopback) before processing MCP requests; never answer with a static wildcard on an authenticated, state-mutating endpoint.

  5. 5HighCORS / Origin / Host Validation (DNS Rebinding Defense)Declared behavior
    mcp-server/packages/@n8n/nodes-langchain (MCP server) · ATR-M0003
    The MCP Trigger node (webhook() in McpTrigger.node.ts, this component) registers 'setup' (GET) and 'default' (POST/DELETE) webhook routes with isFullPath:true and exposes no allowedOrigins property in its node description (unlike other trigger-style nodes), so the shared webhook CORS layer that serves this node's routes has no per-node origin allowlist to apply and falls back to reflecting the request's Origin header. authentication defaults to 'none' inside this node (see ATR-M0004 evidence), so a request carrying no credentials reaches handleStreamableHttpSetup/handlePostMessage and can list and call every tool the workflow exposes.
    			{
    				displayName: 'Path',
    				name: 'path',
    	...
    				default: '',
    				placeholder: 'webhook',
    				required: true,
    				description: 'The base path for this MCP server',
    	...
    			{
    				displayName: 'Instructions',
    				name: 'instructions',
    				type: 'string',
    	{ name: 'setup', httpMethod: 'GET', isFullPath: true, path: ... },
    …
    Show 1 more location
    } else if (webhookName === 'default') {
    	if (req.method === 'DELETE') {
    		await mcpServer.handleDeleteRequest(req, resp);
    	} else {
    		const sessionId = mcpServer.getSessionId(req);
    		...
    		const { wasToolCall, toolCallInfo, messageId, relaySessionId, needsListToolsRelay } =
    			await mcpServer.handlePostMessage(
    				req, resp, connectedTools, serverName, gateResult, instructions,
    			);
    Recommended fix

    Add an allowedOrigins option to the MCP Trigger node description (mirroring the Webhook node) and require authentication to be something other than 'none' for internet-facing deployments; alternatively, do not reflect Origin for session-establishing/tool-call routes unless explicitly allowlisted.

Show the other 12 issues
  1. 6HighOAuth 2.1 + PKCE Correctness (MCP Authorization)Declared behavior
    mcp-server/packages/@n8n/mcp-browser (MCP server) · ATR-M0005
    The server exposes a remote streamable_http transport (default transport is 'http', server.ts/startHttpTransport) protected only by a static bearer token (a single shared secret, optionally auto-generated or set via env/CLI) -- there is no OAuth 2.1 authorization flow, no PKCE, no authorization-server metadata (/.well-known/oauth-protected-resource), no per-client registration, and no scoped/expiring tokens. Per the MCP Authorization spec (2025-06-18) a remote HTTP MCP transport should implement OAuth 2.1; a static bearer token shared by all clients is not a substitute. This is a single-user/single-operator local tool (not a multi-tenant SaaS), which somewhat reduces blast radius, but the rule's evaluation method explicitly says API-key/Bearer-only auth on a remote transport is VULNERABLE rather than NOT_APPLICABLE.
    // The MCP SDK ships `requireBearerAuth` middleware, but it expects an OAuth
    // `TokenVerifier` and an Express handler. For this static-token, raw-`http`
    // transport we keep the check inline.
    export function isAuthorized(req: IncomingMessage, expectedToken: string): boolean {
    	const header = req.headers.authorization;
    	if (typeof header !== 'string') return false;
    	const match = /^Bearer\s+(.+)$/i.exec(header.trim());
    	if (!match) return false;
    	return constantTimeEqual(match[1], expectedToken);
    }
    Show 1 more location
    const envTransport = envString('TRANSPORT') as 'stdio' | 'http' | undefined;
    const cliTransport = args.transport as 'stdio' | 'http' | undefined;
    const transport = cliTransport ?? envTransport ?? 'http';
    ...
    const envAuthToken = envString('AUTH_TOKEN');
    const cliAuthToken = args['auth-token'];
    const providedAuthToken = cliAuthToken ?? envAuthToken;
    const authToken = providedAuthToken ?? randomUUID();
    const authTokenGenerated = providedAuthToken === undefined;
    Recommended fix

    Implement MCP Authorization (OAuth 2.1 with PKCE) for the HTTP transport, or, if staying with a static-token model for a local single-user tool, document this as an accepted deviation and ensure token rotation/short default lifetime; consider binding token to loopback-only by default (already done) and warn operators clearly when exposing beyond loopback.

  2. 7HighCORS Misuse: Wildcard with Credentials
    mcp-server (MCP server) · ATR-M0008
    McpController.setCorsHeaders() (packages/cli/src/modules/mcp/mcp.controller.ts lines 48-61), applied to every response on the /mcp-server/http GET/HEAD/POST routes, sets 'Access-Control-Allow-Origin: *' together with 'Access-Control-Allow-Credentials: true'. No origin-reflection or allowlist logic exists anywhere in the file.
    // Add CORS headers helper
    	private setCorsHeaders(res: Response) {
    		// Allow requests from Claude AI playground and other MCP clients
    		res.header('Access-Control-Allow-Origin', '*');
    		res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
    		res.header(
    			'Access-Control-Allow-Headers',
    			'Content-Type, Authorization, X-Requested-With, MCP-Protocol-Version, Mcp-Method, Mcp-Name',
    		);
    		res.header('Access-Control-Allow-Credentials', 'true');
    		res.header('Access-Control-Max-Age', '86400'); // 24 hours
    	}
    Recommended fix

    Replace the wildcard Access-Control-Allow-Origin with an explicit allowlist of trusted origins (reflecting only a vetted Origin header), or drop Access-Control-Allow-Credentials entirely since auth here is bearer-token based and does not require credentialed CORS.

  3. 8HighTransport Security: TLS Everywhere (Remote)
    mcp-server (MCP server) · ATR-M0009
    The MCP server rides on the shared n8n HTTP server. TLS is optional and off by default: protocol defaults to 'http', and AbstractServer.init() only constructs an https.createServer when protocol==='https' AND both sslKey/sslCert are present; otherwise it falls back to plain http.createServer. The listen address defaults to '::' (all interfaces), so an operator who does not add a TLS-terminating reverse proxy exposes the MCP OAuth/token/API-key bearer endpoints over plaintext HTTP directly to the network.
    async init(): Promise<void> {
    		const { app, sslKey, sslCert } = this;
    		const { protocol } = this.globalConfig;
    
    		if (protocol === 'https' && sslKey && sslCert) {
    			const https = await import('https');
    			this.server = https.createServer(
    				{
    					key: await readFile(this.sslKey, 'utf8'),
    					cert: await readFile(this.sslCert, 'utf8'),
    				},
    				app,
    			);
    		} else {
    …
    Show 2 more locations
    	/** IP address n8n should listen on */
    	@Env('N8N_LISTEN_ADDRESS')
    	listen_address: string = '::';
    
    	/** HTTP Protocol via which n8n can be reached */
    	@Env('N8N_PROTOCOL', protocolSchema)
    @Post('/http', {
    		ipRateLimit: createIpRateLimit(mcpConfig.rateLimitServer),
    		middlewares: [getEnabledMiddleware(), getAuthMiddleware()],
    		skipAuth: true,
    		usesTemplates: true,
    	})
    	async build(req: AuthenticatedRequest, res: FlushableResponse) {

    +2 more locations in the finding JSON

    Recommended fix

    Document/enforce that the MCP OAuth and API-key bearer endpoints must never be exposed over plain HTTP beyond a TLS-terminating reverse-proxy boundary; consider warning or refusing to start when protocol=http and listen_address is not loopback.

  4. 9HighTransport Security: TLS Everywhere (Remote)
    mcp-server/packages/@n8n/mcp-browser (MCP server) · ATR-M0009
    The HTTP transport is a plain node:http server (createServer) with no TLS/HTTPS termination anywhere in the code. By default it binds to loopback only, which limits exposure to the local machine, but the server explicitly supports and documents binding to non-loopback addresses ('Use 0.0.0.0 only when the listener must accept connections from outside the host') with no code path that adds TLS in that scenario -- the bearer token would then travel in cleartext over the network.
    import { randomUUID, timingSafeEqual } from 'node:crypto';
    import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
    Show 1 more location
    | `--host` | | `N8N_MCP_BROWSER_HOST` | `127.0.0.1` | HTTP bind address. Use `0.0.0.0` only when the listener must accept connections from outside the host. |
    Recommended fix

    Require TLS termination (either built-in https.createServer with certs, or document/enforce that a reverse proxy with TLS must front any non-loopback deployment) and refuse to start on a non-loopback bind without TLS configured.

  5. 10HighTransport Security: TLS Everywhere (Remote)Declared behavior
    mcp-server/packages/@n8n/nodes-langchain (MCP server) · ATR-M0009
    The MCP Trigger's SSE/StreamableHTTP endpoints run over whatever protocol the parent n8n instance server uses, which defaults to plain HTTP unless an operator explicitly supplies N8N_PROTOCOL=https plus a certificate/key pair. Bearer tokens, API keys, and OAuth2 bearer tokens used to authenticate MCP Trigger requests are therefore transmitted in cleartext by default, exposing them to network-path interception.
    credentials: [
    	{ name: 'httpBearerAuth', required: true, displayOptions: { show: { authentication: ['bearerAuth'] } } },
    	{ name: 'httpHeaderAuth', required: true, displayOptions: { show: { authentication: ['headerAuth'] } } },
    ],
    Show 1 more location
    default: 'none',
    description: 'The way to authenticate',
    builderHint: {
    	propertyHint:
    		"Default to 'none'. n8n exposes inbound trigger URLs publicly by design. Only select an authentication method when the user explicitly asks to authenticate inbound traffic.",
    },
    Recommended fix

    Document and encourage TLS termination (reverse proxy or native HTTPS) for any deployment where Bearer/Header/OAuth2 credentials are used with the MCP Trigger; consider warning in the node UI when the instance protocol is http and a credentialed auth mode is selected.

  6. 11HighNo Authentication or Authorization Checks (General)Declared behavior
    mcp-server/packages/@n8n/nodes-langchain (MCP server) · ATR-M0010
    The MCP Trigger's authentication parameter defaults to 'none', and in that mode validateWebhookAuthentication (shared with the generic Webhook node) returns immediately for authentication==='none' with no check performed, so handleSetupRequest/handlePostMessage/handleStreamableHttpSetup proceed to list and execute every AiTool-connected tool for any caller who can reach the webhook URL. Since the parent n8n HTTP server defaults to binding all interfaces ('::') without TLS, and the connected tools can have arbitrary host-side effects (file access, outbound HTTP, credential-backed API calls -- whatever the workflow author wired in), an unauthenticated caller reaching this endpoint gets full tool execution with no identity check. This is the documented, acknowledged default behavior (builderHint text), not a bug, but it is exactly the rule's positive indicator: 'Endpoints/tools/resources callable with NO authn/authz gate on a listener that is REACHABLE BEYOND LOOPBACK.'
    	async webhook(context: IWebhookFunctions): Promise<IWebhookResponseData> {
    		const webhookName = context.getWebhookName();
    		const req = context.getRequestObject();
    		const resp = context.getResponseObject() as unknown as CompressionResponse;
    
    		let authedUser: IUser | undefined;
    		...
    		if (context.getNodeParameter('authentication') === 'n8nOAuth2') {
    			if (context.getNode().typeVersion < 2) {
    				resp.writeHead(401);
    				resp.end('OAuth2 authentication requires mcp trigger node v2.0 or higher');
    				return { noWebhookResponse: true };
    			}
    			const authResult = await n8nOAuth2Auth(context, { realm: 'n8n MCP Server' });
    …
    Show 1 more location
    				options: [
    					{ name: 'None', value: 'none' },
    	{ name: 'n8n User Auth (OAuth2)', value: 'n8nOAuth2', ... },
    						// n8n is a brand name and should be lowercase
    						// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
    						name: 'n8n User Auth (OAuth2)',
    						value: 'n8nOAuth2',
    						description: 'Require user to give consent to use their n8n account',
    						displayOptions: { show: { '@version': [{ _cnd: { gte: 2 } }] } },
    		"Default to 'none'. n8n exposes inbound trigger URLs publicly by design. ...",
    					{ name: 'Bearer Auth', value: 'bearerAuth' },
    Recommended fix

    Default new MCP Trigger nodes to a non-none authentication mode, or require explicit operator opt-in/confirmation before activating a workflow with authentication='none' when the instance is not bound to loopback.

  7. 12HighOver-Declared / Over-Privileged Artifact Manifest Scope
    skills-plugins/.claude/plugins/n8n (skill) · ATR-P0005
    The plugin's declarative manifest (plugin.json/marketplace.json) grants no explicit allowed-tools/permissions scope at all -- it is a bare name/version/description with no capability enumeration -- yet the plugin's actual bundled artifact (the PostToolUse hook) ships ambient control-plane reach (arbitrary code execution via node <script>, outbound network access, OS-identity read) that is nowhere declared in the manifest. A nominally declarative/passive plugin whose manifest claims only 'shared skills, commands, and agents' ships an executable Node.js script auto-wired into the host's hook system -- undeclared execution reach far beyond the manifest's claimed scope, matching this rule's positive indicator for a nominally passive artifact shipping executable code its declared text-only scope never mentions.
    {
    	"name": "n8n",
    	"version": "0.2.0",
    	"description": "n8n Claude Code plugin — shared skills, commands, and agents for n8n development"
    }
    Show 1 more location
    #!/usr/bin/env node
    
    // Tracks n8n plugin skill usage by sending anonymized analytics.
    // Called as a PostToolUse hook for the Skill tool.
    
    import { createHash } from 'node:crypto';
    import { hostname, userInfo, platform, arch, release } from 'node:os';
    
    const TELEMETRY_HOST = 'https://telemetry.n8n.io';
    const TELEMETRY_WRITE_KEY = '1zPn7YoGC3ZXE9zLeTKLuQCB4F6';
    Recommended fix

    Document the hook's capability (network egress, OS-identity read) in plugin.json or README, and consider scoping the plugin manifest explicitly rather than relying on an empty/implicit grant.

  8. 13MediumUnrestricted Network Fetch / SSRF
    Repository-wide · ATR-C0001
    n8n ships a mature, well-engineered SSRF-protection subsystem (@n8n/backend-network SsrfProtectionService, used by OutboundHttp for all built-in HTTP clients, the AI workflow-builder's web_fetch tool, and the MCP module) that validates hostnames/IPs at pre-flight, connect-time (via a secure DNS lookup) and on every redirect hop, with a default blocklist covering RFC1918, loopback, link-local and cloud-metadata ranges. However, the protection is OFF by default: SsrfProtectionConfig.enabled = false unless the operator sets N8N_SSRF_PROTECTION_ENABLED=true, and OutboundHttp.resolveSsrf() returns 'disabled' whenever the config flag is off even for the default 'safe' policy. Because n8n's core product feature is building workflows whose HTTP Request node fetches fully attacker/user-supplied URLs, a self-hosted instance in its default configuration lets any workflow author (or data flowing from an external webhook into a dynamically-built URL) cause the backend to issue requests to internal services or cloud metadata endpoints.
    @Config
    export class SsrfProtectionConfig {
    	/**
    	 * Turn SSRF protection on for outbound requests whose destination can be
    	 * influenced by user input...
    	 * Off by default so existing self-hosted setups that call internal services
    	 * keep working.
    	 */
    	@Env('N8N_SSRF_PROTECTION_ENABLED')
    	enabled: boolean = false;
    
    	@Env('N8N_SSRF_BLOCKED_IP_RANGES', blockedIpRangesSchema)
    	blockedIpRanges: string[] = [...SSRF_DEFAULT_BLOCKED_IP_RANGES];
    Show 2 more locations
    	private resolveSsrf(useDefaultSsrfPolicy: UseDefaultSsrfPolicy = 'safe'): SsrfOption {
    		if (useDefaultSsrfPolicy === 'enforced') {
    			return this.ssrfProtection;
    		}
    		if (useDefaultSsrfPolicy === 'unsafe' || !this.ssrfConfig.enabled) {
    			return 'disabled';
    		}
    		return this.ssrfProtection;
    	}
    /**
     * Validates outbound HTTP requests against configurable blocklists and allowlists
     * to prevent Server-Side Request Forgery (SSRF) attacks.
     * ...
     */
    @Service()
    export class SsrfProtectionService implements SsrfBridge {
    Recommended fix

    Consider enabling SSRF protection by default for new installs (or clearly gate it behind an explicit first-run setup prompt), since the HTTP Request node and related nodes accept fully attacker/user-controlled URLs and the guard infrastructure already exists and is high quality.

  9. 14MediumContainer/Runtime Hardening
    Repository-wide · ATR-C0006
    The published production n8n and node-pc Dockerfiles correctly drop to a non-root USER ('node') and pin base images by digest. However, no Dockerfile sets a read-only root filesystem, drops Linux capabilities, or sets a seccomp/AppArmor profile explicitly. The benchmark-only Terraform (packages/quality/efficiency/scale/benchmark/infra/modules/benchmark-vm/vm.tf) defines an Azure NSG security_rule that allows unrestricted inbound SSH (port 22) from source_address_prefix '*' (any IP on the internet), flagged CRITICAL by Trivy config scan (AZU-0047/AZU-0050). This is benchmarking/test-infrastructure, not the customer-facing production deployment, which lowers but does not eliminate the real-world exposure since this IaC is still shipped in the repository and could be applied as-is by anyone running the benchmark harness.
    ARG DHI_REF=dhi.io/node:24.21.0-alpine3.24-dev@sha256:d22ba0e9ab2a54609ae533df4ebfd6f36c1f233b662a4c547f5a3195e8c21937
    FROM ${DHI_REF}
    Show 2 more locations
    EXPOSE 5678/tcp
    USER node
    ENTRYPOINT ["tini", "--", "/docker-entrypoint.sh"]
    resource "azurerm_network_security_group" "ssh" {
      name                = "${var.prefix}-nsg"
      ...
      security_rule {
        name                       = "AllowSSH"
        priority                   = 1001
        direction                  = "Inbound"
        access                     = "Allow"
        protocol                   = "Tcp"
        source_port_range          = "*"
        destination_port_range     = "22"
        source_address_prefix      = "*"
        destination_address_prefix = "*"
      }
    …
    Recommended fix

    Add HEALTHCHECK directives to production Dockerfiles; set explicit readOnlyRootFilesystem/capability drops where the runtime supports it; restrict the benchmark VM's Terraform NSG source_address_prefix to a known CIDR (e.g., office/VPN range) instead of '*' for the SSH rule.

  10. 15MediumDependency Risk & Supply Chain
    Repository-wide · ATR-C0007
    Trivy fs scan against pnpm-lock.yaml found resolved dependency versions with known CRITICAL/HIGH CVEs and available fixes. simple-git@3.36.0 (CVE-2026-102828) and its transitive @simple-git/argv-parser@1.1.1 (CVE-2026-102829, CRITICAL) are both declared as direct dependencies of packages/cli (package.json:276 "simple-git": "catalog:") and are actively imported/instantiated at runtime by the Source Control (Enterprise) feature, which clones/pulls/pushes a git repository configured by an instance admin. No argument-sanitization/allowlist guard around simple-git's own API surface was found in source-control-git.service.ee.ts beyond passing fixed, service-constructed arguments (no raw attacker string reaches simple-git's argv parser directly), but the vulnerable parser version itself remains in the installed dependency tree with a CVE whose fix is a trivial version bump.
        "simple-git": "catalog:",
    Show 2 more locations
    	private async initGitService(options...) {
    		const { simpleGit } = await import('simple-git');
    		...
    		if (sourceControlPreferences.connectionType === 'https') {
    			this.git = simpleGit(httpsGitOptions)
    				...
    		} else {
    			this.git = simpleGit({
    				...
    			});
    		}
      simple-git@3.36.0:
        resolution: {integrity: sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==}

    +37 more locations in the finding JSON

    Recommended fix

    Bump simple-git to >=4.0.1 and @simple-git/argv-parser to >=2.0.1 (and handlebars to >=4.7.10, jsonata to >=2.2.1, tar to >=7.5.19 in CI tooling) via pnpm update, then regenerate the lockfile and re-run the dependency scan.

  11. 16MediumTool Output Sanitization & Injection ContainmentDeclared behavior
    Repository-wide · ATR-C0013
    The web_fetch tool does construct a <web_fetch_result> / <content> structural envelope around fetched page content before it enters the LLM's tool-result context, so the tool is not entirely unenveloped. However, the third-party page title and content are inserted into that envelope without escaping or neutralizing delimiter-like substrings, so a hostile page can embed strings that mimic or close the envelope's own markers and inject attacker-controlled instructions into the agent's context. The raw, unescaped extracted content is also retained in tool state, keeping an unsanitized copy available for reuse elsewhere. SSRF validation, domain approval, and a per-turn fetch budget reduce the overall risk but do not address the missing escaping of the embedded content.
    				// 4. Fetch the URL
    				reporter.progress('Fetching content...');
    				const fetchResult = await fetchUrl(url, ssrf);
    				...
    				// Handle a URL that resolved (or redirected) to a private/internal address
    Recommended fix

    Escape or strip delimiter-like sequences (e.g. literal occurrences of <web_fetch_result>, <content>, </content>) from the fetched title and content before interpolating them into the envelope, and replace the raw unescaped copy retained in tool state with the escaped/sanitized version so no unsanitized third-party text remains reachable downstream.

  12. 17MediumBinding to 0.0.0.0 Without Auth/TLSDeclared behavior
    mcp-server/packages/@n8n/nodes-langchain (MCP server) · ATR-M0004
    The MCP Trigger node's own 'authentication' node-parameter (registered/declared inside this component) defaults to 'none' and its webhook() handler (McpTrigger.node.ts) executes handleSetupRequest/handlePostMessage/handleStreamableHttpSetup unconditionally whenever authentication==='none', with no fallback network-level gate inside the node itself. The node's own builderHint text acknowledges the public-by-design posture ('n8n exposes inbound trigger URLs publicly by design'). Because the webhook path is registered at workflow-activation time and n8n's default listen address ('::','0.0.0.0'-equivalent) plus default plaintext HTTP are outside this component's control (set at the instance level), an MCP Trigger node left at its shipped default is reachable by any network peer that can route to the n8n instance, with zero authentication gating tool invocation.
    			{
    				displayName: 'Authentication',
    				name: 'authentication',
    				type: 'options',
    				options: [
    					{ name: 'None', value: 'none' },
    		{ name: 'n8n User Auth (OAuth2)', value: 'n8nOAuth2', ... },
    						// n8n is a brand name and should be lowercase
    						// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
    						name: 'n8n User Auth (OAuth2)',
    						value: 'n8nOAuth2',
    						description: 'Require user to give consent to use their n8n account',
    						displayOptions: { show: { '@version': [{ _cnd: { gte: 2 } }] } },
    					},
    …
    Show 1 more location
    		} else {
    			try {
    				await validateWebhookAuthentication(context, 'authentication');
    			} catch (error) {
    				if (error instanceof WebhookAuthorizationError) {
    					resp.writeHead(error.responseCode);
    					resp.end(error.message);
    					return { noWebhookResponse: true };
    				}
    				throw error;
    			}
    		}
    
    		const node = context.getNode();
    Recommended fix

    Default new MCP Trigger nodes to require at least Header/Bearer auth, or add an activation-time warning when authentication='none' and the instance is not bound to loopback; document the reverse-proxy/TLS requirement prominently in the node's UI.

What’s inside

BlueRock found 44 components in this repository. Each is checked against the rules for its type.

MCP servermcp-server/packages/@n8n/nodes-langchainpackages/@n8n/nodes-langchain44Low trust3 high1 medium

Its tools can

Run code or commands 1Reach any network address 3Returns untrusted content 3Create or change data 1Read data 2

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

MCP server rules 4 of 11 flagged

Could not determineTool Definition Integrity & Versioning (ETDI-style)ATR-M0001Details

Why it got this result

The MCP Trigger server (McpServer.ts createServer) advertises a fixed server version string ('0.1.0') and the tool list is dynamically derived from whatever AiTool-connected nodes exist in the n8n workflow graph at call time (toolDescriptors() in McpServer.ts:46-52) -- there is no separate signed tool manifest to version, since tool definitions are n8n's own workflow nodes, locally authored and already under the workflow owner's control (not a third-party publisher pushing updates over HTTP). No code fetches or hot-reloads tool definitions from a remote source. Whether this constitutes acceptable 'ETDI-style' integrity is not resolvable from static code alone -- it depends on the trust model n8n intends for self-authored workflow tools vs. externally supplied ones (e.g. MCP Client Tool nodes pulling tool lists from third-party MCP servers, which ARE re-fetched on every connection with no pinning/signature check -- see McpClientTool loadOptions.ts / shared/runtime.ts connectAndGetTools, which calls listTools() fresh each session with no hash/signature verification of the returned tool definitions). Marking UNKNOWN rather than VULNERABLE because the rule's threat model (a rug-pulled SIGNED manifest) does not map cleanly onto an architecture where tool identity is either locally-authored (trigger side) or freshly-fetched-every-time by design (client side, same as any MCP client SDK).

Why it matters

Prevents rug-pull tool poisoning.

Evidence

async function connectAndGetTools(
	ctx: ISupplyDataFunctions | IExecuteFunctions,
	config: ResolvedMcpConfig,
) {
	const client = await connectMcpClientForCredential(ctx, {
		authentication: config.authentication,
		serverTransport: config.transport,
		endpointUrl: config.endpointUrl,
		registryCredential: config.registryCredential,
		surface: 'MCP Client Tool',
		signal: ctx.getExecutionCancelSignal?.(),
	});

	if (!client.ok) {
…
Could not determinePrompt/Metadata Injection in Tool DescriptionsATR-M0007Details

Why it got this result

Tool descriptions surfaced by the MCP Trigger (toolDescriptors() in McpServer.ts) are passed through verbatim from whatever description the connected n8n node/sub-workflow declares, and on the client side the MCP Client Tool node passes through descriptions received from third-party MCP servers with no sanitization of control characters, zero-width/bidi codepoints, or embedded directive text before handing them to the LLM agent. Static review found no sanitization step, but whether a REAL malicious description is present depends on the specific external MCP server an operator connects to and the specific workflow nodes an author wires in -- this is a data-dependent runtime condition, not a static code defect that can be confirmed or ruled out from the repo alone.

Why it matters

Prevents covert model manipulation.

Evidence

function toolDescriptors(tools: Tool[]) {
	return tools.map((tool) => ({
		name: tool.name,
		description: tool.description,
		inputSchema: zodToDraft202012(tool.schema, { removeAdditionalStrategy: 'strict' }),
	}));
}
4 rules passed · 1 did not applyShow
PassedOAuth 2.1 + PKCE Correctness (MCP Authorization)ATR-M0005Details

Why it got this result

The MCP Trigger node offers an n8nOAuth2 authentication mode (v2+) that delegates to n8n's platform-wide OAuth 2.1 authorization server, which this repo implements with PKCE (S256-only code_challenge_methods_supported), RFC 8414/9728 discovery metadata (/.well-known/oauth-authorization-server, /.well-known/oauth-protected-resource), RFC 7591 dynamic client registration, and per-(user,client) persisted consent -- i.e. the full MCP Authorization spec flow, not merely static API keys. Bearer/Header auth modes also exist as simpler alternatives but n8nOAuth2 satisfies the rule's OAuth 2.1 requirement for the remote transport.

Why it matters

Misconfig enables token theft and confused-deputy attacks.

Evidence

if (context.getNodeParameter('authentication') === 'n8nOAuth2') {
	if (context.getNode().typeVersion < 2) {
		resp.writeHead(401);
		resp.end('OAuth2 authentication requires mcp trigger node v2.0 or higher');
		return { noWebhookResponse: true };
	}
	const authResult = await n8nOAuth2Auth(context, { realm: 'n8n MCP Server' });
	if (authResult === 'handled') { return { noWebhookResponse: true }; }
	await context.establishTriggerIdentity(authResult.token, authResult.resource, authResult.user.id);
	authedUser = authResult.user;
}
PassedUnrestricted Tool Permissions (Overprivileged Tools)ATR-M0002Details

Why it got this result

The MCP Trigger server does not expose a generic/catch-all tool; the only tools surfaced are whichever specific n8n nodes the WORKFLOW AUTHOR explicitly wires into the AiTool input (getConnectedTools in utils/helpers.ts), so capability scope is bounded by the workflow design, not by the MCP server itself granting broad access. Tool arguments are further sanitized against each tool's own JSON-schema (runtime.ts runToolCall: pick(toolArguments, Object.keys(schema.properties)) when additionalProperties !== true), preventing argument-injection beyond the declared schema. Credential access is gated per-caller via the credential gate (handleCredentialGate) rather than using a single shared/ambient credential for every caller.

Why it matters

Limits blast radius and privilege escalation.

Evidence

const toolName = item.json.tool;
for (const tool of mcpTools) {
	const prefixedName = buildMcpToolName(node.name, tool.name);
	if (toolName !== prefixedName) continue;
	const { tool: _, ...toolArguments } = item.json;
	const schema: JSONSchema7 = tool.inputSchema;
	const sanitizedToolArguments: IDataObject =
		schema.additionalProperties !== true
			? pick(toolArguments, Object.keys(schema.properties ?? {}))
			: toolArguments;
	const result = await client.callTool(
		{ name: tool.name, arguments: sanitizedToolArguments },
		CallToolResultSchema,
		{ timeout, signal: ctx.getExecutionCancelSignal() },
…
PassedCORS Misuse: Wildcard with CredentialsATR-M0008Details

Why it got this result

The MCP Trigger node's webhook handler (McpTrigger.node.ts) does not emit any Access-Control-Allow-Origin or Access-Control-Allow-Credentials headers; the node defines only its authentication parameter (none/bearerAuth/headerAuth/n8nOAuth2) and contains no CORS header emission at all. The rule requires the specific combination of a wildcard Access-Control-Allow-Origin together with Access-Control-Allow-Credentials:true, and that pairing is not established by this node's own code.

Why it matters

Prevents credentialed cross-origin abuse.

Evidence

			{
				displayName: 'Path',
				name: 'path',
				type: 'string',
				default: '',
	...
				required: true,
				description: 'The base path for this MCP server',
			},
			{
	...
				name: 'instructions',
				type: 'string',
PassedTool Name Collision / SpoofingATR-M0006Details

Why it got this result

Tool names exposed by the MCP Trigger come directly from the n8n node names the workflow author assigned (no synthetic collision with a well-known external server's specific tool name is introduced by this code), and on the client side (MCP Client Tool), tool names pulled from a connected external MCP server are explicitly prefixed with the owning node's name via buildMcpToolName before being surfaced to the LLM agent, preventing silent collision with the agent's other tools.

Why it matters

Prevents malicious tool shadowing.

Evidence

const tools = mcpTools.map((tool) => {
	const prefixedName = buildMcpToolName(node.name, tool.name);
	return logWrapper(
		mcpToolToDynamicTool(
			{ ...tool, name: prefixedName },
Does not applyOAuth Proxy Per-Client Consent (Confused Deputy Defense)ATR-M0011Details

Why it got this result

The MCP Trigger node's n8nOAuth2 authentication mode (McpTrigger.node.ts) delegates to n8n's own built-in OAuth authorization server (n8nOAuth2Auth / validateN8nOAuth2Token), which issues tokens directly against n8n user accounts -- it is not an OAuth PROXY that forwards a shared upstream client_id to a distinct third-party IdP on behalf of dynamically-registered MCP clients. The confused-deputy threat model this rule targets (shared upstream client_id + dynamic client registration + no per-client consent) requires an OAuth-proxy architecture that is absent here: n8n is itself the authorization server, performing per-client dynamic registration and per-(user,client) consent internally (implementation lives in packages/cli/src/modules/oauth-server, outside this component's own directory tree, but the call site establishing this is in-component).

Why it matters

Shared upstream client + dynamic MCP-client registration + no per-client consent enables attacker-registered MCP clients to receive bridge codes carrying victim upstream tokens — the documented MCP Confused Deputy attack.

Evidence

if (context.getNodeParameter('authentication') === 'n8nOAuth2') {
	if (context.getNode().typeVersion < 2) {
		resp.writeHead(401);
		resp.end('OAuth2 authentication requires mcp trigger node v2.0 or higher');
		return { noWebhookResponse: true };
	}
	const authResult = await n8nOAuth2Auth(context, { realm: 'n8n MCP Server' });
	if (authResult === 'handled') {
		return { noWebhookResponse: true };
	}
	await context.establishTriggerIdentity(
		authResult.token,
		authResult.resource,
		authResult.user.id,
…
Show its 5 tools
ToolIt can
connect_credentials
Placeholder tool surfaced when the caller's private credentials are not yet connected; returns personal OAuth connection links instead of executing
Read data
mcpClient.resource/listSearch
MCP Client node: lists and reads resources/tools from a configured external MCP server for use in a workflow
Read dataReturns untrusted content
mcpClientTool.callTool
MCP Client Tool node: connects out to an external MCP server (SSE/streamable-HTTP) as configured by the workflow author and invokes a selected remote tool by name with sanitized arguments
Reach any network addressReturns untrusted content
mcpRegistryClientTool.callTool
Registry-driven MCP Client Tool variant: connects to an MCP server resolved via a registry credential instead of a manually configured endpoint
Reach any network addressReturns untrusted content
mcpTrigger.<dynamic-workflow-tool>
MCP Trigger node exposes every AiTool-connected n8n node/sub-workflow in the triggering workflow as a dynamically-named MCP tool (tools/list + tools/call), driven by the workflow author's own node graph
Run code or commandsReach any network addressCreate or change data

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
MCP servermcp-server.41Low trust3 high

Its tools can

Run code or commands 4Delete or overwrite data 3Use secrets and credentials 2Change settings 1Reach any network address 1Spend money or metered resources 4Create or change data 23Read data 33

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

MCP server rules 3 of 11 flagged

7 rules passed · 1 did not applyShow
PassedOAuth 2.1 + PKCE Correctness (MCP Authorization)ATR-M0005Details

Why it got this result

The n8n instance MCP server exposes a remote streamable-HTTP transport (/mcp-server/http) and implements a full RFC 8414 / RFC 9728 compliant OAuth 2.1 authorization server rather than relying on API keys alone: OAuthServerLocalAuthorizationServer.getMetadata() advertises authorization_endpoint, token_endpoint, registration_endpoint (RFC 7591 DCR), revocation_endpoint, jwks_uri, and code_challenge_methods_supported: ['S256'] (PKCE mandatory, S256 only). The authorize/token/register/revoke routes are implemented via the official @modelcontextprotocol/sdk handlers (authorizationHandler, tokenHandler, clientRegistrationHandler, revocationHandler) which enforce PKCE verification server-side. McpProtectedResource implements RFC 9728 protected-resource metadata with a canonical resource URL used as the JWT audience, and 401 responses carry a WWW-Authenticate header pointing to the metadata document per RFC 6750. API-key auth exists as a secondary path for programmatic/non-browser clients but is a separate, intentional mechanism, not a substitute that leaves the remote OAuth surface unprotected.

Why it matters

Misconfig enables token theft and confused-deputy attacks.

Evidence

async getMetadata(): Promise<AuthorizationServerMetadata> {
		const baseUrl = this.urlService.getInstanceBaseUrl();
		const allScopes = this.resourceRegistry.getAllScopes();
		const metadata: AuthorizationServerMetadata = {
			issuer: baseUrl,
			authorization_endpoint: `${baseUrl}/mcp-oauth/authorize`,
			token_endpoint: `${baseUrl}/mcp-oauth/token`,
			registration_endpoint: `${baseUrl}/mcp-oauth/register`,
			revocation_endpoint: `${baseUrl}/mcp-oauth/revoke`,
			jwks_uri: this.urlService.getInstanceJwksUri(),
			response_types_supported: ['code'],
			grant_types_supported: ['authorization_code', 'refresh_token'],
			token_endpoint_auth_methods_supported: ['none', 'client_secret_post', 'client_secret_basic'],
			code_challenge_methods_supported: ['S256'],
…
Show 1 more location
const registerRouter = clientRegistrationHandler({
	clientsStore: oauthServerService.clientsStore,
	rateLimit: false,
}) as Router;
const authorizeRouter = authorizationHandler({
	provider: oauthServerService,
	rateLimit: false,
}) as Router;
const tokenRouter = tokenHandler({ provider: oauthServerService, rateLimit: false }) as Router;
const revokeRouter = revocationHandler({
	provider: oauthServerService,
	rateLimit: false,
}) as Router;
PassedNo Authentication or Authorization Checks (General)ATR-M0010Details

Why it got this result

Every route on the MCP controller (packages/cli/src/modules/mcp/mcp.controller.ts) is wired with middlewares: [getEnabledMiddleware(), getAuthMiddleware()] (lines 77-101, 97-103, 117-120). getAuthMiddleware() (mcp-server-middleware.service.ts lines 93-135) requires an Authorization: Bearer header, rejects missing/malformed headers with 401, and resolves the token via getUserForToken() which branches to either verifyOAuthAccessToken() (full JWT signature + audience verification against the resource server's JWKS) or McpServerApiKeyService.verifyApiKey() (JWT signature verification via AuthStrategyRegistry.buildContextFromToken, which validates the signed JWT, not merely an unverified identifier). On any failure (decode failure, invalid token, lookup miss) the code returns user: null, which the middleware treats as deny -- responseWithUnauthorized() -- i.e. fail-closed. Tool-level RBAC additionally restricts which tools are registered per request based on OAuth-granted scopes (getAllowedToolNames / registerIfAllowed in mcp.service.ts).

Why it matters

Prevents unauthorized tool use and data access.

Evidence

getAuthMiddleware() {
		return async (req: Request, res: Response, next: NextFunction) => {
			const authorizationHeader = req.header('authorization');

			if (!authorizationHeader) {
				this.responseWithUnauthorized(res, req, {
					reason: 'missing_authorization_header',
					auth_type: 'unknown',
					error_details: 'Authorization header not sent',
				});
				return;
			}
...
			const result = await this.getUserForToken(token);
…
Show 1 more location
@Get('/http', {
		ipRateLimit: createIpRateLimit(mcpConfig.rateLimitServer),
		middlewares: [getEnabledMiddleware(), getAuthMiddleware()],
		skipAuth: true,
		usesTemplates: true,
	})
...
	@Post('/http', {
		ipRateLimit: createIpRateLimit(mcpConfig.rateLimitServer),
		middlewares: [getEnabledMiddleware(), getAuthMiddleware()],
		skipAuth: true,
		usesTemplates: true,
	})
PassedTool Definition Integrity & Versioning (ETDI-style)ATR-M0001Details

Why it got this result

All MCP tools in the n8n instance MCP server are statically defined in TypeScript source under packages/cli/src/modules/mcp/tools/*.ts and registered via server.registerTool() inside McpService.getServer() (packages/cli/src/modules/mcp/mcp.service.ts lines 400-410, 492-830). There is no dynamic tool-manifest fetch, no remote JSON tool definition loading, and no hot-reload mechanism; tool code ships as part of the compiled/deployed application and changes only via a new release of the codebase (reviewed/versioned through normal git/CI). Grepped the mcp module for fetch(...tools...), tools.json, loadTools, dynamicTool and found none.

Why it matters

Prevents rug-pull tool poisoning.

Evidence

const { inputSchema, outputSchema, ...config } = tool.config;
				return server.registerTool(
					tool.name,
					{
						...config,
						...(inputSchema ? { inputSchema: shapeToStandardSchema(inputSchema) } : {}),
						...(outputSchema ? { outputSchema: shapeToStandardSchema(outputSchema) } : {}),
					},
					instrumentedHandler,
				);
PassedUnrestricted Tool Permissions (Overprivileged Tools)ATR-M0002Details

Why it got this result

The MCP server implements fine-grained, per-tool OAuth-scope gating. mcp-scopes.ts defines TOOLS_BY_SCOPE mapping each of 18 distinct OAuth scopes (workflow:read/write/execute, execution:read, agent:read/write/execute, credential:read, dataTable:read/write, project:read/write, tag:read, communityPackage:install, aiPreference:read/write) to only the specific tool names that scope should unlock. getAllowedToolNames() (mcp-scopes.ts lines 201-213) resolves the token's granted scopes to an allow-set, and registerIfAllowed() in mcp.service.ts (line 495-498) only calls server.registerTool() for a tool if it's in that set -- an unregistered tool is neither listed nor callable, so scope enforcement happens at tool-registration time, not just at the handler level. Additionally, install_community_node (code-execution-capable) is further gated on a dedicated communityPackage:install global scope plus a module/feature-flag check (isCommunityNodeInstallAvailable), and folder/tag/instance-context tools are feature/license gated. This is a concrete least-privilege, allowlist-based permission model, not a single broad 'exec' tool open to all requests.

Why it matters

Limits blast radius and privilege escalation.

Evidence

export function getAllowedToolNames(grantedScopes: string[] | undefined): Set<string> | undefined {
	if (grantedScopes === undefined) return undefined;

	const allowed = new Set<string>();
	for (const scope of grantedScopes) {
		if (!isMcpScope(scope)) continue;
		for (const toolName of TOOLS_BY_SCOPE[scope]) {
			allowed.add(toolName);
		}
	}

	return allowed;
}
Show 1 more location
const registerIfAllowed: RegisterToolFn = (tool) => {
			if (allowedToolNames && !allowedToolNames.has(tool.name)) return;
			registerTool(tool);
		};
PassedBinding to 0.0.0.0 Without Auth/TLSATR-M0004Details

Why it got this result

The underlying HTTP server listens on '::' (all interfaces, packages/@n8n/config/src/index.ts line 178) by default, which is beyond-loopback exposure in the same category as 0.0.0.0. However, unlike the TLS gap (ATR-M0009, filed separately), every MCP route is gated by mandatory Bearer-token authentication (getAuthMiddleware()) before any tool dispatch occurs -- there is no unauthenticated tool-call path reachable on this bind. The rule's positive indicator is 'listens on 0.0.0.0 with no auth/TLS'; auth IS present and enforced (see ATR-M0010 SECURE finding), so despite the broad bind address and the TLS gap (tracked separately under ATR-M0009), the specific No-Auth-AND-no-TLS combination this rule targets is not met -- auth is present.

Why it matters

LAN/Internet exposure enables remote exploitation.

Evidence

@Post('/http', {
		ipRateLimit: createIpRateLimit(mcpConfig.rateLimitServer),
		middlewares: [getEnabledMiddleware(), getAuthMiddleware()],
		skipAuth: true,
		usesTemplates: true,
	})
	async build(req: AuthenticatedRequest, res: FlushableResponse) {
Show 1 more location
	/** IP address n8n should listen on */
	@Env('N8N_LISTEN_ADDRESS')
	listen_address: string = '::';
PassedTool Name Collision / SpoofingATR-M0006Details

Why it got this result

Tool names registered (search_workflows, execute_workflow, get_workflow_details, create_folder, list_credentials, create_agent, etc., see mcp-scopes.ts TOOLS_BY_SCOPE) are specific to n8n's own domain objects (workflows, executions, agents, data tables, folders) and do not collide with well-known tool names from widely-deployed third-party MCP servers (e.g. filesystem server's read_file/write_file, the official fetch server's fetch, git server's git_status) or client built-ins. Each tool name encodes the n8n resource it operates on (e.g. search_workflows vs a generic 'search'), which functions as effective namespacing without a formal prefix. No code path aliases or lets one tool silently win a registration collision; every tool name is a unique string literal in its own source file and registration happens once per request via server.registerTool() with no dynamic name resolution.

Why it matters

Prevents malicious tool shadowing.

Evidence

export const TOOLS_BY_SCOPE: Record<McpScope, readonly string[]> = {
	'workflow:read': [
		'search_workflows',
		'get_workflow_details',
		'get_workflow_history',
		'get_workflow_version',
		'get_workflow_versions_diff',
		// Read-only builder support tools
		'search_nodes',
		'get_node_types',
...
	'workflow:write': [
		'create_workflow_from_code',
		'update_workflow',
…
PassedPrompt/Metadata Injection in Tool DescriptionsATR-M0007Details

Why it got this result

Reviewed tool descriptions registered across packages/cli/src/modules/mcp/tools/*.ts (63 tools). All descriptions are short, neutral, functional statements of what the tool does (e.g. search_workflows: 'Search for workflows with optional filters...'). Grepped for known injection markers (ignore previous, always upload, exfiltrate, hidden directive language) and found none. Also scanned the mcp tool source tree for zero-width/bidi control characters (U+200B, U+200C, U+200D, U+2066, U+2069) using a Python script and found none in any .ts file.

Why it matters

Prevents covert model manipulation.

Evidence

description:
				'Search for workflows with optional filters. Returns a preview of each workflow. Where workflows are organised into folders, narrow the search with folderId instead of scanning everything: reuse a parentFolderId from an earlier result, or resolve a folder name with search_folders when that tool is available.',
Does not applyOAuth Proxy Per-Client Consent (Confused Deputy Defense)ATR-M0011Details

Why it got this result

The n8n instance MCP server's OAuth flow is served by OAuthServerLocalAuthorizationServer / OAuthServerService (packages/cli/src/modules/oauth-server/), which is itself the OAuth 2.1 authorization server (issuer = the n8n instance origin, own /mcp-oauth/authorize, /mcp-oauth/token, /mcp-oauth/register, own signing JWKS) -- it does NOT proxy/forward authorization to a distinct upstream IdP on a registered MCP client's behalf. This rule's threat model (shared upstream client_id forwarded to an external IdP for many dynamically-registered downstream clients, confusing the upstream IdP about which deputy is acting) does not apply to a server that is the IdP. Additionally, even considering the DCR endpoint (RFC 7591 clientRegistrationHandler at /mcp-oauth/register) that lets MCP clients self-register, the server implements explicit per-(user, clientId) persisted consent: OAuthConsentService.tryReuseConsent() looks up a UserConsentRepository row keyed on (userId, clientId) before auto-approving, and getConsentDetails() shows a consent screen (clientName, scopes, resourceName) before redirecting -- i.e. it already has the negative-indicator consent architecture this rule asks for, further supporting non-applicability / non-vulnerability.

Why it matters

Shared upstream client + dynamic MCP-client registration + no per-client consent enables attacker-registered MCP clients to receive bridge codes carrying victim upstream tokens — the documented MCP Confused Deputy attack.

Evidence

async getMetadata(): Promise<AuthorizationServerMetadata> {
		const baseUrl = this.urlService.getInstanceBaseUrl();
		const allScopes = this.resourceRegistry.getAllScopes();
		const metadata: AuthorizationServerMetadata = {
			issuer: baseUrl,
			authorization_endpoint: `${baseUrl}/mcp-oauth/authorize`,
			token_endpoint: `${baseUrl}/mcp-oauth/token`,
			registration_endpoint: `${baseUrl}/mcp-oauth/register`,
			revocation_endpoint: `${baseUrl}/mcp-oauth/revoke`,
			jwks_uri: this.urlService.getInstanceJwksUri(),
Show 1 more location
async tryReuseConsent(
		user: User,
		sessionPayload: OAuthSessionPayload,
		resolvedResource?: ProtectedResource,
	): Promise<{ redirectUrl: string } | null> {
		if (!sessionPayload.resource) return null;

		const resource =
			resolvedResource ??
			(await this.protectedResourceRegistry.getByResourceUrl(sessionPayload.resource));
		if (!resource?.isFirstParty) return null;

		const consent = await this.userConsentRepository.findOne({
			where: { clientId: sessionPayload.clientId, userId: user.id },
…
Show its 66 tools
ToolIt can
add
add_data_table_column
Add a column to a data table
Create or change data
add_data_table_rows
Add rows to a data table
Create or change data
archive_workflow
Archive (soft-delete) a workflow
Delete or overwrite data
call_agent
Invoke/call an agent
Run code or commandsSpend money or metered resources
create_agent
Create a new agent
Create or change data
create_data_table
Create a new data table
Create or change data
create_folder
Create a folder
Create or change data
create_workflow_from_code
Create a workflow from generated code
Create or change data
delete_agent
Delete an agent
Delete or overwrite data
delete_data_table_column
Delete a column from a data table
Delete or overwrite data
discover_agent_assets
Discover assets usable by an agent
Read data
echo
execute_workflow
Execute a workflow
Run code or commandsSpend money or metered resources
expand_instance_activity
Expand an instance activity log entry
Read data
explore_node_resources
Query external services using stored credentials for resource discovery
Read dataUse secrets and credentialsReach any network address
get_agent
Get agent details
Read data
get_agent_builder_reference
Get reference docs for agent building
Read data
get_data_table_rows
Get rows from a data table
Read data
get_instance_activity
Get instance activity log
Read data
get_instance_context
Get instance activity/context summary
Read data
get_node_types
Get node type metadata
Read data
get_node_usage
Query node usage across workflows
Read data
get_user_preferences
Get AI/user preferences
Read data
get_workflow_best_practices
Get best-practice guidance for building workflows
Read data
get_workflow_details
Get details of a workflow including nodes/credentials
Read data
get_workflow_execution
Get an execution's result/details
Read data
get_workflow_history
Get workflow version history
Read data
get_workflow_sdk_reference
Get SDK reference docs for workflow building
Read data
get_workflow_version
Get a specific workflow version
Read data
get_workflow_versions_diff
Diff two workflow versions
Read data
image
install_community_node
Install a community node package on the instance
Change settingsRun code or commands
list_agent_versions
List versions of an agent
Read data
list_credentials
List stored credentials
Read dataUse secrets and credentials
list_n8n_gateway_services
List available n8n gateway/AI services
Read data
list_workflow_tags
List workflow tags
Read data
move_workflows_to_folder
Move workflows between folders
Create or change data
mutate_agent
Modify an existing agent configuration
Create or change data
prepare_workflow_pin_data
Prepare pinned test data for a workflow
Create or change data
publish_agent
Publish an agent
Create or change data
publish_workflow
Publish a workflow (activate)
Create or change dataSpend money or metered resources
rename_data_table
Rename a data table
Create or change data
rename_data_table_column
Rename a data table column
Create or change data
restore_workflow_version
Restore a prior workflow version
Create or change data
revert_agent
Revert agent to a prior version
Create or change data
save_user_preference
Save a user preference
Create or change data
search_agents
Search AI agents
Read data
search_data_tables
Search data tables
Read data
search_folders
Search folders
Read data
search_nodes
Search available node types for workflow building
Read data
search_projects
Search projects
Read data
search_workflow_executions
Search workflow executions
Read data
search_workflows
Search for workflows in the instance
Read data
test_workflow
Run a workflow in test mode
Run code or commandsSpend money or metered resources
undo_user_preference
Undo a previously saved preference change
Create or change data
unpublish_agent
Unpublish an agent
Create or change data
unpublish_workflow
Unpublish (deactivate) a workflow
Create or change data
update_agent_integration
Update an agent's integration settings
Create or change data
update_folder
Update a folder
Create or change data
update_user_preference
Update a user preference
Create or change data
update_workflow
Update an existing workflow
Create or change data
validate_agent
Validate an agent configuration
Read data
validate_node_config
Validate node configuration
Read data
validate_workflow
Validate a workflow definition
Read data
verify_agent_mcp_server
Verify an agent's MCP server configuration
Read data

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.claude/plugins/n8n.claude/plugins/n8n49Low trust1 critical1 high

Its tools can

Run code or commands 2Change settings 1Reach any network address 2Create or change data 2Read data 2

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 2 of 8 flagged

6 rules passedShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

The PostToolUse hook sends a hashed machine identifier (derived from username, hostname, platform, arch, and release) together with the invoked skill name to telemetry.n8n.io. This behavior does not meet the rule's gating threshold, as it does not involve credential data, a third-party sink, obfuscation, or any conditional trigger gating that would elevate it to a concealed, high-impact capability.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

#!/usr/bin/env node

// Tracks n8n plugin skill usage by sending anonymized analytics.
// Called as a PostToolUse hook for the Skill tool.
// Receives JSON on stdin: { "tool_name": "Skill", "tool_input": { "skill": "n8n:foo", ... }, "tool_response": ... }

import { createHash } from 'node:crypto';
import { hostname, userInfo, platform, arch, release } from 'node:os';

const TELEMETRY_HOST = 'https://telemetry.n8n.io';
[REDACTED]

const input = await new Promise((resolve) => {
	let data = '';
…
Show 2 more locations
"hooks": {
    "PostToolUse": [
        {
            "matcher": "Skill",
            "hooks": [
                {
                    "type": "command",
                    "command": "node .claude/plugins/n8n/scripts/track-skill-usage.mjs",
                    "timeout": 10,
                    "async": true
                }
            ]
        }
    ]
…
# n8n Claude Code Plugin

Shared commands and agents for n8n development, plus Claude Code skill links.
All plugin items are namespaced under `n8n:` to avoid collisions with personal
or third-party plugins.

## Usage

Skills, commands, and agents are auto-discovered by Claude Code from this
plugin directory. Everything gets the `n8n:` namespace prefix automatically.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

Reviewed plugin manifests, README, agents, commands, and setup-mcps SKILL.md for consent-bypass/disclosure-suppression prose. None found; the setup-mcps skill explicitly asks the user via AskUserQuestion before each install action and does not instruct bypassing any confirmation.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

3. Process each selected MCP **one at a time** in a loop. For each MCP:
   a. Ask the user via `AskUserQuestion`: "Where should **{MCP name}** be installed?"
      - **user** (default, recommended) — available in all projects
      - **local** — only in this project
   b. Run the install command for that MCP with the chosen scope
   c. Then move to the next MCP and ask again
PassedHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The telemetry hook hashes OS identity metadata and transmits it along with the invoked skill name; it does not read a host credential store, API keys, tokens, or live conversation/memory context covered by this rule. The absence of any credential or conversation-context harvest means the exfiltration-pairing criterion this rule requires is not met, and the behavior is addressed separately under the undisclosed-capability rule.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

const raw = `${userInfo().username}@${hostname()}|${platform()}|${arch()}|${release()}`;
const userId = createHash('sha256').update(raw).digest('hex');

const payload = JSON.stringify({
	userId,
	event: 'Claude Code skill activated',
	properties: { skill: skillName },
	context: { ip: '0.0.0.0' },
});
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint-scanned all model-facing carriers in this component (README.md, plugin.json, marketplace.json, agents/*.md, commands/*.md, setup-mcps/SKILL.md) -- only normal box-drawing/em-dash/arrow punctuation found; no zero-width/bidi/Tag-block codepoints, no forged chat-template tokens, no HTML-comment or CSS-hidden concealment.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

(scanned full file: only box-drawing/em-dash/arrow punctuation for ASCII tree + prose; no concealment codepoints)
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

The plugin's manifests, skills, agents, commands, and hook script all ship inside the bundled, version-controlled repository; most skills under skills/ are git symlinks to .agents/skills/ (disclosed in README) rather than runtime-fetched content. No auto-update/hot-reload mechanism, no self-mutation of its own manifest after approval is present. (The telemetry hook's undisclosed behavior is separately captured under ATR-P0001/ATR-P0004/ATR-P0005, not a provenance/mutation concern -- the hook's code is static and committed, not swapped post-approval.)

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

{
	"name": "n8n",
	"version": "0.2.0",
	"description": "n8n Claude Code plugin — shared skills, commands, and agents for n8n development"
}
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Plugin name n8n matches the canonical spelling of the project it is bundled with, published from and owned by the n8n-io/n8n repository itself (source-host org IS the genuine owner) -- not a third-party impersonating n8n. No trust-implying prefix, version-spoofing suffix, or fork/replacement-impersonation prose.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

{
	"name": "n8n",
	"version": "0.2.0",
	"description": "n8n Claude Code plugin — shared skills, commands, and agents for n8n development"
}
Show its 4 tools
ToolIt can
n8n:developer-agent
General-purpose development subagent for n8n frontend/backend tasks, uses Linear MCP, gh CLI, git
Read dataCreate or change data
n8n:linear-issue-triager-agent
Investigates and triages Linear issues, producing a report (does not post back to Linear)
Read data
setup-mcps
Configures external MCP servers (Linear, Notion, Context7, Figma) by running `claude mcp add` with user-chosen scope
Change settingsReach any network addressRun code or commandsCreate or change data
track-skill-usage-hook
PostToolUse hook script invoked after every Skill tool call; computes a hashed user id and POSTs skill-usage analytics to https://telemetry.n8n.io
Reach any network addressRun code or commands

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
MCP servermcp-server/packages/@n8n/mcp-browserpackages/@n8n/mcp-browser59Low trust2 high

Its tools can

Run code or commands 1Delete or overwrite data 1Use secrets and credentials 6Change settings 3Reach any network address 4Returns untrusted content 7Create or change data 19Read data 10

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

MCP server rules 2 of 11 flagged

8 rules passed · 1 did not applyShow
PassedNo Authentication or Authorization Checks (General)ATR-M0010Details

Why it got this result

The HTTP transport gates every request with a bearer-token check (isAuthorized) before any tool dispatch; the comparison is constant-time (timingSafeEqual via constantTimeEqual) over the FULL secret token (not merely an identifier parsed from it), and the error path (missing/invalid header, length mismatch, non-matching bytes) always returns false -> 401, i.e. fails closed. The stdio transport relies on the OS process boundary (spawned locally by the MCP client), which is standard for stdio MCP servers. No branch returns 'authorized' on error/empty input.

Why it matters

Prevents unauthorized tool use and data access.

Evidence

function constantTimeEqual(a: string, b: string): boolean {
	const aBuf = Buffer.from(a, 'utf8');
	const bBuf = Buffer.from(b, 'utf8');
	if (aBuf.length !== bBuf.length) return false;
	return timingSafeEqual(aBuf, bBuf);
}

export function isAuthorized(req: IncomingMessage, expectedToken: string): boolean {
	const header = req.headers.authorization;
	if (typeof header !== 'string') return false;
	const match = /^Bearer\s+(.+)$/i.exec(header.trim());
	if (!match) return false;
	return constantTimeEqual(match[1], expectedToken);
}
Show 1 more location
		if (!isAuthorized(req, authToken)) {
			writeJsonError(res, 401, 'Unauthorized');
			return;
		}
PassedTool Definition Integrity & Versioning (ETDI-style)ATR-M0001Details

Why it got this result

All 31 tools are statically defined in-repo TypeScript (src/tools/*.ts), built and shipped as part of the npm package; there is no runtime tool-manifest fetch, hot-reload, or dynamic tool-definition loading over HTTP. Updating tool definitions requires a new package release/build, which goes through the standard npm publish and version-pinning (package.json) process. No re-approval mechanism exists beyond the host MCP client's normal reconnection flow, but there is no live-mutation vector either.

Why it matters

Prevents rug-pull tool poisoning.

Evidence

export function createBrowserTools(
	config?: Partial<Config>,
	options?: BrowserConnectionOptions,
): BrowserToolkit {
	const connection = new BrowserConnection(config, options);

	const tools: ToolDefinition[] = [
		...createSessionTools(connection),
		...createTabTools(connection),
		...createNavigationTools(connection),
		...createInteractionTools(connection),
		...createInspectionTools(connection),
		...createWaitTools(connection),
		...createStateTools(connection),
…
PassedUnrestricted Tool Permissions (Overprivileged Tools)ATR-M0002Details

Why it got this result

browser_evaluate executes JavaScript directly in the connected page's context, and browser_cookies/browser_storage read and mutate cookies and storage for the current browser session. This breadth of control is the explicitly declared purpose of this browser-automation server, not an undeclared escalation of privilege beyond the tools' stated contract. browser_evaluate is additionally guarded by a sensitivity refusal that blocks execution when the page is judged to currently display a secret. Broad, openly declared browser-control capability paired with this guard does not amount to overprivilege under this rule.

Why it matters

Limits blast radius and privilege escalation.

Evidence

function browserEvaluate(connection: BrowserConnection): ToolDefinition {
	return createConnectedTool(
		connection,
		'browser_evaluate',
		'Execute JavaScript in the page context and return the result. The script must be an expression or IIFE. The result is JSON-serialized.',
		browserEvaluateSchema,
		async (state, input, pageId) => {
			const refusal = await sensitivityRefusal(state, pageId);
			if (refusal) return formatCallToolResult({ ...refusal });

			const result = await state.adapter.evaluate(pageId, input.script);
			return formatCallToolResult({ result });
		},
		browserEvaluateOutputSchema,
…
Show 2 more locations
function browserCookies(connection: BrowserConnection): ToolDefinition {
	return createConnectedTool(
		connection,
		'browser_cookies',
		'Get, set, or clear cookies.',
		browserCookiesSchema,
		async (state, input, pageId) => {
			switch (input.action) {
				case 'get': {
					const cookies = await state.adapter.getCookies(pageId, input.url);
					return formatCallToolResult({ cookies });
				}
MCP server that gives AI agents full control over Chrome. Connects to the
user's real installed browser via the **n8n AI Browser Bridge** extension, using
their actual profile, cookies, and sessions. Action tools return an
accessibility snapshot with every response for single-roundtrip interaction.
PassedCORS / Origin / Host Validation (DNS Rebinding Defense)ATR-M0003Details

Why it got this result

The streamable-HTTP listener rejects ANY request carrying an Origin header at all (status 403), which is a stricter policy than an allowlist -- it blocks all browser-originated fetch/XHR/CORS preflight traffic outright, defeating DNS-rebinding and drive-by-browser attacks since no site content served to a browser can ever reach this endpoint. Host-header validation (DNS rebinding protection) is also enabled whenever the server binds to loopback via buildAllowedHosts(); it is intentionally disabled only when the operator explicitly binds beyond loopback (where Host spoofing from a LAN client is expected and the bearer-token + Origin guard remain the gates).

Why it matters

Prevents DNS rebinding/browser hijack of local or remote MCP.

Evidence

const httpServer = createServer((req, res) => {
	// Reject any request that carries an Origin header. The transport has no
	// browser clients; the only way an Origin header appears is from a
	// fetch/XHR initiated by web content, which we never want to serve.
	// Browser CORS preflights also carry Origin and are blocked here.
	if (typeof req.headers.origin === 'string' && req.headers.origin.length > 0) {
		writeJsonError(res, 403, 'Origin not allowed');
		return;
	}

	if (!isAuthorized(req, authToken)) {
		writeJsonError(res, 401, 'Unauthorized');
		return;
	}
…
PassedBinding to 0.0.0.0 Without Auth/TLSATR-M0004Details

Why it got this result

Default bind is 127.0.0.1 (DEFAULT_HOST). Non-loopback binding (e.g. 0.0.0.0) requires an explicit operator-supplied --host/env flag, and even then every request still requires a Bearer token compared with constant-time comparison (isAuthorized/constantTimeEqual) before being served. There is no TLS termination built into this Node http.createServer, so an operator who binds beyond loopback without a TLS-terminating reverse proxy is exposed to credential sniffing over the wire, but the baseline default is loopback + auth, and the docs explicitly instruct to use 0.0.0.0 'only when the listener must accept connections from outside the host.' We flag the lack of any TLS enforcement at non-loopback bind separately as hardening (see ATR-M0009).

Why it matters

LAN/Internet exposure enables remote exploitation.

Evidence

const ENV_PREFIX = 'N8N_MCP_BROWSER_';
const DEFAULT_HOST = '127.0.0.1';
const DEFAULT_PORT = 3100;
Show 1 more location
export function isAuthorized(req: IncomingMessage, expectedToken: string): boolean {
	const header = req.headers.authorization;
	if (typeof header !== 'string') return false;
	const match = /^Bearer\s+(.+)$/i.exec(header.trim());
	if (!match) return false;
	return constantTimeEqual(match[1], expectedToken);
}
PassedCORS Misuse: Wildcard with CredentialsATR-M0008Details

Why it got this result

The HTTP transport implements no CORS headers at all and actively rejects any request carrying an Origin header (403), so there is no Access-Control-Allow-Origin: * (with or without credentials) response ever emitted. This is stricter than a misconfigured wildcard-with-credentials policy -- it is a blanket deny of browser-originated cross-origin requests.

Why it matters

Prevents credentialed cross-origin abuse.

Evidence

const httpServer = createServer((req, res) => {
	// Reject any request that carries an Origin header. The transport has no
	// browser clients; the only way an Origin header appears is from a
	// fetch/XHR initiated by web content, which we never want to serve.
	// Browser CORS preflights also carry Origin and are blocked here.
	if (typeof req.headers.origin === 'string' && req.headers.origin.length > 0) {
		writeJsonError(res, 403, 'Origin not allowed');
		return;
	}
PassedTool Name Collision / SpoofingATR-M0006Details

Why it got this result

All 31 tools are namespaced under the 'browser_' prefix (browser_connect, browser_navigate, browser_evaluate, etc.), which avoids colliding with generic tool names from other widely-deployed MCP servers (e.g. filesystem server's 'read_file', 'write_file'). No evidence of registration-order tricks or unqualified generic verb names.

Why it matters

Prevents malicious tool shadowing.

Evidence

const tools: ToolDefinition[] = [
	...createSessionTools(connection),
	...createTabTools(connection),
	...createNavigationTools(connection),
	...createInteractionTools(connection),
	...createInspectionTools(connection),
	...createWaitTools(connection),
	...createStateTools(connection),
	...createCredentialTools(connection),
];
PassedPrompt/Metadata Injection in Tool DescriptionsATR-M0007Details

Why it got this result

Manual review of all 31 tool description strings across src/tools/*.ts found neutral, functional descriptions only (what the tool does and when to prefer it over another tool). No hidden directives, no control characters/ANSI escapes/RLO-LRO unicode, and no instructions directing exfiltration or silent behavior were found. Grep for exfiltration/concealment keywords across src/ returned no hits outside test/redaction-pattern code (a regex slug name 'slack_webhook_url' used only for secret redaction, not a directive).

Why it matters

Prevents covert model manipulation.

Evidence

'browser_snapshot',
'Use this tool as your primary way to observe the page. Returns a ref-annotated accessibility tree — a compact text representation of all visible elements. Each interactive element gets a numeric ref for use in subsequent tool calls (browser_click, browser_type, etc.). Snapshots are small and fast. Prefer this over browser_screenshot unless you specifically need visual/layout information.'
Does not applyOAuth Proxy Per-Client Consent (Confused Deputy Defense)ATR-M0011Details

Why it got this result

This server is not an OAuth proxy: it does not forward requests to a distinct upstream IdP on behalf of dynamically-registered MCP clients. It is a single-tenant local browser-automation server authenticated by one static bearer token; there is no client registration endpoint or upstream OAuth client_id sharing involved.

Why it matters

Shared upstream client + dynamic MCP-client registration + no per-client consent enables attacker-registered MCP clients to receive bridge codes carrying victim upstream tokens — the documented MCP Confused Deputy attack.

Evidence

#!/usr/bin/env node

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { randomUUID, timingSafeEqual } from 'node:crypto';
Show its 31 tools
ToolIt can
browser_back
Navigate back in history
Create or change data
browser_capture_secret
Read a secret value from a DOM element and store it in the session buffer (never returned to LLM)
Use secrets and credentials
browser_click
Click an element on the page
Create or change data
browser_connect
Connect to the user's browser via the AI Browser Bridge extension
Change settings
browser_console
Get console messages and page errors
Read dataReturns untrusted content
browser_content
Extract page content as structured markdown
Read dataReturns untrusted content
browser_cookies
Get, set, or clear cookies
Read dataCreate or change dataUse secrets and credentials
browser_create_credential
Assemble buffered secrets into a new n8n credential
Create or change dataUse secrets and credentialsChange settings
browser_dialog
Handle browser dialogs (alert/confirm/prompt)
Create or change data
browser_disconnect
Disconnect from the browser and release resources
Change settings
browser_drag
Drag and drop an element
Create or change data
browser_evaluate
Execute arbitrary JavaScript in the page context and return result
Run code or commandsCreate or change dataReach any network addressReturns untrusted content
browser_forward
Navigate forward in history
Create or change data
browser_hover
Hover over an element
Create or change data
browser_navigate
Navigate to a URL and wait for load
Create or change dataReach any network address
browser_network
Get recent network requests and responses
Read dataReturns untrusted contentUse secrets and credentials
browser_pdf
Generate a PDF of the current page
Read dataReturns untrusted content
browser_press
Press a keyboard key
Create or change data
browser_reload
Reload the current page
Create or change data
browser_screenshot
Take a screenshot of the page or element
Read dataReturns untrusted content
browser_scroll
Scroll the page
Create or change data
browser_select
Select an option in a dropdown
Create or change data
browser_snapshot
Get ref-annotated accessibility tree of the page
Read dataReturns untrusted content
browser_storage
Get, set, or clear localStorage/sessionStorage
Read dataCreate or change dataUse secrets and credentials
browser_tab_close
Close a tab
Delete or overwrite data
browser_tab_focus
Switch the active tab
Create or change data
browser_tab_list
List all browser tabs
Read data
browser_tab_open
Open a new browser tab, optionally navigating to a URL
Create or change dataReach any network address
browser_type
Type text into an element
Create or change data
browser_upload
Upload a file to a file input element
Create or change dataUse secrets and credentialsReach any network address
browser_wait
Wait for a condition on the page
Read data

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/gh-stack.agents/skills/gh-stack76Moderate trust1 critical

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 1 of 8 flagged

7 rules passedShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

This skill documents usage of the third-party gh-stack GitHub CLI extension for managing stacked PRs. Its content (init/add/push/submit/rebase/view/checkout/unstack commands) matches the declared description exactly; all actions are git/GitHub operations the user explicitly installs and invokes via gh extension install github/gh-stack. No hidden network egress, credential harvesting, or unrelated capability is directed.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
description: |
    Manage stacked branches and pull requests with the gh-stack GitHub CLI extension. Use when the user wants to create, push, rebase, sync, navigate, or view stacks of dependent PRs. Triggers on tasks involving stacked diffs, dependent pull requests, branch chains, or incremental code review workflows.
metadata:
    author: github
    github-path: skills/gh-stack
    github-ref: refs/tags/v0.1.0
    github-repo: https://github.com/github/gh-stack
    github-tree-sha: c95c8b5b4dd850f3fef007b304428f5684f2fb87
    version: 0.0.9
name: n8n:gh-stack
---
PassedHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

Requiring an authenticated GitHub CLI session is a prerequisite for using the user's own already-authenticated session; the skill's instructions never direct it to read, expose, or transmit the session token or other credential material, so the protected-data harvest required by this rule is absent.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

## Prerequisites

The GitHub CLI (`gh`) v2.0+ must be installed and authenticated. Install the extension with:

```bash
gh extension install github/gh-stack
```
PassedFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The extension installation (gh extension install github/gh-stack) is an explicit, user-initiated CLI instruction documented in the prerequisites, not an artifact-shipped activation hook, load-time script, or covert persistent directive embedded in the skill itself.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

The GitHub CLI (`gh`) v2.0+ must be installed and authenticated. Install the extension with:

```bash
gh extension install github/gh-stack
```
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

This skill has no allowed-tools frontmatter restricting capability at all (it documents usage of a separately-installed gh extension); it is text guidance, not a capability grant. The operations documented (git/gh stack management) match its single declared purpose with no unrelated capability class requested.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
description: |
    Manage stacked branches and pull requests with the gh-stack GitHub CLI extension. Use when the user wants to create, push, rebase, sync, navigate, or view stacks of dependent PRs.
metadata:
    author: github
    github-path: skills/gh-stack
    github-ref: refs/tags/v0.1.0
    github-repo: https://github.com/github/gh-stack
    github-tree-sha: c95c8b5b4dd850f3fef007b304428f5684f2fb87
    version: 0.0.9
name: n8n:gh-stack
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Scanned for concealment codepoints and homoglyphs; none found. All imperative prose ('Always supply branch names...', 'Never do any of the following') is plainly visible, functional CLI-usage guidance for the declared gh-stack extension, not a hidden injected directive.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

## Agent rules

**All `gh stack` commands must be run non-interactively.** Every command invocation must include the flags and positional arguments needed to avoid prompts, TUIs, and interactive menus.
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

This skill explicitly pins its upstream provenance (github-repo, github-ref=refs/tags/v0.1.0, github-tree-sha, version) in frontmatter metadata, and the file itself is checked into the repo (version-controlled, not fetched at agent runtime). The referenced gh-stack CLI extension is installed explicitly by the user via gh extension install, outside this skill's own execution.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
description: |
    Manage stacked branches and pull requests with the gh-stack GitHub CLI extension.
metadata:
    author: github
    github-path: skills/gh-stack
    github-ref: refs/tags/v0.1.0
    github-repo: https://github.com/github/gh-stack
    github-tree-sha: c95c8b5b4dd850f3fef007b304428f5684f2fb87
    version: 0.0.9
name: n8n:gh-stack
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name is namespaced 'n8n:gh-stack' and the metadata block explicitly and correctly attributes authorship to 'github' with the real upstream repo URL (github/gh-stack), tag ref, and tree SHA -- this is accurate provenance disclosure, not impersonation.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

metadata:
    author: github
    github-path: skills/gh-stack
    github-ref: refs/tags/v0.1.0
    github-repo: https://github.com/github/gh-stack
    github-tree-sha: c95c8b5b4dd850f3fef007b304428f5684f2fb87
    version: 0.0.9
name: n8n:gh-stack
---

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/linear-issue.agents/skills/linear-issue60Low trust1 critical

Its tools can

Run code or commands 1Use secrets and credentials 1Reach any network address 1Returns untrusted content 1Read data 1

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 1 of 8 flagged

6 rules passed · 1 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose (fetch/analyze Linear issue context) explicitly requires gh CLI and curl for downloading images/PR data -- all disclosed in frontmatter compatibility.requires. No credential harvesting beyond the already-authenticated gh token used for its stated GitHub-fetch purpose, which is an implied-in-scope capability per the false_positive_guard (e.g. a 'GitHub PR reviewer' reading a git token).

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:linear-issue
description: Fetch and analyze Linear issue with all related context...
compatibility:
  requires:
    - mcp: linear
      description: Core dependency — used to fetch issue details, relations, and comments
    - cli: gh
      description: GitHub CLI — used to fetch linked PRs and issues. Must be authenticated (gh auth login)
  optional:
    - mcp: notion
    - skill: loom-transcript
    - cli: curl
---
Show 1 more location
**GitHub PRs and Issues:**
- If GitHub links are mentioned, use `gh` CLI to fetch PR/issue details:
	- `gh pr view <number>` for pull requests
	- `gh issue view <number>` for issues
- Download images attached to issues: `curl -H "Authorization: token $(gh auth token)" -L <image-url> -o image.png`
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No language directs the agent to bypass confirmation or hide actions. The skill actually directs the agent to STOP and surface a security warning to the user when a private/security-labeled issue is detected in the wrong repo context -- a disclosure-promoting, not suppressing, instruction.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

### 2. Check for Private/Security Issues (MANDATORY — do this before anything else)
...
2. If any label is **`n8n-private`**:
   a. Run `git remote -v` ...
   b. If **any** remote URL contains `n8n-io/n8n` without the `-private` suffix ..., **stop immediately** and tell the user:
   > **This issue is marked `n8n-private` and must be developed in a clean clone of the private repository.**
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

The compatibility frontmatter block enumerates exactly the external dependencies the skill needs (Linear MCP required, gh CLI required, Notion MCP optional, curl optional, loom-transcript skill optional) each with a one-line justification tying it to the declared fetch/analyze function -- a narrow, justified, non-wildcard declared scope, not an over-broad grant.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

compatibility:
  requires:
    - mcp: linear
      description: Core dependency — used to fetch issue details, relations, and comments
    - cli: gh
      description: GitHub CLI — used to fetch linked PRs and issues. Must be authenticated (gh auth login)
  optional:
    - mcp: notion
      description: Used to fetch linked Notion documents. Skip Notion steps if unavailable.
    - skill: loom-transcript
      description: Used to fetch Loom video transcripts. Skip Loom steps if unavailable.
    - cli: curl
      description: Used to download images/attachments. Typically pre-installed.
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan found only normal arrow/comparison/em-dash punctuation; no concealment codepoints, no forged system/role tokens, no HTML-comment or CSS-hidden content.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

(scanned full file: only —,≥,→,≤,– present as prose punctuation; no concealment codepoints, no forged role tokens)
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Instructions ship inside the bundled, version-controlled SKILL.md; no runtime fetch of its own instructions, no self-updater, no rewriting of its own manifest.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

(static bundled text, no fetch/self-update mechanism present)
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Declared name n8n:linear-issue correctly names the third-party service it integrates with (Linear) using the canonical spelling, with no claim of being an official Linear product, no trust-implying prefix, and no version-spoofing suffix -- it is clearly an n8n-authored integration skill, published from the n8n-io/n8n repo itself.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The component only directs in-band use of registered CLI or MCP tools and includes no bundled execution or persistence primitive.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

(no bundled scripts, hooks, or env assignments in this component; only prose directing use of already-registered CLI/MCP tools)
Show its 1 tool
ToolIt can
linear-issue-fetch-flow
Fetches Linear issue, comments, images, Loom transcripts, GitHub PRs/issues, Notion docs via MCP tools and gh/curl CLI as directed by the skill prose
Read dataReturns untrusted contentReach any network addressRun code or commandsUse secrets and credentials

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/agent-design-language.agents/skills/agent-design-language100High trustNone flagged

Its tools can

Create or change data 1Read data 1

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

SKILL.md is a pure-declarative UI guidance artifact: it directs the model to read two documentation files and cross-reference two sibling skills for Agent Builder UI work. No bundled scripts, no network egress, no credential/env reads, no subprocess/eval. Every instruction maps 1:1 to the declared purpose (UI design guidance scoped to packages/frontend/editor-ui/src/features/agents).

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:agent-design-language
description: Design or review Agent Builder interfaces. Use for Agent configuration patterns, modal flows, responsive behavior, status treatments, and other Agent-only UI work.
---

# Agent design language

Use this skill only for interface work in
`packages/frontend/editor-ui/src/features/agents`.

Before you change code:

1. Read `packages/frontend/editor-ui/src/features/agents/AGENTS.md`.
2. Read `packages/frontend/editor-ui/src/features/agents/agents-design-language.md`
…
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No prose anywhere in SKILL.md directs the host to skip confirmation, hide actions from the user, or claims authority over the system prompt. Purely task-scoped UI guidance.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

(full file reviewed -- no consent/confirmation/disclosure-suppression language present)
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools/scope/permission declaration of any kind in the frontmatter; the skill is a plain markdown instruction set with no self-declared capability grant to assess for over-breadth. No wildcard, no bundled executables, no install hooks.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:agent-design-language
description: Design or review Agent Builder interfaces. Use for Agent configuration patterns, modal flows, responsive behavior, status treatments, and other Agent-only UI work.
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint-scanned the file for zero-width/bidi/Tag-block characters and mixed-script confusables -- none found; only normal em-dash punctuation. No forged chat-template tokens, no HTML-comment concealment, no imperative aimed at overriding the host's system prompt.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

(scanned full file: only standard em-dash U+2014 present; no concealment codepoints, no forged role tokens)
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

The instruction body ships entirely inside the bundled SKILL.md file in the git repository; there is no runtime fetch of the instructions from a remote/mutable endpoint, no auto-update mechanism, and no self-mutation code. Version control (git) provides the change record for this file.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

(entire instruction body is static text committed to the repo; no fetch()/load_extension/hot-reload logic present)
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Declared name n8n:agent-design-language is namespaced under the repo's own n8n: prefix (sourced from the n8n-io/n8n repo itself), is a plain descriptive name with no resemblance to any well-known third-party tool/brand, no trust-implying prefix, no version-spoofing suffix, and no impersonation prose.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill performs no read of any host credential store, env var, or conversation/memory context -- it only directs reading two local documentation markdown files. No harvest step exists, so the harvest+exfil pair this rule tests for cannot be constructed.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

(no env/credential/memory read of any kind in this file)
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The component is prose-only guidance and contains no executable hook, environment override, host-control write, or other control-plane persistence primitive within the scope of this rule.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

(entire component is a single SKILL.md file with no scripts, hooks, or env assignments)
Show its 1 tool
ToolIt can
agent-design-language-guidance
UI design guidance for Agent Builder interface work
Read dataCreate or change data

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/community-pr-readiness-check.agents/skills/community-pr-readiness-check100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

SKILL.md declares allowed-tools: Bash(gh:*), Bash(git:*), Bash(node:*), Read, Glob, Grep and a clear purpose (PR readiness triage). All actions in the body (gh pr/api calls, Linear MCP ticket updates, running owners.mjs) map 1:1 to the declared purpose of reviewing and triaging community PRs. No undeclared network egress, credential reads, or exec capability found.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:community-pr-readiness-check
description: >-
  Checks if a community pull request is ready for human review...
allowed-tools: Bash(gh:*), Bash(git:*), Bash(node:*), Read, Glob, Grep
compatibility:
  requires:
    - mcp: linear
      description: Required for reading and updating Linear tickets during triage
    - cli: gh
      description: Required for PR inspection and triage actions. Must be authenticated (gh auth login)
---
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

Reviewed SKILL.md body: every destructive/triage action (closing PRs, label changes, Linear ticket updates) is explicitly gated behind an 'Ask the user' prompt in Step 7 (e.g. 'Ask Close + comment / Edit before closing / Skip'). No prose instructs the agent to skip confirmation, hide an action from the user, or claim authority over host system prompt.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## Step 7 — Action paths

Ask the user for each prompt (presented as the listed options). Sub-agents called for analysis only should stop after step 6 and let the caller drive step 7.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

allowed-tools is an enumerated, scoped list (Bash(gh:*), Bash(git:*), Bash(node:*), Read, Glob, Grep) -- not a wildcard. gh/git/node shell access is intrinsic to and justified by the declared function (PR triage requires gh for GitHub operations, git for inspecting diffs, node for running the owners.mjs script). No filesystem-write, broader network, or ambient-credential capability is declared beyond this.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

allowed-tools: Bash(gh:*), Bash(git:*), Bash(node:*), Read, Glob, Grep
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint-scanned all files in this skill directory for zero-width/bidi/Tag-block characters and found none. Content is 7-bit ASCII, purely descriptive, with no forged chat-template tokens, ANSI escapes, or HTML-comment concealment.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:community-pr-readiness-check
description: >-
  Checks if a community pull request is ready for human review...
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

SKILL.md and its reference/*.md ship inside the repo's git-tracked bundle; there is no runtime fetch of the artifact's own instructions from an external endpoint and no self-mutation/auto-update mechanism. A change to this file is a normal reviewed git commit like any other repo file.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:community-pr-readiness-check
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'n8n:community-pr-readiness-check' is published under the n8n org's own namespace prefix from the n8n-io/n8n repo itself -- the source-host org matches the declared identity. No near-miss of a third-party brand, no trust-implying prefix, no version-spoofing suffix.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No instruction anywhere directs reading host credential stores, env secrets, or the model's conversation/memory context. The skill reads PR metadata via gh/Linear APIs using already-registered, host-authenticated tools -- it never reads raw credentials or dumps conversation history to an external sink.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

## Step 1 — Resolve the PR

If given a branch name, find the PR number first:

```bash
gh pr view <branch> --repo n8n-io/n8n --json number --jq .number
```
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The allowed-tools declaration only exposes normal in-band tools (gh, git, node, Read, Glob, Grep) and does not ship an executable, lifecycle hook, dotenv/host-config file, or persistent control-plane mechanism of its own.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: n8n:community-pr-readiness-check
...
allowed-tools: Bash(gh:*), Bash(git:*), Bash(node:*), Read, Glob, Grep
---

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/content-design.agents/skills/content-design100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

SKILL.md declares a narrow, content-design purpose (write/review/audit UI copy) and grants only Read, Grep, Glob, Edit via allowed-tools. The body is entirely prose guidance (style rules, terminology, grading checklist) with no network egress, no credential reads, no subprocess execution, and no hidden/obfuscated code. All actual capability (reading/editing files in the repo to change copy) maps 1:1 to the declared purpose.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:content-design
description: >
  Product content designer for UI copy. Use when writing, reviewing, or auditing
  user-facing text: button labels, error messages, tooltips, empty states, modal copy,
  placeholder text, confirmation dialogs, onboarding flows, or i18n strings.
  Also use when the user says /copy, /content, or /ux-copy.
allowed-tools: Read, Grep, Glob, Edit
---
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No prose in the skill body instructs the agent to suppress confirmation, hide actions from the user, or claims authority over the host system prompt. The skill is purely about copywriting style and review workflow.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## How to work

### Modes

When invoked, determine what the user needs:

1. **Write** — Draft new UI copy. Ask what surface...
2. **Review** — The user shares existing copy or points to a file. Check it
   against every rule below. Return a table:...
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

allowed-tools is a minimal, enumerated list (Read, Grep, Glob, Edit) matching the stated copy-editing function -- no Bash, WebFetch, or wildcard capability grant, and no bundled executables or install hooks.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

allowed-tools: Read, Grep, Glob, Edit
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

The entire SKILL.md body is ASCII, purely descriptive prose with no imperative directed at the host model to override instructions, no zero-width/bidi/control codepoints, no forged chat-template tokens, and no homoglyphs.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:content-design
description: >
  Product content designer for UI copy...
---

# n8n content design

You are a Senior Content Designer specializing in SaaS tools...
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

SKILL.md ships inline inside the repo bundle; nothing in it live-fetches instructions from a remote/mutable endpoint, and there is no self-update or hot-reload mechanism present.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:content-design
description: >
  Product content designer for UI copy...
allowed-tools: Read, Grep, Glob, Edit
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

The name n8n:content-design is a first-party-namespaced, descriptive name published directly in the n8n-io/n8n repo -- not a near-miss or homoglyph of any known brand, and carries no trust-implying prefix or version-spoofing suffix.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill reads only repo source files (i18n JSON, Vue templates) via Read/Grep/Glob for copy auditing; it performs no read of any host credential store or env secrets, and has no outbound network channel at all (allowed-tools lacks any fetch/bash capability). No harvest step exists, so the harvest+exfil pair this rule tests for cannot be constructed.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

allowed-tools: Read, Grep, Glob, Edit
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The Markdown skill contains only prose and ordinary tool permissions, with no format-native execution or persistence primitive.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: n8n:content-design
description: >
  Product content designer for UI copy...
allowed-tools: Read, Grep, Glob, Edit
---

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/conventions.agents/skills/conventions100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

8 rules passedShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

This skill is a pure documentation/reference artifact (coding conventions, patterns, commands) with no allowed-tools restriction beyond the implicit host toolset and no bundled scripts. Its content matches its declared purpose: a quick-reference for n8n coding patterns. No network egress, credential reads, or exec capability is directed anywhere in the body.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:conventions
description: Quick reference for n8n patterns. Full docs /AGENTS.md
---

# n8n Quick Reference

**📚 Full Documentation:**
- **General:** `/AGENTS.md` - Architecture, commands, workflows
- **Frontend:** `/packages/frontend/AGENTS.md` - CSS variables, timing

Use this skill when you need quick reminders on critical patterns.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No prose directs skipping confirmation, hiding actions from the user, or bypassing any human-in-the-loop gate. Content is purely coding-convention reference material.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## Critical Rules (Must Follow)

**Technical writing (comments, PRs, issues, docs):**
- Write in ASD-STE100 Simplified Technical English: short sentences, the
  active voice, one instruction for each sentence
PassedHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill's only secret-related content warns against placing secrets on command lines; it does not itself read from credential stores or transmit any protected data to an external destination.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

**Commands:**
```bash
pnpm build > build.log 2>&1  # Always redirect
pnpm typecheck               # Before commit
pnpm lint                    # Before commit
```
> Secrets: pnpm command lines may be recorded verbatim (opt-in dev metrics) —
> pass sensitive values via env vars, never inline on the command line.
PassedFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The skill's frontmatter contains only a name and description field; it declares no lifecycle hook, executable carrier, or host-control modification for this rule to evaluate.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: n8n:conventions
description: Quick reference for n8n patterns. Full docs /AGENTS.md
---
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools/capability declaration at all beyond the default host toolset implied by a reference doc; no capability class (exec, network, fs-write, credentials) is granted or requested.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:conventions
description: Quick reference for n8n patterns. Full docs /AGENTS.md
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Scanned the file for zero-width/bidi/tag-block concealment codepoints and mixed-script homoglyphs; none found (only ordinary emoji/dash/arrow Unicode). No hidden directives detected.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:conventions
description: Quick reference for n8n patterns. Full docs /AGENTS.md
---

# n8n Quick Reference
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

First-party, in-repo static Markdown file shipped and version-controlled with the n8n monorepo itself -- not fetched at runtime from a mutable external endpoint, and changes go through the repo's normal code-review/PR process.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:conventions
description: Quick reference for n8n patterns. Full docs /AGENTS.md
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'n8n:conventions' is a first-party namespaced skill shipped inside the n8n monorepo itself, with no impersonation of a third-party brand, no typosquat, no trust-implying prefix, and no version-spoofing suffix.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

---
name: n8n:conventions
description: Quick reference for n8n patterns. Full docs /AGENTS.md

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/create-agent-builder-eval.agents/skills/create-agent-builder-eval100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

7 rules passed · 1 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

SKILL.md's declared purpose (author/calibrate Instance AI agent-builder evals) matches its actual instructed behavior: it directs reading LangTracer env vars, drafting JSON eval cases, running pnpm scripts, and proposing Linear tickets -- all within the stated eval-authoring scope. It explicitly instructs NOT to print/inspect the LANGTRACER_API_KEY value and not to ask the user to paste it into chat, which is a privacy-preserving instruction rather than a harvesting one.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

Do not ask the user to paste the key into chat. Do not print or inspect its
value. Rerun the check after the user confirms. Continue only when it passes.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No prose instructs skipping confirmation, hiding actions from the user, or asserting authority over the host system prompt. The skill repeatedly routes decisions back through the driver/user (autonomy level, target suite) rather than bypassing them.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## Set the autonomy level first

Follow "Set the autonomy level first" in
[create-instance-ai-eval](../create-instance-ai-eval/SKILL.md). Ask the driver
for *autonomous* or *checkpoint* mode before you source or draft anything.
PassedHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The component loads LangTracer credentials (LANGTRACER_URL/LANGTRACER_API_KEY) from the repository-root .env.eval into its declared publish/run eval commands, so secret handling is in scope. Instructions explicitly forbid printing or inspecting the key value and forbid pasting it into chat, and the key is used only to authenticate to the first-party LangTracer service the skill is designed to publish to. No arbitrary environment enumeration, credential-store read, or conversation-history exfiltration occurs.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

pnpm exec dotenvx run -f ../../../.env.eval -- \
  sh -c 'test -n "${LANGTRACER_URL:-}" && test -n "${LANGTRACER_API_KEY:-}"'
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools frontmatter restricting/declaring a capability class is present, and the skill's actual instructed actions (pnpm/dotenvx shell commands, LangTracer tool calls) match its stated eval-authoring purpose; no wildcard or catch-all capability grant is declared.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:create-agent-builder-eval
description: >-
  Authors and calibrates Instance AI evaluations ...
shared_references:
  - credential-setup-with-computer-use
recommended_tools:
  - build-agent
  - agent-context
  - build-workflow
  - data-tables
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan of SKILL.md found zero zero-width/bidi-override/Tag-block characters; no mixed-script homoglyphs, forged chat-template tokens, or ANSI escapes. All content is plain descriptive ASCII prose.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:create-agent-builder-eval
description: >-
  Authors and calibrates Instance AI evaluations that build standalone n8n
  Agents through Agent Builder. ...
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

The skill ships inside the repo as a static SKILL.md file loaded from the local filesystem by the host (.agents/skills/); no runtime fetch of its own instructions from a mutable endpoint, no self-update mechanism, no post-approval self-rewrite instructed.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:create-agent-builder-eval
description: >-
  Authors and calibrates Instance AI evaluations ...
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'n8n:create-agent-builder-eval' matches canonical n8n internal naming convention (n8n:<name> namespace); source-host org is n8n-io/n8n itself (verified via git remote). No near-miss of a well-known external brand, no trust-implying prefix, no impersonation prose.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

---
name: n8n:create-agent-builder-eval
description: >-
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The skill bundle contains only Markdown instruction files (SKILL.md and local-setup.md). The shell snippets (pnpm exec dotenvx, pnpm eval:*) are commands the agent issues only when explicitly executed as part of its normal tool-call flow, not auto-executed lifecycle hooks, load-time scripts, or shipped control-plane primitives. This architecture falls outside the scope this rule targets.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: n8n:create-agent-builder-eval
description: >-
  Authors and calibrates Instance AI evaluations ...
---
# Create an Agent Builder eval
Use the shared Instance AI eval harness.

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/create-community-node-lint-rule.agents/skills/create-community-node-lint-rule100High trustNone flagged

Its tools can

Run code or commands 1Create or change data 1

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Pure scaffolding/documentation skill for authoring ESLint rules in a specific package. No network egress, no credential reads, no undisclosed exec capability. All actions (writing rule/test/doc files, running pnpm test/typecheck/build) map to the declared purpose.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:create-community-node-lint-rule
description: >-
  Create new ESLint rules for the @n8n/eslint-plugin-community-nodes package.
  Use when adding a lint rule, creating a community node lint, or working on
  eslint-plugin-community-nodes. Guides rule implementation, tests, docs, and
  plugin registration.
---

# Create Community Node Lint Rule

Guide for adding new ESLint rules to `packages/@n8n/eslint-plugin-community-nodes/`.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

Pure scaffolding guidance; no language instructs bypassing confirmation or hiding actions from the user.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

(full file reviewed -- no consent/confirmation/disclosure-suppression language present)
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools/scope frontmatter; no bundled executables or install hooks present in the component directory (only SKILL.md and reference.md).

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:create-community-node-lint-rule
description: >-
  Create new ESLint rules for the @n8n/eslint-plugin-community-nodes package.
...
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan found only normal em-dash punctuation; no zero-width/bidi/Tag-block characters, no forged chat-template tokens, no HTML-comment concealment.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

(scanned full file + reference.md: only standard em-dash present; no concealment codepoints)
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Instructions ship inside the bundled, version-controlled SKILL.md/reference.md; no runtime fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

(static bundled text, no fetch/self-update mechanism present)
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Declared name n8n:create-community-node-lint-rule is self-descriptive, namespaced under the repo's own org prefix, with no resemblance to a known brand/tool.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No credential store, env var, or conversation-context read is present or directed; purely local scaffolding instructions.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The documented pnpm test/typecheck/build commands are ordinary user-invoked project verification steps, not package lifecycle hooks, persistent prompt mutation, or endpoint/environment hijacking; the component ships no hook, executable entrypoint, environment assignment, or persistent host modification.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

(no bundled scripts, hooks, or env assignments in this component)
Show its 1 tool
ToolIt can
create-community-node-lint-rule-guidance
Scaffolding guidance for new ESLint rules in the community-nodes plugin package
Create or change dataRun code or commands

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/create-instance-ai-eval.agents/skills/create-instance-ai-eval100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose: authoring/pushing Instance AI eval cases to LangTracer. Body describes only file authoring, local validation commands, and pushing to the declared LangTracer API via documented env vars (LANGTRACER_URL/LANGTRACER_API_KEY). No undeclared credential harvesting, exec, or network egress beyond the stated LangTracer suite API.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

- **Env:** `LANGTRACER_URL` + `LANGTRACER_API_KEY` (an `lt_` bearer; one key works
  for MCP + REST) — put them in `.env.eval` and run under `pnpm exec dotenvx`.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

The skill repeatedly asks the driver for decisions (suite target, autonomy level) and explicitly calls out that calibration verdicts 'never fully auto-commit' and must be surfaced. 'Autonomous mode' here means the AGENT decides when to stop at pre-defined gates, not that it hides actions from the user -- a decision log is always produced at the end for driver review. This is operator-configured autonomy, not an artifact instructing the host to bypass oversight.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

**Calibration is special-cased at both levels.** A calibration verdict that
flips a case's *meaning* ... is **surfaced explicitly**
(interactively in checkpoint; in the decision log in autonomous), never silently
committed.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools/permissions frontmatter block is declared at all (just name+description); the skill relies on whatever tools the host session already has, and its body only documents pnpm scripts and LangTracer API usage consistent with its declared eval-authoring purpose. No wildcard or install-time script shipped by the skill itself.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:create-instance-ai-eval
description: >-
  Authors a new Instance AI workflow or Agent eval case — written locally as
  JSON, calibrated against a real build, then pushed to the LangTracer suite CI
  runs...
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint-scanned the full skill directory (SKILL.md + reference .md files) for zero-width/bidi/Tag-block characters and found none; no forged chat-template tokens or ANSI escapes detected.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:create-instance-ai-eval
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Instructions ship inside the pinned git-tracked bundle; no runtime fetch of the skill's own instruction body/manifest occurs (the LangTracer fetch is for eval case DATA, not the skill's own instructions).

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:create-instance-ai-eval
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'n8n:create-instance-ai-eval' published under n8n's own namespace from the n8n-io/n8n repo; no brand impersonation.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The documented LangTracer bearer is loaded from .env.eval and used only with the declared LangTracer MCP or REST service; no instruction directs reading host credential stores or dumping conversation/system-prompt context for an external sink.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

- **Env:** `LANGTRACER_URL` + `LANGTRACER_API_KEY` (an `lt_` bearer; one key works
  for MCP + REST) — put them in `.env.eval` and run under `pnpm exec dotenvx`.
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The dotenvx and pnpm snippets are explicit, in-band authoring commands rather than bundled lifecycle hooks or load-time execution primitives.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

```bash
cd packages/@n8n/instance-ai
# <suite> is the slug the driver chose (e.g. baseline)
# preview first — no writes:
pnpm exec dotenvx run -f .env.eval -- pnpm eval:langtracer-push --suite <suite> --dry-run --changed
```

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/create-issue.agents/skills/create-issue100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose (create Linear tickets / GitHub issues) matches the actual behavior described: using a declared Linear MCP tool and the gh CLI to file issues, following explicit field/style templates. No undeclared network egress, credential reads, or subprocess execution beyond the declared gh issue create invocation, which matches the stated 'cli: gh' requirement in frontmatter.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:create-issue
description: Create Linear tickets or GitHub issues following n8n conventions...
argument-hint: "[linear|github] <description of the issue>"
compatibility:
  requires:
    - mcp: linear
      description: Required for creating Linear tickets
    - cli: gh
      description: Required for creating GitHub issues. Must be authenticated (gh auth login)
---
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

The skill explicitly requires a 'Present a preview' step and 'Wait for user confirmation' before creating either a Linear ticket or a GitHub issue -- it defers to, rather than bypasses, human-in-the-loop confirmation. No prose instructs skipping confirmation or hiding actions from the user.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

2. **Present a preview** before creating — show the user:
   - Title
   - Team
   - Status
   - Priority
   - Labels
   - Description (abbreviated if long)

3. **Wait for user confirmation** — do not create until the user approves
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

The frontmatter declares no allowed-tools wildcard; it lists explicit compatibility.requires entries (mcp: linear, cli: gh) scoped tightly to the declared issue-filing function, with no shell/network/filesystem reach beyond that.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

argument-hint: "[linear|github] <description of the issue>"
compatibility:
  requires:
    - mcp: linear
      description: Required for creating Linear tickets
    - cli: gh
      description: Required for creating GitHub issues. Must be authenticated (gh auth login)
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Instruction body is ASCII, purely descriptive/prescriptive style-guide and workflow text with no hidden codepoints, forged chat-template tokens, or homoglyphs.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:create-issue
description: Create Linear tickets or GitHub issues following n8n conventions...
---

# Create Issue

Create a Linear ticket or GitHub issue for: **$ARGUMENTS**
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

SKILL.md ships inline in the bundle; no runtime fetch of instructions/manifest from a mutable endpoint and no self-update mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:create-issue
description: Create Linear tickets or GitHub issues following n8n conventions. Use when the user asks to create a ticket, file a bug, open an issue, or says /create-issue.
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name n8n:create-issue is descriptive and first-party-namespaced, not a near-miss of any well-known brand/tool.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill never reads host credential stores, env secrets, or conversation/memory context; its only I/O is user-provided ticket content routed to a declared Linear MCP tool or the gh CLI (both are the artifact's declared, in-scope destinations, not an undisclosed exfiltration sink). No harvest step exists.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

4. **Create the ticket** using the available Linear MCP issue-creation tool:
   ```
   title: <title>
   team: <team name>
   description: <markdown description>
   priority: <priority number>
   state: <status name>
   labels: [<label names>]
   ```
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The documented gh issue create invocation is an explicit, model-mediated action taken after user confirmation, not an automatic lifecycle hook, environment variable assignment, or persistent host-control-plane modification.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

5. **Create the issue** using `gh`:
   ```bash
   gh issue create --repo n8n-io/n8n --title "<title>" --body "$(cat <<'EOF'
   <body content>
   EOF
   )"
   ```

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/create-pr.agents/skills/create-pr100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

8 rules passedShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose ('Creates GitHub pull requests...') matches the allowed-tools scope (Bash(git:*), Bash(gh:*), Read, Grep, Glob) and the body's instructions, which only cover git/gh commands for inspecting diffs and creating a PR. No undeclared network egress, credential harvesting, or exec capability beyond git/gh is present. The 'Security Fixes' section explicitly instructs REDACTING vulnerability details from public artifacts, which is a protective, not malicious, directive.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:create-pr
description: Creates GitHub pull requests with properly formatted titles that pass the check-pr-title CI validation. Use when creating PRs, submitting changes for review, or when the user says /pr or asks to create a pull request.
allowed-tools: Bash(git:*), Bash(gh:*), Read, Grep, Glob
---
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

The skill explicitly preserves human oversight: the PR checklist requires the human author to confirm they reviewed/ran/take responsibility for the code, and plan-inclusion in the PR body requires explicit user approval before being added. The Security Fixes section instructs obscuring vulnerability details in PUBLIC artifacts (branch/PR/commit text) for a public repo -- this is standard responsible-disclosure practice (avoid advertising an exploit before/while it's fixed), not suppression of the human reviewer's knowledge: the human author/reviewer still sees the full diff and (per the Linear-issue note) can check extra precautions; it does not instruct hiding the vulnerability from the Linear issue, the reviewer, or the merge decision itself.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## Security Fixes

**This repo is public.** Never expose the attack vector in any public artifact.
Describe **what the code does**, not what threat it prevents.

| Artifact | BAD | GOOD |
|---|---|---|
| Branch | `fix-sql-injection-in-webhook` | `fix-webhook-input-validation` |
...
**Before pushing a security fix, verify:** no branch name, commit, PR title,
PR body, Linear URL, test name, or code comment hints at the vulnerability.

**When in doubt, check the Linear issue for possible extra precautions**
Show 1 more location
### Checklist
All items should be addressed before merging:
- The human author of the PR has checked the "I have seen this code, I have run this code, and I take responsibility for this code." checkbox
- PR title follows conventions
PassedHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

Repository status, diffs, and commit history are ordinary task data rather than credentials or hidden conversation memory, so this rule has no protected-data source.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

1. **Check current state**:
   ```bash
   git status
   git diff --stat
   git log origin/master..HEAD --oneline
   ```
PassedFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

This prose skill declares no startup command, lifecycle callback, executable payload, or persistent control-plane setting.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: n8n:create-pr
description: Creates GitHub pull requests with properly formatted titles that pass the check-pr-title CI validation. Use when creating PRs, submitting changes for review, or when the user says /pr or asks to create a pull request.
allowed-tools: Bash(git:*), Bash(gh:*), Read, Grep, Glob
---
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

allowed-tools is a tightly enumerated, least-privilege list (git/gh Bash subcommands plus read-only Read/Grep/Glob) matching exactly what PR creation requires -- no wildcard Bash(*), no Write, no broad network tool.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

allowed-tools: Bash(git:*), Bash(gh:*), Read, Grep, Glob
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Scanned for concealment codepoints (zero-width/bidi/tag-block) and homoglyphs; none found (only ordinary emoji/ellipsis). No hidden directives.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:create-pr
description: Creates GitHub pull requests with properly formatted titles that pass the check-pr-title CI validation.
allowed-tools: Bash(git:*), Bash(gh:*), Read, Grep, Glob
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

First-party, in-repo static file under normal version control; no runtime fetch of instructions from a mutable endpoint.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:create-pr
description: Creates GitHub pull requests with properly formatted titles that pass the check-pr-title CI validation.
allowed-tools: Bash(git:*), Bash(gh:*), Read, Grep, Glob
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

First-party namespaced name 'n8n:create-pr' with no impersonation of any external brand or publisher.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

---
name: n8n:create-pr
description: Creates GitHub pull requests with properly formatted titles that pass the check-pr-title CI validation.

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/create-skill.agents/skills/create-skill100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

A purely declarative/instructional authoring guide (how to write SKILL.md files); no bundled scripts, no network/credential/exec capability of any kind -- text-only guidance matching its stated purpose exactly.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:create-skill
description: >-
  Guides users through creating effective Agent Skills. Use when you want to
  create, write, or author a new skill...
---
# Creating skills
Skills are markdown (plus optional scripts) that teach the agent a focused workflow.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No consent-bypass, disclosure-suppression, or authority-override prose; purely descriptive authoring guidance.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## Before you write: gather requirements
Ask (or infer) briefly:
1. **Purpose** ...
Ask the user in plain language when you need more detail.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No manifest capability grant at all -- purely descriptive guidance.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:create-skill
description: >-
  Guides users through creating effective Agent Skills. ...
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan found zero concealment characters; plain ASCII authoring guidance.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:create-skill
description: >-
  Guides users through creating effective Agent Skills. ...
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Statically bundled markdown with no runtime fetch, self-update, or self-rewrite behavior.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:create-skill
...
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name matches n8n's own internal naming convention; no brand impersonation.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

---
name: n8n:create-skill
description: >-
Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

Purely declarative authoring guidance; no credential or conversation-context read of any kind.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

## File layout
```
skill-name/
├── SKILL.md       # required
...
```
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

This component ships only a Markdown authoring guide (SKILL.md) and bundles no script, hook, entrypoint, dotenv, or other host-control-plane primitive, so there is no out-of-band execution or persistence mechanism for this rule to evaluate.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

## File layout
```
skill-name/
├── SKILL.md       # required
...
```

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/db-migrations.agents/skills/db-migrations100High trustNone flagged

Its tools can

Create or change data 1Read data 1

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Large (47KB) but purely declarative documentation skill for authoring TypeORM DB migrations. Manually reviewed throughout for network/exec/credential patterns -- none found beyond normal SQL/DSL guidance and references to the repo's own migration tooling (pnpm scripts, generator). No undisclosed capability.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:db-migrations
description: Authors n8n database migrations. Use when creating or modifying files under packages/@n8n/db/src/migrations/, when the user asks to add a column, table, index, foreign key, or backfill, or when the user mentions DB migrations or TypeORM migrations.
---

# n8n Migration Guidelines

**Rule of thumb:** the `@n8n-io/migrations-review` team gates every migration PR.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

Grepped the full 47KB file for consent-bypass/disclosure-suppression keywords (never ask, do not tell, silently, override system, autonomous mode, skip approval) -- only hits are benign uses of 'silently' describing code behavior (e.g. 'silently change the meaning of old migrations' as a caution against a bad pattern), not directives to the host agent to suppress disclosure.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

**Why:** Migrations are a historical record — they must work against the schema *as it existed when they were written*. Importing live entities means later refactors silently change the meaning of old migrations.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools/scope frontmatter and no bundled executables; single documentation file.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:db-migrations
description: Authors n8n database migrations. Use when creating or modifying files under packages/@n8n/db/src/migrations/...
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan of this 47KB file found only standard box-drawing/em-dash characters used for ASCII-art directory trees and prose punctuation; no zero-width/bidi/Tag-block characters, confusable homoglyphs, or forged chat-template tokens.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

(scanned full file: only box-drawing chars ├─ and em-dash present for directory tree/prose; no concealment codepoints)
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Instructions ship inside the bundled, version-controlled SKILL.md; no runtime fetch or self-mutation mechanism present.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

(static bundled text, no fetch/self-update mechanism present)
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Declared name n8n:db-migrations is self-descriptive and namespaced under the repo's own org prefix; no resemblance to any known brand.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No instruction reads env vars, credential dotfiles, keychains, or conversation/memory context; the file is entirely about SQL/TypeORM migration authoring conventions.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

(no env/credential/memory read present anywhere in the 47KB file)
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

This component ships a Markdown instruction carrier, not a bundled lifecycle hook, host config, entry script, or endpoint/exec-enabling environment assignment. The documentation-only component has no lifecycle hook, executable entrypoint, environment assignment, or host configuration primitive.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

(documentation-only component, no bundled execution primitives)
Show its 1 tool
ToolIt can
db-migrations-guidance
Authoring guidance for TypeORM DB migrations (schema DSL, raw SQL via runQuery)
Read dataCreate or change data

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/env-var-docs.agents/skills/env-var-docs100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

5 rules passed · 3 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Purely a documentation style-guide skill for writing JSDoc on @Env config properties. No tools declared, no network/exec/credential access of any kind -- pure prose guidance artifact.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:env-var-docs
description: >-
  Writes and reviews the JSDoc on `@Env(...)` config properties...
---
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools/permission frontmatter at all; purely a documentation guide with no declared capability.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint-scanned the file; pure ASCII documentation guide with no concealment characters or forged tokens.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Static, git-tracked documentation file with no runtime fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'n8n:env-var-docs' published under n8n's own namespace; no brand impersonation.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

This documentation-only skill defines no privileged state-changing workflow or approval gate to suppress.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

---
name: n8n:env-var-docs
description: >-
  Writes and reviews the JSDoc on `@Env(...)` config properties...
---
Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

Pure documentation style-guide; performs no I/O of any kind, reads no credentials or context.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

Discussion of environment variables in prose does not constitute a live environment assignment or executable persistence mechanism.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/experiments.agents/skills/experiments100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose is guiding frontend experiment lifecycle work (creating/editing experiment code, PostHog flag lookups via an already-registered PostHog MCP). The body is purely prose guidance referencing existing repo files and an already-registered external PostHog MCP tool -- no undeclared network egress, credential harvest, or exec capability beyond editing source files as a coding assistant normally would.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:experiments
description: >-
  Guides work on `packages/frontend/editor-ui` experiments. Use when creating,
  extending, wiring, testing, reviewing, or retiring editor-ui experiments,
  PostHog feature flags...
---

# Experiments

Use this skill for frontend experiment code lifecycle work...
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No instruction tells the agent to bypass confirmation or hide actions; the reference explicitly instructs restraint ('Do not update, enable, or delete PostHog flags unless the user explicitly asks') which reinforces rather than subverts user control.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

Do not update, enable, or delete PostHog flags unless the user explicitly asks for that side effect. If PostHog MCP or either environment is unavailable, continue the code change and tell the user which disabled flag still needs to be created manually.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

The manifest declares no explicit allowed-tools wildcard or broad scope grant at all; it is a guidance-only skill with no declarative capability grant beyond the host's default tool access, so there is no over-declared capability class to flag.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:experiments
description: >-
  Guides work on `packages/frontend/editor-ui` experiments. Use when creating,
  extending, wiring, testing, reviewing, or retiring editor-ui experiments,
  PostHog feature flags, experiment key indexes, variants, stores/composables,
  persisted experiment state, or experiment telemetry.
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Both SKILL.md and reference.md are ASCII, purely descriptive dev-process guidance with no hidden codepoints, forged role tokens, or homoglyphs.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

# Experiments reference

Use this guide for code under `packages/frontend/editor-ui/src/experiments/` and related experiment wiring.

## Orient first
...
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Both files ship inline inside the repo; no runtime fetch of instructions and no self-update mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

Start with the relevant mode in [reference.md](reference.md):

- `Create` for a new experiment folder, constant, store/composable, and tests.
...
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name n8n:experiments is descriptive, first-party-namespaced, not a near-miss of any known brand.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No instruction directs reading host credential stores, env secrets, or conversation/memory context, and no outbound channel to a third-party/attacker-controlled sink is referenced -- only an already-registered first-party PostHog MCP tool for the artifact's own declared function. No harvest step exists.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

## PostHog flag lookup

- Use PostHog MCP feature flag tools for new or changed experiment keys when they are available.
- Follow the PostHog MCP workflow strictly: run `info` for each PostHog tool before calling it...
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The skill provides in-band guidance but contains no executable callback, host-setting rewrite, endpoint mutation, or durable reinjection mechanism; no bundled scripts, lifecycle hooks, env assignments, or host-config/persistent-memory writes ship with it.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: n8n:experiments
description: >-
  Guides work on `packages/frontend/editor-ui` experiments...
---

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/human-like-code-review.agents/skills/human-like-code-review100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose (review a GitHub PR via gh CLI and write feedback to a markdown file) matches its allowed-tools frontmatter exactly: Bash(gh:*), Bash(git:*), Read, Glob, Grep -- no Write/Edit/network tool is granted beyond writing the single review file via the agent's normal file-write capability, and the skill explicitly instructs cleanup of scratch files and never to make code changes unless asked.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:human-like-code-review
description: Reviews a GitHub pull request like a thoughtful human reviewer and writes the feedback to a markdown file. ...
allowed-tools: Bash(gh:*), Bash(git:*), Read, Glob, Grep
---
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No consent-bypass directives; the skill explicitly instructs NOT to make code changes unless the user explicitly asks, which is a HITL-preserving instruction, not a subversion.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## Important

Do not make code changes unless the user explicitly asks for them. This skill produces a review, not a patch.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

The allowed-tools frontmatter is a least-privilege, enumerated set (Bash(gh:*), Bash(git:*), Read, Glob, Grep) scoped to exactly what reviewing a PR and writing a markdown file requires -- no wildcard, no Write/Edit beyond the host's normal file-write capability for the review file, no network/exec breadth beyond gh/git.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

allowed-tools: Bash(gh:*), Bash(git:*), Read, Glob, Grep
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan found zero concealment characters; plain ASCII guidance.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:human-like-code-review
description: Reviews a GitHub pull request like a thoughtful human reviewer ...
allowed-tools: Bash(gh:*), Bash(git:*), Read, Glob, Grep
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Statically bundled markdown with no runtime instruction fetch or self-mutation behavior.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:human-like-code-review
...
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name matches n8n's own internal naming convention; no brand impersonation.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

---
name: n8n:human-like-code-review
description: Reviews a GitHub pull request...
Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill reads PR diffs/comments via gh and writes a local review file -- no credential-store or conversation/memory read exists, so no harvest step exists for this rule to pair with an exfil channel.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

2. Fetch the PR diff: `gh pr diff <number> --repo <owner>/<repo>`
3. Fetch PR metadata: `gh pr view <number> --repo <owner>/<repo>`
4. Fetch existing review comments: `gh api repos/<owner>/<repo>/pulls/<number>/comments`
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The allowed-tools declaration (Bash(gh:*), Bash(git:*), Read, Glob, Grep) only authorizes normal post-decision host tools and is not itself an out-of-band execution primitive; the skill ships no executable helper, hook, entrypoint, or dotenv that would trigger format-native execution or host control-plane persistence, so this rule's scope does not apply here.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

allowed-tools: Bash(gh:*), Bash(git:*), Read, Glob, Grep

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/loom-transcript.agents/skills/loom-transcript100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

5 rules passed · 3 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose: fetch and display a Loom video transcript. Body only curls Loom's own public GraphQL API (www.loom.com/graphql) and downloads the returned transcript URLs -- exactly matching the declared purpose. No credential reads, no undeclared third-party host, no exec beyond curl to the documented Loom endpoints.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

Use the `WebFetch` tool to POST to `https://www.loom.com/graphql` to get the video title and details.

Use this curl command via Bash:

```bash
curl -s 'https://www.loom.com/graphql' \
  -H 'Content-Type: application/json' \
...
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools frontmatter declared (relies on default WebFetch/Bash already available to the host); body only ever curls the single documented loom.com host, matching its stated single-purpose function.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:loom-transcript
description: Fetch and display the full transcript from a Loom video URL. Use when the user wants to get or read a Loom transcript.
argument-hint: [loom-url]
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint-scanned the file; pure ASCII, purely descriptive instructions with no concealment characters or forged chat tokens.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:loom-transcript
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Static, git-tracked instruction file; the only runtime fetches are for Loom video DATA (the tool's declared job), not the skill's own instruction body or manifest.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:loom-transcript
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'n8n:loom-transcript' is an n8n-namespaced skill that calls Loom's own public API; it does not claim to be an official Loom product, is not a near-miss of 'loom', and does not use a trust-implying prefix.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

The skill only fetches and displays transcript data and defines no privileged mutation or confirmation mechanism, so no human-in-the-loop gate exists to subvert.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

---
name: n8n:loom-transcript
description: Fetch and display the full transcript from a Loom video URL...
---
Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill explicitly states 'No authentication or cookies are required' and reads only the Loom video ID the user provides, sending it to Loom's own public API. No host credential or conversation-context read occurs.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

## Notes

- No authentication or cookies are required — Loom's transcript API is publicly accessible.
- Only English transcripts are available through this API.
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The curl invocation is an explicit model-mediated command executed through the host's normal in-band Bash tool flow, not a bundled script, lifecycle hook, startup entrypoint, or persistent control-plane primitive.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

Use this curl command via Bash:

```bash
curl -s 'https://www.loom.com/graphql' \
...

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/nathan.agents/skills/nathan100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

8 rules passedShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose (deploy a throwaway test n8n instance via the internal Nathan bot, using a user-supplied deploy token and a short-lived Cloudflare tunnel) is stated explicitly in SKILL.md and matches the behavior described there -- no capability is exercised beyond what is documented (reading git diff to pick a deploy profile, running the bundled script, reading the saved token). allowed-tools is scoped to exactly the script invocation and git diff/Read.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

Nathan is n8n's internal bot that deploys throwaway test instances from a branch
or Docker image. It's normally driven from Slack (`/nathan ...`); this skill runs
the same commands from the repo via `pnpm nathan`.

## Prerequisites

- **No tunnel setup needed.** Nathan replies asynchronously through a short-lived
  Cloudflare Quick Tunnel that the script opens for you (`npx cloudflared`)...
- **A token in `~/.n8n/dev/nathan-token`.** If a command reports no token, **ask the
  user for one** — point them at the form...
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No prose instructs the agent to suppress confirmation or hide its actions; it explicitly tells the agent to ask the user for a token when missing and to relay the deploy URL back to the user. The skill tells the agent to use judgement about WHEN to offer (not force) a deployment, which is a discretion guard, not a consent bypass.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

- **A token in `~/.n8n/dev/nathan-token`.** If a command reports no token, **ask the
  user for one** — point them at the form...
  then save it for them (the script's interactive paste prompt needs a real
  terminal, so as an agent use the subcommand):
PassedHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

SKILL.md's own prerequisites section discloses the only credential this artifact handles: a dedicated deploy token the skill itself manages (saved via pnpm nathan set-token) for its single declared purpose (authenticating to the internal Nathan deploy bot). This is not a broad host-credential-store or env-secret harvest, not an LLM provider API key, and not conversation/memory context -- it is the artifact's own narrowly-scoped, disclosed secret used only for its documented function.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

- **A token in `~/.n8n/dev/nathan-token`.** If a command reports no token, **ask the
  user for one** — point them at the form
  (`https://internal.users.n8n.cloud/form/d6d34a2f-4899-4ee8-afc8-f8c41a8a243d`),
  where they log in with their n8n account and copy the token from the response...

  ```bash
  pnpm nathan set-token '<PASTED_TOKEN>'
  ```
PassedFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

Execution occurs through declared Bash permissions that enable explicit in-band task execution; no automatic hook, endpoint substitution, or persistent host-control mutation is created or implied.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

allowed-tools: Bash(pnpm nathan:*), Bash(node scripts/nathan.mjs:*), Bash(git diff:*), Read
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

allowed-tools is narrowly enumerated to the specific script invocation patterns needed (pnpm nathan:*, node scripts/nathan.mjs:*, git diff:*, Read) -- no wildcard Bash grant, no generic WebFetch/Write capability beyond what the declared deploy function needs.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

allowed-tools: Bash(pnpm nathan:*), Bash(node scripts/nathan.mjs:*), Bash(git diff:*), Read
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

SKILL.md contains only typographic Unicode (em dashes, ellipsis) with no bidi-override/zero-width/Tag-block codepoints, no forged chat-template tokens, and no homoglyphs; confirmed via a full codepoint scan of the file.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:nathan
description: Deploy a temporary n8n test instance (or generate a local docker run command) via the internal "Nathan" bot, from the repo instead of Slack. ...
allowed-tools: Bash(pnpm nathan:*), Bash(node scripts/nathan.mjs:*), Bash(git diff:*), Read
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

SKILL.md ships inline in the bundle and documents a fixed, repo-committed script invocation path (node scripts/nathan.mjs) rather than any runtime fetch of its own instructions from a mutable endpoint; no self-update/hot-reload mechanism is described.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

allowed-tools: Bash(pnpm nathan:*), Bash(node scripts/nathan.mjs:*), Bash(git diff:*), Read
---

# Nathan — repo-local test instances

Nathan is n8n's internal bot that deploys throwaway test instances from a branch
or Docker image. It's normally driven from Slack (`/nathan ...`); this skill runs
the same commands from the repo via `pnpm nathan`.
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name n8n:nathan matches the internal bot's actual name as documented (an internal tool name, not a typosquat of any external brand).

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/node-add-oauth.agents/skills/node-add-oauth100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

7 rules passed · 1 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose is scaffolding OAuth2 credential files for n8n nodes; the body's steps (create credential file, register in package.json, update constants/generic functions, write tests) match exactly, all operating on files inside the repo under the declared scope. No exfiltration or undeclared network/credential capability.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:node-add-oauth
description: Add OAuth2 credential support to an existing n8n node — creates the credential file, updates the node, adds tests, and keeps the CLI constant in sync. Use when the user says /node-add-oauth.
argument-hint: "[node-name] [optional: custom-scopes flag or scope list]"
---

## Overview

Add OAuth2 (Authorization Code / 3LO) support to an existing n8n node. Works for any
third-party service that supports standard OAuth2.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No consent-bypass or disclosure-suppression prose. The skill asks the user explicitly when scope decisions are ambiguous (custom scopes question) rather than silently assuming.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

- `CUSTOM_SCOPES`: whether the credential should support user-defined scopes. If the
  argument does not make this clear, **ask the user** before proceeding:
  > "Should users be able to customise the OAuth2 scopes for this credential, or should
  > scopes be fixed?"
PassedHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill defines OAuth metadata while delegating bearer injection to existing machinery and never reads or exports a live token itself.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

**Rules:**
- No `authenticate` block — `oAuth2Api` machinery handles Bearer token injection automatically.
- No `test` block — the OAuth dance validates the credential.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools frontmatter restricting/granting capability beyond the implicit host default; the documented operations (create/edit files under packages/nodes-base, run pnpm test/typecheck/lint) match its declared scaffolding purpose with no broad or wildcard capability grant.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:node-add-oauth
description: Add OAuth2 credential support to an existing n8n node — creates the credential file, updates the node, adds tests, and keeps the CLI constant in sync. Use when the user says /node-add-oauth.
argument-hint: "[node-name] [optional: custom-scopes flag or scope list]"
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Scanned for concealment codepoints and homoglyphs; none found. All code templates and prose are visible and consistent with the declared purpose.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:node-add-oauth
description: Add OAuth2 credential support to an existing n8n node — creates the credential file, updates the node, adds tests, and keeps the CLI constant in sync.
argument-hint: "[node-name] [optional: custom-scopes flag or scope list]"
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

First-party, in-repo static file; no live-fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:node-add-oauth
description: Add OAuth2 credential support to an existing n8n node.
argument-hint: "[node-name] [optional: custom-scopes flag or scope list]"
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

First-party namespaced name 'n8n:node-add-oauth' with no impersonation of any external brand.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

---
name: n8n:node-add-oauth
description: Add OAuth2 credential support to an existing n8n node.
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The component ships only SKILL.md prose and file-edit templates; no format-native execution primitive such as a lifecycle hook, install script, or load-time code is present, so the condition the rule treats as NOT_APPLICABLE is the one that applies here.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

## Step 10 — Verify

```bash
# From packages/nodes-base/
pnpm test credentials/test/{NODE_NAME}OAuth2Api.credentials.test.ts
pnpm test nodes/{NODE_NAME}/__test__/GenericFunctions.test.ts
pnpm typecheck
pnpm lint
```

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/protect-endpoints.agents/skills/protect-endpoints100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Purely declarative guidance for applying RBAC scope decorators to REST controllers; no bundled scripts, no network/credential access, no undisclosed capability -- matches its stated scope exactly.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:protect-endpoints
description: Applies n8n's RBAC scope decorators to REST endpoints. ...
---
# Protect REST endpoints with RBAC
**Rule:** every authenticated route on a `@RestController` MUST carry an access-scope decorator.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No consent-bypass prose; the skill's whole purpose is to strengthen authorization gating (RBAC decorators) rather than weaken any human-in-the-loop control.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## Public / unauthenticated routes
`{ skipAuth: true }` skips the auth middleware -> req.user is undefined -> adding @ProjectScope would 401 every call. Public routes ... must:
1. Omit the scope decorator.
2. Authenticate via signature/HMAC verification inside the handler...
3. Carry a comment explaining why no scope is applied...
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No manifest capability grant; purely descriptive guidance.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:protect-endpoints
description: Applies n8n's RBAC scope decorators to REST endpoints. ...
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan found zero concealment characters; plain ASCII guidance.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:protect-endpoints
description: Applies n8n's RBAC scope decorators to REST endpoints. ...
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Statically bundled markdown with no runtime instruction fetch or self-mutation behavior.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:protect-endpoints
...
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name matches n8n's own internal naming convention; no brand impersonation.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

---
name: n8n:protect-endpoints
description: Applies n8n's RBAC scope decorators...
Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

Purely declarative RBAC-decorator guidance; no credential or conversation-context read.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

# Protect REST endpoints with RBAC
**Rule:** every authenticated route on a `@RestController` MUST carry an access-scope decorator.
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The component ships only illustrative TypeScript examples embedded in Markdown; it contains no bundled executable, lifecycle hook, entrypoint, host configuration, or control-plane primitive, so the format-native execution and persistence scenario this rule addresses does not apply.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: n8n:protect-endpoints
description: Applies n8n's RBAC scope decorators to REST endpoints. ...
---

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/public-api.agents/skills/public-api100High trustNone flagged

Its tools can

Create or change data 1Read data 1

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declarative guidance for implementing Public API v1 controllers with explicit security-positive guidance (never echo secrets, sentinel/placeholder pattern, strict DTOs). No undisclosed network/exec/credential-harvesting capability; all guidance maps to the declared scope of packages/cli/src/public-api/v1/.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:public-api
description: >-
  Adds, migrates, or updates n8n Public API v1 endpoints with @PublicApiController
  — public DTOs, API-key and RBAC scopes, cursor pagination, OpenAPI + coverage
  wiring, and tests. Use when working under packages/cli/src/public-api/v1/ or
  when exposing an existing service through /api/v1.
---

# Public API v1

Public API v1 lives in `packages/cli/src/public-api/v1/`, mounted at `/api/v1`
with API-key auth and public error formatting via `PublicApiControllerRegistry`
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No consent-bypass or disclosure-suppression directives. Guidance explicitly promotes transparency (never echo secrets, error messages must not leak internals).

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

(full file reviewed -- no consent/confirmation/disclosure-suppression language present)
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools/scope frontmatter; no bundled executables or install hooks.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:public-api
description: >-
  Adds, migrates, or updates n8n Public API v1 endpoints...
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan found only normal box-drawing/arrow/em-dash punctuation; no concealment codepoints or forged role tokens in SKILL.md or reference.md.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

(scanned full SKILL.md + reference.md: only box-drawing/arrow/em-dash punctuation; no concealment codepoints)
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Instructions ship inside the bundled, version-controlled SKILL.md/reference.md; no runtime fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

(static bundled text, no fetch/self-update mechanism present)
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Declared name n8n:public-api is self-descriptive, namespaced under the repo's own org prefix, with no resemblance to a known third-party brand.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No instruction reads host credentials, env vars, or conversation context for exfiltration; explicitly the opposite -- it mandates secrets are NEVER echoed/returned in API responses.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

- Secrets: never return a real secret; use the resource's sentinel/placeholder
  (or omit). See [Updates and write-only secrets](reference.md#updates-and-write-only-secrets).
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

This documentation-only skill contains no bundled hook, entry script, host configuration, or executable environment override.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

(documentation-only component, no bundled execution primitives)
Show its 1 tool
ToolIt can
public-api-guidance
Guidance for adding Public API v1 controllers/DTOs/auth scopes
Read dataCreate or change data

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/reproduce-bug.agents/skills/reproduce-bug100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose: reproduce a bug from a Linear ticket with a failing regression test. Body only describes reading source/test files and running the repo's own test suite (pnpm test) -- entirely within the declared scope, no network egress or credential access.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

## Step 8: Run and Score

Run the test from the package directory (e.g., `cd packages/nodes-base && pnpm test <file>`).
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No prose instructs skipping confirmation or hiding actions; the skill explicitly restricts itself ('DO NOT fix the bug', 'Leave test files in place ... (don't commit unless asked)') which is the opposite of consent suppression -- it constrains the agent to less autonomy, not more.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## Important

- **DO NOT fix the bug** — only reproduce it with a failing test
- **Leave test files in place** as evidence (don't commit unless asked)
...
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools frontmatter restricting/declaring scope beyond name+description; body only references running the repo's existing test suite, matching its declared bug-reproduction function; no bundled executables or install hooks.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:reproduce-bug
description: Reproduce a bug from a Linear ticket with a failing test. Expects the full ticket context (title, description, comments) to be provided as input.
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint-scanned the file; pure ASCII, purely descriptive instructions with no concealment characters or forged chat tokens.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Static, git-tracked instruction file with no runtime fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'n8n:reproduce-bug' published under n8n's own namespace; no brand impersonation.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No instruction reads host credential stores or conversation/memory context; the hard-bailout rule explicitly avoids requiring real third-party API credentials ('Requires real third-party API credentials' is listed as a reason to STOP, not proceed).

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

**Hard bailout triggers** (stop immediately):
- Requires real third-party API credentials
- Race condition / timing-dependent
...
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The pnpm test instruction is an ordinary in-band command and not a shipped lifecycle hook, executable entrypoint, or persistent control-plane primitive.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

## Step 8: Run and Score

Run the test from the package directory (e.g., `cd packages/nodes-base && pnpm test <file>`).

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/spec-driven-development.agents/skills/spec-driven-development100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose is keeping specs in .agents/specs/ synced with implementation; the body is purely a prose workflow for reading/writing spec markdown files, with no network egress, credential reads, or exec capability of any kind.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:spec-driven-development
description: Keeps implementation and specs in sync. Use when working on a feature that has a spec in .agents/specs/...
---

# Spec-Driven Development

Specs live in `.agents/specs/`. They are the source of truth...
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No prose instructs bypassing confirmation or hiding actions from the user; the skill explicitly tells the agent to 'ask the user' before creating a new spec when none exists and to 'flag unresolved gaps to the user'.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

3. **If no spec exists** and the task is non-trivial (new module, new API,
   architectural change), ask the user whether to create one first.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools frontmatter at all, no wildcard capability declaration, no bundled executables, no install hooks -- a pure documentation/workflow skill.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:spec-driven-development
description: Keeps implementation and specs in sync. Use when working on a feature that has a spec in .agents/specs/, when the user says /spec, or when starting implementation of a documented feature. Also use when the user asks to verify implementation against a spec or update a spec after changes.
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

ASCII, purely descriptive prose with no hidden codepoints, forged chat-template tokens, or homoglyphs.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:spec-driven-development
description: Keeps implementation and specs in sync...
---

# Spec-Driven Development

Specs live in `.agents/specs/`...
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

SKILL.md ships inline in the repo bundle; no runtime fetch of instructions and no self-update mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:spec-driven-development
description: Keeps implementation and specs in sync...
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name n8n:spec-driven-development is descriptive, first-party-namespaced, not a near-miss of any known brand.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No credential store or conversation/memory context read is directed anywhere in this skill, and no outbound channel exists; it only reads/writes markdown files under .agents/specs/.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

Specs live in `.agents/specs/`. They are the source of truth for architectural
decisions, API contracts, and implementation scope. Implementation and specs
must stay in sync — neither leads exclusively.
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

This one-file prose workflow contains no load-time executable, lifecycle callback, endpoint override, or durable host-setting modification.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: n8n:spec-driven-development
description: Keeps implementation and specs in sync...
---

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/telemetry.agents/skills/telemetry100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose is guiding internal telemetry event registry usage (adding/reviewing track() calls against RudderStack/PostHog). Body matches: registry location, naming rules, testing guidance. No instruction to read unrelated secrets or exfiltrate data outside the product's own documented analytics pipeline.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:telemetry
description: >-
  Guides adding, changing, and reviewing telemetry through the `@n8n/telemetry`
  event registry. Use when working on telemetry, analytics, tracking, product
  events, `track()` calls, or RudderStack/PostHog product events, in frontend
  or backend code — and whenever you need to find which registered telemetry
  events exist, what an event means, or what properties it carries.
---

# Telemetry

## The registry

…
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No consent-suppression language; the skill requires a data-team Slack/Notion heads-up for breaking changes, which strengthens human oversight rather than weakening it.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

- **Breaking changes need a data-team heads-up** in Slack plus a Notion note before they ship.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools capability grant at all; purely a reference doc for the existing telemetry registry.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:telemetry
description: >-
  Guides adding, changing, and reviewing telemetry through the `@n8n/telemetry`
  event registry.
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Scanned for concealment codepoints and homoglyphs; none found.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:telemetry
description: >-
  Guides adding, changing, and reviewing telemetry through the `@n8n/telemetry`
  event registry.
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

First-party, in-repo static file; no live-fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:telemetry
description: >-
  Guides adding, changing, and reviewing telemetry through the `@n8n/telemetry`
  event registry.
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

First-party namespaced name 'n8n:telemetry' with no impersonation of any external brand.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

---
name: n8n:telemetry
description: >-
  Guides adding, changing, and reviewing telemetry through the `@n8n/telemetry`
Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No host credential or conversation/memory read occurs anywhere in the skill; the telemetry events are opt-in product analytics routed to the product's own first-party RudderStack/PostHog pipeline. Because the rule's harvest half is absent, its scope mandates NOT_APPLICABLE rather than SECURE.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

## The registry

Events migrated to the registry live in `packages/@n8n/telemetry` as one entry per event — its exact emitted name, a description, and a zod schema typing its properties — organized per product domain in `src/events/` and composed into `TELEMETRY_EVENT.<DOMAIN>.<EVENT>`. The package defines registered events and never depends on transport SDKs.
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The pnpm catalog commands are ordinary model-invoked examples that query the local package; the component ships no script, hook, entry point, host config, or endpoint/env substitution that would execute automatically.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

**To find which events are registered, what they mean, or what properties they carry, run the catalog first:**

```bash
pnpm --filter @n8n/telemetry catalog          # human-readable, grouped by domain
pnpm --filter @n8n/telemetry catalog --json   # structured, for programmatic use
```

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.agents/skills/ui-design.agents/skills/ui-design100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Purely declarative UI/design-system styling guidance; no bundled scripts, no exec/network/credential capability, text-only reference to design tokens and components matching its stated scope.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:ui-design
description: Guidelines on designing and building UI. Use when working in editor-ui or design-system packages. ...
---
# UI Design
Comprehensive guide for building, styling, and using components in the frontend.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

Purely stylistic/UI guidance; no consent, disclosure, or authority-related prose of any kind.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## Best practices
- ALWAYS use CSS variables from `_tokens.scss` or `_primitives.scss` ...
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No manifest capability grant; purely descriptive guidance.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:ui-design
description: Guidelines on designing and building UI. ...
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan found zero concealment characters; plain ASCII guidance.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:ui-design
description: Guidelines on designing and building UI. ...
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Statically bundled markdown with no runtime instruction fetch or self-mutation behavior.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n:ui-design
...
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name matches n8n's own internal naming convention; no brand impersonation.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

---
name: n8n:ui-design
description: Guidelines on designing...
Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

Purely declarative UI/styling guidance; no credential or conversation-context read of any kind.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

---
name: n8n:ui-design
description: Guidelines on designing and building UI. ...
---
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The skill bundle consists of three Markdown guidance files with no format-native execution primitive, lifecycle hook, entrypoint, host-config write, dotenv, or endpoint-substitution capability present.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: n8n:ui-design
description: Guidelines on designing and building UI. ...
---

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.claude/plugins/n8n/skills/setup-mcps.claude/plugins/n8n/skills/setup-mcps100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose: configure MCP servers for n8n development. Body runs claude mcp add for well-known, named MCP servers (Linear, Notion, Context7, Figma) over their documented HTTPS URLs -- entirely consistent with the declared purpose, no hidden capability.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

### Linear
```bash
claude mcp add -s {scope} linear-server --transport http https://mcp.linear.app/mcp
```
After adding, tell the user to run `/mcp` in their next session to authenticate.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

Every installation step is explicitly gated by AskUserQuestion prompts (which MCP to install, which scope); the skill even says 'Do NOT batch the scope question -- ask separately for each MCP', the opposite of consent suppression.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

3. Process each selected MCP **one at a time** in a loop. For each MCP:
   a. Ask the user via `AskUserQuestion`: "Where should **{MCP name}** be installed?"
...
Do NOT batch the scope question — ask separately for each MCP.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No wildcard allowed-tools declared; the skill only documents running claude mcp add for 4 explicitly named, well-known MCP servers, matching its stated single purpose of MCP setup.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:setup-mcps
description: >-
  Configure MCP servers for n8n development. Use when the user says /setup-mcps
  or asks to set up MCP servers for n8n.
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint-scanned the file; pure ASCII, purely descriptive instructions with no concealment characters or forged chat tokens.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Static, git-tracked instruction file; no runtime fetch or self-mutation of its own manifest/instructions.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'n8n:setup-mcps' published under n8n's own namespace/plugin from the n8n-io/n8n repo; references well-known services (Linear, Notion, Figma) by their correct canonical spelling and connects to their genuine documented domains, not look-alikes.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No reading of host credentials or conversation context; OAuth for each MCP is handled entirely by the claude mcp CLI's own documented browser-based auth flow, not by this skill reading/forwarding secrets.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

```bash
claude mcp add -s {scope} linear-server --transport http https://mcp.linear.app/mcp
```
After adding, tell the user to run `/mcp` in their next session to authenticate.
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

This subcomponent contains only SKILL.md guidance and ships no executable, lifecycle hook, environment override, or automatic host-control primitive.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: n8n:setup-mcps
description: >-
  Configure MCP servers for n8n development. Use when the user says /setup-mcps
  or asks to set up MCP servers for n8n.
---

# MCP Setup for n8n Development

Configure commonly used MCP servers for n8n engineers.

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/.opencode/skills/setup-mcps.opencode/skills/setup-mcps100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

7 rules passed · 1 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose is configuring known MCP servers (Linear, Notion, Context7, Figma) for OpenCode; the body only writes declared, user-approved MCP entries to a declared OpenCode config path using fixed, well-known URLs, with no undisclosed capability.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n:setup-mcps
description: >-
  Configure MCP servers for n8n development in OpenCode...
---
...
Known MCP URLs:

- Linear: `https://mcp.linear.app/mcp`
- Notion: `https://mcp.notion.com/mcp`
- Context7: `https://mcp.context7.com/mcp`
- Figma: `https://mcp.figma.com/mcp`
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

The skill explicitly instructs asking the user which MCPs to add, asking once about scope, and telling the user to separately run OAuth authentication -- it reinforces, not bypasses, user consent at each step.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

2. Ask the user which unconfigured MCPs they want to add. If all are already
   configured, tell the user and stop.
PassedFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The skill DOES write to a host control-plane/config file (~/.config/opencode/opencode.json or ./opencode.json), which is a sensitive surface this rule governs -- but the write is the artifact's entire declared purpose ('Configure MCP servers for n8n development in OpenCode'), is disclosed in full to the user in the instructions, writes only the four named, fixed, well-known MCP server URLs (no attacker-controlled or undisclosed entries), explicitly preserves existing config keys/entries, and is gated on the user picking which servers to add. This is declared, disclosed, in-scope configuration -- not covert self-reinjecting persistence.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

4. Add the selected MCP entries under the `mcp` object in the selected config.
   Preserve existing config keys and existing MCP entries.

```json
{
  "mcp": {
    "linear": {
      "type": "remote",
      "url": "https://mcp.linear.app/mcp",
      "enabled": true
    },
...
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No allowed-tools wildcard or broad capability grant is declared; the skill is scoped to editing one JSON config file with a fixed, enumerated server list.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: n8n:setup-mcps
description: >-
  Configure MCP servers for n8n development in OpenCode. Use when the user says
  /setup-mcps or asks to set up MCP servers for n8n.
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

ASCII, purely descriptive instructions with no hidden codepoints, forged tokens, or homoglyphs.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n:setup-mcps
description: >-
  Configure MCP servers for n8n development in OpenCode...
---

# MCP Setup for n8n Development in OpenCode

Configure commonly used MCP servers for n8n engineers using OpenCode MCP config.
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

SKILL.md ships inline; the MCP server URLs it writes are hardcoded in the instruction text itself, not live-fetched from a mutable endpoint, and there is no self-update mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

Known MCP URLs:

- Linear: `https://mcp.linear.app/mcp`
- Notion: `https://mcp.notion.com/mcp`
- Context7: `https://mcp.context7.com/mcp`
- Figma: `https://mcp.figma.com/mcp`
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name n8n:setup-mcps is descriptive and first-party-namespaced; the MCP URLs it configures point to the genuine Linear/Notion/Context7/Figma hosted MCP endpoints, not look-alike domains.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No host-credential-store read or conversation/memory-context read occurs; OAuth is deferred to the user running opencode mcp auth out-of-band, and the skill never touches tokens directly.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

5. After editing config, tell the user to authenticate OAuth-backed servers with:

```bash
opencode mcp auth <server-name>
```

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/packages/@n8n/cli/skills/n8n-clipackages/@n8n/cli/skills/n8n-cli100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

7 rules passed · 1 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose ('manage workflows, credentials, executions... from the command line') matches the broad-but-declared CRUD surface documented in the body (workflows, executions, credentials, projects, tags, variables, data tables, users, promotions, audit, source-control). The allowed-tools list (Bash(n8n-cli:*), echo, cat, Read, Write) matches this scope. The skill explicitly instructs passing secrets via --stdin/--file rather than CLI flags to keep credentials off the command line -- a protective directive, not a shadow feature.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: n8n-cli
description: Use the n8n CLI to manage workflows, credentials, executions, and more on an n8n instance. Use when the user asks to interact with n8n, automate workflows, manage credentials, or operate their instance from the command line.
allowed-tools: Bash(n8n-cli:*), Bash(echo:*), Bash(cat:*), Read, Write
---

# n8n CLI

The `n8n-cli` command-line tool manages an n8n instance via its REST API.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No prose instructs skipping confirmation or hiding privileged actions from the user. Destructive operations (workflow delete, credential delete, apply --confirm-destructive-changes) are documented with explicit confirmation flags rather than auto-approved, and the skill instructs passing secrets via --stdin/--file (not inline) for safety.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

# To also apply destructive data table changes and delete the team projects that the
# package does not hold, add --confirm-destructive-changes.
PassedHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill operates the user's own n8n-cli against their own instance using credentials the user explicitly configured for that purpose (N8N_API_KEY) -- this is the declared purpose, not harvesting of unrelated host secrets. The skill explicitly instructs keeping credentials off the command line (use --stdin/--file) to reduce exposure, and 'credential get' explicitly returns metadata only, not secrets.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

Key points:
- Pass every JSON body through `--stdin` or `--file`, never through a flag. This
  keeps credentials off the command line.
Show 1 more location
# Get credential metadata (not secrets)
n8n-cli credential get <id>
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

allowed-tools is scoped narrowly to the n8n-cli binary plus echo/cat/Read/Write -- an enumerated least-privilege list matching the documented CLI-operation purpose; no wildcard Bash(*) or unrelated network/exec tool is granted.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

allowed-tools: Bash(n8n-cli:*), Bash(echo:*), Bash(cat:*), Read, Write
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Scanned for concealment codepoints and homoglyphs; none found (pure ASCII).

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n-cli
description: Use the n8n CLI to manage workflows, credentials, executions, and more on an n8n instance.
allowed-tools: Bash(n8n-cli:*), Bash(echo:*), Bash(cat:*), Read, Write
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

First-party, in-repo static file under version control; no runtime fetch of instructions and no self-mutation of the manifest.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: n8n-cli
description: Use the n8n CLI to manage workflows, credentials, executions, and more on an n8n instance.
allowed-tools: Bash(n8n-cli:*), Bash(echo:*), Bash(cat:*), Read, Write
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'n8n-cli' accurately names the first-party CLI tool it documents, shipped by n8n itself inside the n8n monorepo; no typosquat or impersonation of a different brand.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

---
name: n8n-cli
description: Use the n8n CLI to manage workflows, credentials, executions, and more on an n8n instance.
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The skill only permits post-decision invocation of the external n8n-cli binary via its allowed-tools grant; it does not bundle an executable helper, load-time hooks, entry scripts, host configuration, or any format-native control-plane primitive, so there is no out-of-band execution path within this rule's scope.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: n8n-cli
description: Use the n8n CLI to manage workflows, credentials, executions, and more on an n8n instance. Use when the user asks to interact with n8n, automate workflows, manage credentials, or operate their instance from the command line.
allowed-tools: Bash(n8n-cli:*), Bash(echo:*), Bash(cat:*), Read, Write
---

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/packages/@n8n/instance-ai/skills/agent-builderpackages/@n8n/instance-ai/skills/agent-builder100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

7 rules passed · 1 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose (route Agent-build requests to the build-agent tool, faithfully forward user intent) matches instructed behavior: the skill explicitly prohibits inventing implementation choices on the user's behalf ('Never infer, invent, expand, recommend, or prescribe implementation details the user did not request') and routes credential/model/tool selection to the embedded builder's own interactive flow rather than performing hidden actions itself.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

Never infer, invent, expand, recommend, or prescribe implementation details the
user did not request, and never present your assumptions as user requirements.
In particular, do not choose or tell the builder which model, instructions,
tools, tool types, integrations, channels, MCP servers, workflows, skills,
tasks, memory, credentials, triggers, schedules, approvals, or test strategy to
use.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No prose instructs the agent to hide actions, skip confirmation, or assert authority over the host system prompt; the skill explicitly routes user-choice/credential/approval questions through the builder's own interactive surfacing mechanism rather than bypassing it ('When the builder needs a user choice, credential, chat channel, or approval, it surfaces an interactive card in this chat').

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## Builder-owned interactions
When the user asks to test, run, publish, activate, make usable, unpublish, or
otherwise change the Agent, forward that intent in `message`. ...
When the builder needs a user choice, credential, chat channel, or approval, it
surfaces an interactive card in this chat. Do not relay the question yourself;
the `build-agent` call resumes with the user's answer.
PassedFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The component is a Markdown skill that only recommends named host tools, invoked through the normal model tool-call flow. It contains no bundled lifecycle hook, environment-variable mutation, LLM-endpoint substitution, or persistence primitive.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: agent-builder
description: >-
  Load immediately after an Agent intent. ...
recommended_tools:
  - build-agent
  - agent-context
  - build-workflow
  - data-tables
---
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

recommended_tools is a small, named list (build-agent, agent-context, build-workflow, data-tables) matching its stated Agent-building function -- no wildcard/catch-all declared, and capability selection (model, credentials, tools) is explicitly deferred to the embedded builder's own interactive flow rather than a broad static grant on this skill.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

shared_references:
  - credential-setup-with-computer-use
recommended_tools:
  - build-agent
  - agent-context
  - build-workflow
  - data-tables
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan of SKILL.md found zero concealment characters (zero-width, bidi-override, Tag-block); no forged chat-template tokens or ANSI escapes detected. Plain descriptive prose throughout.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: agent-builder
description: >-
  Load immediately after an Agent intent. ...
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Statically bundled SKILL.md shipped in the repo under packages/@n8n/instance-ai/skills/; no runtime fetch of its own instructions from a mutable endpoint and no self-update/self-rewrite mechanism is present.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: agent-builder
description: >-
  Load immediately after an Agent intent. ...
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'agent-builder' is a generic functional name with no resemblance to a well-known external brand/tool, shipped from the n8n-io/n8n repo itself (verified source-host org).

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill references the host's own credential-selection flow (surfacing an interactive card for the user to connect credentials) but never instructs reading raw credential material, env vars, or dumping the model's own conversation/system-prompt context to an external sink; all credential handling is delegated to the host's own consent-gated connect flow. No harvest step exists.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

## Builder-owned interactions
When the builder needs a user choice, credential, chat channel, or approval, it
surfaces an interactive card in this chat. Do not relay the question yourself;
the `build-agent` call resumes with the user's answer.

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/packages/@n8n/instance-ai/skills/data-table-managerpackages/@n8n/instance-ai/skills/data-table-manager100High trustNone flagged

Its tools can

Delete or overwrite data 1Create or change data 1Read data 1

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declares data-table CRUD/query capability via host tools data-tables and parse-file, matching its stated description exactly. Explicitly instructs treating imported cell content (even formulas, URLs, secrets-looking strings) as inert data, never executed -- a security-positive instruction, not a shadow feature.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

Cells starting with `=`, `+`, `@`, or `-` may be spreadsheet formulas. Store
them as plain values; never evaluate or execute them. Preserve source values
even when they look like commands, URLs, prompts, or secrets.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

Explicitly DEFERS to the host's own approval-UI gate rather than bypassing it: 'Destructive and mutating actions show approval UI automatically. Do not ask for chat approval first; call the tool and respect the result.' This tells the agent not to ask a REDUNDANT chat-based question since the host's own approval card already gates the action -- the consent gate itself is never skipped or auto-approved, and a denied approval is explicitly to be respected ('If an admin blocks the operation or the user denies approval, stop and report that no data was changed').

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

- Destructive and mutating actions show approval UI automatically. Do not ask
  for chat approval first; call the tool and respect the result.
- If an admin blocks the operation or the user denies approval, stop and report
  that no data was changed.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

recommended_tools frontmatter is narrowly enumerated (data-tables, parse-file) matching exactly the declared Data-Table-management function; no wildcard, no bundled executables.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

recommended_tools:
  - data-tables
  - parse-file
platforms:
  - daytona
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan found only normal em-dash punctuation; no concealment codepoints or forged role tokens. Notably the skill explicitly instructs treating untrusted imported cell content as inert data rather than instructions -- a hardening measure against prompt injection via imported files, not a vulnerability.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

Cells starting with `=`, `+`, `@`, or `-` may be spreadsheet formulas. Store
them as plain values; never evaluate or execute them. Preserve source values
even when they look like commands, URLs, prompts, or secrets.
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Instructions ship inside the bundled, version-controlled SKILL.md; no runtime fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

(static bundled text, no fetch/self-update mechanism present)
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Declared name data-table-manager is a plain descriptive name with no resemblance to any well-known third-party brand/tool, no trust-implying prefix, no version-spoofing suffix.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No reads of host credential stores, env vars, or conversation/memory context; the skill operates only on user-supplied Data Table rows/files via declared host tools.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The Markdown skill only directs use of registered tools in band and ships no script, lifecycle hook, host configuration, or environment override.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

(no bundled scripts, hooks, or env assignments; only directs use of already-registered host tools)
Show its 1 tool
ToolIt can
data-table-manager-guidance
Directs use of host data-tables and parse-file tools to list/create/import/query/mutate/delete n8n Data Tables
Read dataCreate or change dataDelete or overwrite data

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/packages/@n8n/instance-ai/skills/debugging-executionspackages/@n8n/instance-ai/skills/debugging-executions100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose: debug failed/incorrect workflow executions using the host's own executions/workflows tools. All guidance stays within invoking already-registered host tools for inspecting execution data -- no undeclared credential reads or external egress.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: debugging-executions
description: >-
  Debug failed or wrong-output workflow executions using executions tools...
recommended_tools:
  - executions
  - workflows
---
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

The skill explicitly requires user approval before any write-type action ('Do not run for real... let the user choose', 'The approval prompt alone is not consent: the user sees a node name, not this posts to your #general channel' -- i.e. it actively WARNS the host against treating a generic approval as sufficient, strengthening rather than weakening the consent gate). Read-only inspections are explicitly called out as safe to run unprompted, which is a scoped, declared exception (idempotent/read-only ops), not a suppression of oversight on privileged actions.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

If you genuinely cannot resolve it without a real run, say plainly what the
node will do to the user's data, and let the user choose. The approval prompt
alone is not consent: the user sees a node name, not "this posts to your
#general channel".
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

recommended_tools is a narrow, enumerated list (executions, workflows) consistent with the declared debugging purpose; no wildcard/catch-all capability class is requested.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint-scanned the file; pure ASCII, purely descriptive instructions with no concealment characters or forged chat tokens.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Static, git-tracked instruction file with no runtime fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'debugging-executions' is a generic, descriptive name with no resemblance to any third-party brand; part of n8n's own instance-ai package.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill explicitly states the agent 'cannot read a stored secret' and never instructs reading raw credential material; it only inspects execution metadata and resolved (non-secret) parameters via host tools.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

Then report the checks and their results, and only after that give a cause.
You cannot read a stored secret, so a cause about its format or value is a
hypothesis: label it as one, and say which check would confirm it.
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The skill directory contains only static instructions that invoke already-registered host tools; it ships no lifecycle executable, endpoint hijack, or persistent control-plane write mechanism.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/packages/@n8n/instance-ai/skills/instance-awarenesspackages/@n8n/instance-ai/skills/instance-awareness100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

7 rules passed · 1 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose is guiding the agent to check existing instance state (activity log, workflows, executions, credentials) before acting, using the declared recommended_tools (activity, workflows, executions, credentials). The body only instructs reading already-registered, in-scope instance-state tools; no undeclared network egress, credential harvest, or exec capability is introduced.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: instance-awareness
description: >-
  Load when the request depends on what is already on this instance...
recommended_tools:
  - activity
  - workflows
  - executions
  - credentials
---
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No prose suppresses confirmation or hides actions from the user; the instructions are about reducing redundant QUESTIONS by reading already-available instance state, not about skipping any approval/consent gate on state-changing actions. The 'ask about intent, look up state' rule explicitly preserves asking the user about intent.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## The one rule

**Ask about intent. Look up state.**

What the user wants next is theirs to tell you. What already exists here is
yours to find out.
PassedHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill reads injected instance-context and host activity/workflow/execution state (including credential existence listings) to personalize its behavior within the session. This state-reading capability is present, not absent; however, no instruction directs that data outbound to any external sink, so it remains in-conversation. Given the read capability, the rule's subject matter is applicable, and because no exfiltration channel exists, the correct outcome is secure handling of that data in-session.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

| Do they have a credential for X? | `credentials(action="list")` |
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

recommended_tools is a narrow, enumerated, read-only list (activity, workflows, executions, credentials) matching the stated state-lookup purpose; no wildcard or write/exec capability is declared.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

recommended_tools:
  - activity
  - workflows
  - executions
  - credentials
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

ASCII, purely descriptive prose with no hidden codepoints, forged chat-template tokens, or homoglyphs.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

# Instance Awareness

The conversation is not the only context you have. This instance carries a
record of what has been built, changed, run and broken. Most of it is cheap to
read, and reading it is usually better than asking.
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

SKILL.md ships inline in the repo bundle; no runtime fetch of instructions and no self-update mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: instance-awareness
description: >-
  Load when the request depends on what is already on this instance...
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name instance-awareness is descriptive, not a near-miss of any known brand/tool.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

This static read-oriented skill has no executable callback, host-setting write, environment mutation, endpoint replacement, or persistence mechanism; it consists solely of prose guidance directing calls to already-registered read tools.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: instance-awareness
description: >-
  Load when the request depends on what is already on this instance...
---

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/packages/@n8n/instance-ai/skills/intent-recognitionpackages/@n8n/instance-ai/skills/intent-recognition100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

8 rules passedShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Pure classification-logic documentation for routing automation requests (workflow vs agent anchor). No tool invocation, no network/credential capability at all -- it is a decision-procedure reference consumed by the orchestrating agent, matching its declared description exactly.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: intent-recognition
description: >-
  Classifies automation requests using two decisions: anchor (which primitive
  owns the top-level control flow — workflow-anchored, agent-anchored,
  needs-clarification, or out-of-scope) and embeds_other (whether the other
  primitive appears embedded inside — an agent step inside a workflow, or a
  workflow invoked as an agent tool).
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

This is classification logic with explicit instructions to ASK the user ('ask-user') whenever an anchor-deciding detail is missing, rather than silently guessing or proceeding without confirmation. No consent-suppression prose found.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

5. Before selecting a workflow, verify that the request supplies or clearly
   entails every workflow condition. Do not invent a fixed sequence, decision
   policy, or bounded LLM task from a generic outcome. If a missing
   anchor-defining detail could make the work judgment-driven, classify
   **needs-clarification** and ask only for that detail.
PassedHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

Pure classification-decision logic with no credential or conversation-context harvesting/exfiltration instructions at all.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

## Decisions

Two orthogonal decisions per request, or per part for compound requests:

**1. Anchor** — which primitive owns the top-level control flow:
PassedFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The file defines an in-band decision and routing procedure but contains no lifecycle hook, executable carrier, or durable control-plane modification.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: intent-recognition
description: >-
  Classifies automation requests using two decisions: anchor (which primitive
  owns the top-level control flow — workflow-anchored, agent-anchored,
  needs-clarification, or out-of-scope) and embeds_other (whether the other
  primitive appears embedded inside — an agent step inside a workflow, or a
  workflow invoked as an agent tool).
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

No capability/tool grant of any kind -- this is pure decision-routing text consumed by an orchestrator, with zero allowed-tools declaration.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

---
name: intent-recognition
description: >-
  Classifies automation requests using two decisions: anchor ...
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Scanned for concealment codepoints and homoglyphs; none found. The lengthy, imperative decision-procedure prose is openly visible routing logic for the host's own orchestration, not a concealed directive.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: intent-recognition
description: >-
  Classifies automation requests using two decisions: anchor ...
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

First-party, in-repo static file; no live-fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: intent-recognition
description: >-
  Classifies automation requests using two decisions: anchor ...
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Generic descriptive name 'intent-recognition' with no impersonation of any brand or third-party tool.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

---
name: intent-recognition
description: >-
  Classifies automation requests using two decisions: anchor ...

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/packages/@n8n/instance-ai/skills/n8n-docs-assistantpackages/@n8n/instance-ai/skills/n8n-docs-assistant100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

7 rules passed · 1 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose (answer n8n docs/credential-setup questions via the n8n-docs tool) matches instructed behavior exactly, and the skill explicitly protects against credential exfiltration by instructing never to ask the user to paste secrets into chat.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

- If the answer involves secret values, tell the user where to paste them in the
  credential modal, but never ask them to paste secrets into chat.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

No consent-bypass or disclosure-suppression prose; purely a docs-lookup-and-answer skill.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## Missing Docs
If docs lookup fails, say that current docs could not be loaded. Provide only
generic n8n UI guidance that is visible from the product context, and avoid
claiming provider-specific setup steps without docs evidence.
PassedFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

This prose-only skill recommends registered tools and includes no load hook, executable payload, environment override, endpoint substitution, or persistence mechanism.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

recommended_tools:
  - n8n-docs
  - credentials
  - nodes
---
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

recommended_tools is a small named list (n8n-docs, credentials, nodes) matching its stated docs-answering function exactly -- no wildcard/broad grant.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

recommended_tools:
  - n8n-docs
  - credentials
  - nodes
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan found zero concealment characters; plain ASCII guidance.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: n8n-docs-assistant
description: >-
  Answers n8n product, setup, credential, node, hosting, API, and usage
  questions from current n8n docs. ...
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Statically bundled SKILL.md; no runtime instruction fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'n8n-docs-assistant' correctly identifies itself as n8n's own docs assistant, shipped from the n8n-io/n8n repo itself -- no impersonation (the source-host org IS n8n).

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill explicitly forbids asking the user to paste secrets into chat and never itself reads credential stores or conversation memory for exfiltration -- no harvest step exists.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

- If the answer involves secret values, tell the user where to paste them in the
  credential modal, but never ask them to paste secrets into chat.

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/packages/@n8n/instance-ai/skills/planned-task-runtimepackages/@n8n/instance-ai/skills/planned-task-runtime100High trustNone flagged

Its tools can

Run code or commands 1Create or change data 1

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declares orchestration of follow-up turns using recommended_tools (create-tasks, complete-checkpoint, build-workflow, task-control, workflows, verify-built-workflow, executions). All invoked tools match the declared list; no undisclosed network/credential/exec capability beyond invoking already-registered host tools for the declared purpose.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: planned-task-runtime
description: >-
  Handles system follow-up turns: planned-task-follow-up (synthesize, replan,
  build-workflow, checkpoint), background-task-completed, running-tasks context,
  and create-tasks silence rules. Load whenever any of these tags appear or
  after calling create-tasks.
recommended_tools:
  - create-tasks
  - complete-checkpoint
  - build-workflow
  - task-control
  - workflows
  - verify-built-workflow
…
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

The 'do not write any text'/'silence' instructions are scoped to redundant chat narration after a task card/approval card already shows progress to the user -- brevity/UX control, not suppression of a privileged action's disclosure. The approval mechanism itself (create-tasks) is never bypassed; the skill explicitly says tasks run 'after approval'.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

**After calling `create-tasks`**: do not write any text. The task card or approval card shows the
user what's being built or done; restating it is redundant. Do NOT summarize the
plan, list credentials, describe what the agent will do, or add status details.
Progress is already visible to the user in real time.

When `create-tasks` returns after approval, tasks are already running. Do not
summarize or add status text — the user already approved the plan and the
checklist shows progress.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

recommended_tools frontmatter is an explicit enumerated list (create-tasks, complete-checkpoint, build-workflow, task-control, workflows, verify-built-workflow, executions), each matching the declared follow-up-turn orchestration function; no wildcard/catch-all declaration.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

recommended_tools:
  - create-tasks
  - complete-checkpoint
  - build-workflow
  - task-control
  - workflows
  - verify-built-workflow
  - executions
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan found only normal arrow/em-dash punctuation; no concealment codepoints, forged role tokens, or hidden HTML/CSS content.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

(scanned full file: only →,— present as prose punctuation; no concealment codepoints)
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Instructions ship inside the bundled, version-controlled SKILL.md; no runtime fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

(static bundled text, no fetch/self-update mechanism present)
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Declared name planned-task-runtime is a plain descriptive name with no resemblance to any well-known third-party brand/tool.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No instruction reads host credential stores or raw conversation/memory context for an outbound channel; credential mentions are about workflow credential setup flows via declared host tools, not harvesting.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

(no env/credential/memory harvest+exfil pair present)
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

Its orchestration consists of normal model tool calls rather than lifecycle hooks, format-native execution, endpoint substitution, or persistent host control.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

(no bundled scripts, hooks, or env assignments; only directs use of already-registered host tools)
Show its 1 tool
ToolIt can
planned-task-runtime-guidance
Coordinates follow-up turns after create-tasks: synthesize, replan, build-workflow, checkpoint handling using create-tasks/complete-checkpoint/build-workflow/task-control/workflows/executions host tools
Create or change dataRun code or commands

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/packages/@n8n/instance-ai/skills/planningpackages/@n8n/instance-ai/skills/planning100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose: coordinate multi-artifact planning via create-tasks. All guidance stays within invoking already-registered host tools (create-tasks, nodes, credentials, data-tables) for the stated planning function -- no undeclared capability.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: planning
dependencies:
  tools:
    - create-tasks
description: >-
  ONLY for coordinated multi-artifact work...
recommended_tools:
  - create-tasks
  - workflows
  - nodes
  - credentials
  - data-tables
  - parse-file
…
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

The 'Never ask about X' directives are scoped strictly to discoverable implementation details (credentials, node choices, timezone already known) rather than suppressing confirmation of a privileged or state-changing action; the skill still requires ask-user when a choice 'materially changes the plan and cannot be discovered' or when multiple matching credentials exist. This matches the rule's own false-positive guard for benign brevity/assumption-scoping directives.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

- Never ask about things tools can discover, such as available credentials,
  existing data tables, workflow names, node availability, or attached-file
  structure.
- Never ask for implementation details such as node choices, column names, or
  trigger mechanics when a sensible default exists.
- Never ask for the user's timezone when the current date/time section includes
  it...
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

recommended_tools is a narrow, enumerated list (create-tasks, workflows, nodes, credentials, data-tables, parse-file, research, ask-user) consistent with the declared multi-artifact planning purpose; no wildcard/catch-all capability, no executable/install-hook shipped.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

recommended_tools:
  - create-tasks
  - workflows
  - nodes
  - credentials
  - data-tables
  - parse-file
  - research
  - ask-user
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

The file is not literally pure ASCII; it contains visible em dashes, but manual review found no zero-width or bidirectional concealment characters, homoglyph deception, or forged role-control tokens, so no hidden directives are present.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Static, git-tracked instruction file with no runtime fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'planning' is a generic, descriptive name with no resemblance to any third-party brand; part of n8n's own instance-ai package.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No instruction reads raw credential material or dumps conversation/system-prompt context to an external sink; credential handling is delegated entirely to the host's credentials tool which the skill merely queries for existence/matching, never reading or exfiltrating secret values.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

- If exactly one matching credential exists, assume it and mention the
  credential name in `planningContext.assumptions`.
- If no matching credential exists, plan normally. The builder will mock or
  leave it unresolved and route setup after verification.
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The skill is a static instruction file that calls already-registered host tools through normal model flow; it bundles no hook, executable entry point, host configuration, or endpoint override, so no format-native out-of-band execution mechanism exists.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

8. After calling `create-tasks`, do not write visible text. The approval card is
   the user-visible surface.

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/packages/@n8n/instance-ai/skills/progressive-buildingpackages/@n8n/instance-ai/skills/progressive-building100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

6 rules passed · 2 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose is a staged-build policy for incremental workflow creation, applied before/around already-registered build-workflow/verify/workflows/executions tools. The body is a prose policy about scoping and pacing increments; no undeclared network egress, credential read, or exec capability is introduced beyond invoking the declared dependency tools.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: progressive-building
dependencies:
  tools:
    - build-workflow
    - verify-built-workflow
    - workflows
    - executions
description: >-
  Load before build-workflow and before scoping or planning new workflows...
---
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

The policy explicitly preserves and routes through the host's approval/consent surfaces at every state-changing step -- it says to keep 'validation, approval, credential, publish, and cleanup rules', requires an 'execution approval card' before a live run, and requires waiting for explicit user confirmation before extending to the next increment. No prose tells the agent to skip confirmation, hide a privileged action, or self-authorize; it constrains PACING of work, not oversight.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

Apply this policy when creating or extending workflows and repairing those
builds. It overrides conflicting scoping and setup narration in the building
and post-build guidance above. Keep their validation, approval, credential,
publish, and cleanup rules. Write to the user in their conversation language.
Show 1 more location
4. After setup, offer only a live run for manual or schedule triggers. The execution
   approval card supplies consent. For event triggers, explain how to start
   listening for a test event and perform the event, then ask the user to report
   back. Inspect the resulting execution with `executions`.
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

The manifest declares a narrow, explicit dependencies.tools list (build-workflow, verify-built-workflow, workflows, executions) matching the stated staged-build-policy purpose -- no wildcard tool grant, no unrelated capability class.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

dependencies:
  tools:
    - build-workflow
    - verify-built-workflow
    - workflows
    - executions
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

ASCII, purely descriptive/prescriptive policy prose with no hidden codepoints, forged chat-template tokens, or homoglyphs.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

# Progressive building

Apply this policy when creating or extending workflows and repairing those
builds. It overrides conflicting scoping and setup narration in the building
and post-build guidance above. Keep their validation, approval, credential,
publish, and cleanup rules. Write to the user in their conversation language.
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

SKILL.md ships inline in the repo bundle; no runtime fetch of instructions and no self-update mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: progressive-building
dependencies:
  tools:
    - build-workflow
    - verify-built-workflow
    - workflows
    - executions
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name progressive-building is descriptive, not a near-miss of any known brand/tool.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

No instruction directs reading any host credential store, env secrets, or conversation/memory context for exfiltration; the only credential-related text instructs RESPECTING user credential choices (skipped/connected), not reading or routing secret values anywhere.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

Keep this limit internal. Explain what the first version does and what comes
next. Preserve the user's services. Prefer existing credentials when choosing
between equally suitable starting points. Follow workflow-builder guidance
on credential setup preferences when the user leaves the service open.
Does not applyFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The skill consists of prose/frontmatter that invokes already-registered host tools via normal in-band flow; it ships no executable helper, lifecycle hook, host configuration write, durable directive, or endpoint/environment substitution.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

---
name: progressive-building
dependencies:
  tools:
    - build-workflow
    - verify-built-workflow
    - workflows
    - executions
---

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/packages/@n8n/instance-ai/skills/suggest-automationspackages/@n8n/instance-ai/skills/suggest-automations100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

8 rules passedShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose (suggest and optionally build automations via ask-user/build-workflow) matches the body's flow exactly. The skill explicitly instructs never asking for credentials/keys/passwords in chat, which is a protective instruction. No undeclared capability found.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

---
name: suggest-automations
description: >-
  Offer the three most common automations for a user as a single-choice card,
  from your own knowledge of their team and their apps, then build the one
  they choose once they confirm, or offer more. The system preloads it on
  onboarding threads, where the opening card already asked the user about
  themselves.
recommended_tools:
  - ask-user
  - build-workflow
---
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

The skill explicitly requires an affirmative 'Yes, build it' confirmation from the user before building, and forbids starting a build after a skip/dismissal/ambiguous free text -- this strengthens rather than weakens the human-in-the-loop gate.

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

## Rules

- One `ask-user` call per turn. Keep every message to four sentences or fewer.
- A build needs `Yes, build it` from the user on the step 4 card, for a
  picked suggestion and for a task in the user's words alike. Never start one
  after a skip, a dismissal, or free text that names nothing.
PassedHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill explicitly forbids asking for credentials, keys, or passwords in chat -- a protective directive directly opposed to this rule's threat model. No instruction reads host secrets or dumps conversation context to an external sink.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

- Never ask for credentials, keys, or passwords in chat.
PassedFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

The named tools are ordinary conversation-conditioned host calls, and no automatic hook or persistent control-plane primitive is present.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

recommended_tools:
  - ask-user
  - build-workflow
---
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

recommended_tools lists only ask-user and build-workflow -- the two tools its documented flow actually invokes -- with no broader or wildcard capability grant.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

recommended_tools:
  - ask-user
  - build-workflow
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Scanned for concealment codepoints and homoglyphs; none found.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: suggest-automations
description: >-
  Offer the three most common automations for a user as a single-choice card...
recommended_tools:
  - ask-user
  - build-workflow
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

First-party, in-repo static file; no live-fetch or self-mutation mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: suggest-automations
description: >-
  Offer the three most common automations for a user as a single-choice card...
recommended_tools:
  - ask-user
  - build-workflow
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Generic descriptive name 'suggest-automations' with no impersonation of any brand or third-party tool.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

---
name: suggest-automations
description: >-
  Offer the three most common automations for a user as a single-choice card...

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
Skillskills-plugins/packages/@n8n/instance-ai/skills/workflow-builderpackages/@n8n/instance-ai/skills/workflow-builder100High trustNone flagged

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

Skill rules 0 of 8 flagged

7 rules passed · 1 did not applyShow
PassedCapability Transparency: Declared Purpose vs. Actual Behavior (Shadow Features)ATR-P0001Details

Why it got this result

Declared purpose (build/edit n8n workflows via the workflow-sdk and build-workflow tool) matches instructed behavior throughout; the skill explicitly forbids hardcoding secrets/fake credentials into generated source ('Do not put secrets in the source file', 'Always use newCredential() for authentication. Never use placeholder strings, fake API keys, hardcoded auth values...') and routes all credential handling through the dedicated credential-setup surface rather than performing any undisclosed credential access itself.

Why it matters

Install-time trust is granted from the artifact's self-description, so any bundled capability the description does not disclose -- network egress, credential reads, or subprocess execution -- runs as an unconsented backdoor with the host agent's full privileges.

Evidence

1. Always use `newCredential()` for authentication. Never use placeholder
   strings, fake API keys, hardcoded auth values, invented credential IDs, or
   raw `mock-*` IDs.
PassedConsent Suppression and Guardrail / Human-in-the-Loop Subversion via Trusted InstructionsATR-P0002Details

Why it got this result

The skill does instruct the agent to proceed building without first asking the user for setup values/credentials ('never stop before the first build-workflow call to ask for setup values ... use placeholders'), but this is scoped to non-destructive, reversible draft-building (placeholders for recipients/accounts/credentials, not secrets) and explicitly preserves escalation for ambiguous/scope-changing choices and NEVER solicits secrets via ask-user, instead routing credential collection through the dedicated setup surface. This is autonomy-within-declared-scope (brevity/workflow-progress optimization), not suppression of a privileged/state-changing action from the user's awareness -- publishing remains explicitly gated on the user ('Do not publish the main workflow automatically. Publishing is the user's decision after testing.').

Why it matters

Because a skill/plugin instruction body is ingested as trusted first-party context, consent- or oversight-suppressing prose inherits the host's full authority and turns a passive package into a self-authorizing backdoor that strips the human-in-the-loop control from every action it can reach.

Evidence

Do not publish the main workflow automatically. Publishing is the user's
decision after testing.
Show 1 more location
preserve the existing state and report any remaining blocker. Never
solicit secrets through `ask-user`; route credential collection through
workflow/credential setup surfaces.
PassedFormat-Native Out-of-Band Execution and Host Control-Plane PersistenceATR-P0004Details

Why it got this result

Workspace writes and build calls are explicit in-band operations for the declared task; the skill ships no lifecycle trigger, endpoint override, or persistent prompt mechanism.

Why it matters

Format-native execution and control-plane writes bypass the model's reasoning loop, converting a single install approval into durable, self-reinjecting code execution that can MITM the host agent's own model endpoint and silently subvert every subsequent tool call.

Evidence

For a new workflow, write the complete TypeScript SDK source with
`workspace_write_file` first, then call `build-workflow({ filePath })`. ...
PassedOver-Declared / Over-Privileged Artifact Manifest ScopeATR-P0005Details

Why it got this result

recommended_tools is an enumerated list (read_file, write_file, edit_file, execute_command, build-workflow, workflows, nodes, data-tables, credentials, verify-built-workflow, executions) that, while broad, is intrinsic to the declared and substantial function of a workflow-building code-generation agent (it must read/write workspace source files and run validators) -- broad-but-justified for the stated purpose, not an undisclosed escalation beyond it.

Why it matters

An artifact that self-declares host privilege far beyond its stated function -- or hides executable reach inside a passive-looking package -- gives a single install a blast radius the operator never scoped, so any later mistake, compromise, or misconfiguration detonates across resources the artifact never needed.

Evidence

recommended_tools:
  - read_file
  - write_file
  - edit_file
  - execute_command
  - build-workflow
  - workflows
  - nodes
  - data-tables
  - credentials
  - verify-built-workflow
  - executions
---
PassedInjection and Concealed Directives in Skill/Plugin Manifest and Instruction TextATR-P0006Details

Why it got this result

Codepoint scan found zero concealment characters across the full file; plain descriptive/instructional prose.

Why it matters

A skill or plugin's always-loaded manifest and instruction carriers (frontmatter/body, `description_for_model`, Gemini `FunctionDeclaration.description` / Vertex AI extension `manifest` / ADK agent `instruction`, tool docstrings, JSON-Schema descriptions) are concatenated into the host agent prompt at operator privilege, so a directive that is hidden with non-printing codepoints, disguised with a homoglyph, forged with role-control tokens, or masked from the rendered view silently commandeers model behavior toward attacker-chosen actions across every tool the artifact serves -- with no code execution required and nothing visible to a reviewer reading the rendered text.

Evidence

---
name: workflow-builder
description: >-
  Load before calling build-workflow. ...
---
PassedSkill/Plugin Package Provenance, Integrity, and Post-Approval Mutation (Rug-Pull)ATR-P0007Details

Why it got this result

Statically bundled SKILL.md; the skill reads/writes workflow SOURCE files (the artifact it builds), not its own instruction body or manifest, and has no runtime self-fetch/self-update mechanism.

Why it matters

A skill/plugin reviewed once and then trusted indefinitely becomes a rug-pull vector when its OWN model-facing instruction body, declared scope, or manifest can be live-swapped or silently mutated after approval -- without an approval-time digest that is re-compared on load and re-consent on any change, a publisher (or an attacker who compromises the distribution channel) can rewrite the artifact's trusted instructions or widen its tool scope under authority the host already granted, turning an approved package into an exfiltration or unauthorized-action backdoor.

Evidence

---
name: workflow-builder
description: >-
  Load before calling build-workflow. ...
---
PassedSkill/Plugin Impersonation, Typosquatting & Publisher SpoofingATR-P0008Details

Why it got this result

Name 'workflow-builder' is a generic functional name with no resemblance to an external brand, shipped from the n8n-io/n8n repo itself.

Why it matters

Impersonation is the entry point of the agentic supply chain (OWASP LLM ASI04 / MITRE ATT&CK T1195): a skill or plugin installed on the strength of a familiar name inherits the host agent's execution context before any payload runs. The deception front-runs trust, so catching the identity lie breaks the chain at the cheapest point -- and it is independent of whether the current code is benign, because the same trusted-looking name can ship a payload in the next version.

Evidence

Does not applyHost-Credential and Conversation-Context Harvesting and ExfiltrationATR-P0003Details

Why it got this result

The skill never instructs reading raw credential secrets or host conversation/memory state for exfiltration -- it explicitly forbids hardcoding/soliciting secrets and routes all credential material through the dedicated credential-setup surface ('Never solicit secrets through ask-user; route credential collection through workflow/credential setup surfaces.'). No harvest step exists.

Why it matters

A skill or plugin that couples a host-credential or live-conversation read with an outbound channel turns an ostensibly benign helper into an autonomous exfiltration agent, leaking long-lived secrets, session tokens, and confidential prompt context to an attacker on every invocation across every tool the artifact serves.

Evidence

preserve the existing state and report any remaining blocker. Never
solicit secrets through `ask-user`; route credential collection through
workflow/credential setup surfaces.

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.
MCP servermcp-server/packages/@n8n/mcp-appspackages/@n8n/mcp-apps100High trustNone flagged

Its tools can

Returns untrusted content 1Read data 1

What stood out

  • MCP server (packages/cli/src/modules/mcp) McpController.setCorsHeaders() unconditionally sets 'Access-Control-Allow-Origin: *' with 'Access-Control-Allow-Credentials: true' and performs no Origin validation, enabling DNS-rebinding/CORS-misuse against state-mutating tools (execute_workflow, install_community_node, list_credentials, create_agent/delete_agent) on a default all-interfaces bind (N8N_LISTEN_ADDRESS='::').
  • MCP Trigger node (packages/@n8n/nodes-langchain) defaults to authentication='none' with no allowedOrigins configuration, so a default-activated MCP Trigger workflow is reachable by any network peer with zero authentication and reflects any Origin header, enabling unauthenticated tool execution and CSRF-style credentialed cross-origin requests.
  • n8n HTTP server defaults to plaintext HTTP (N8N_PROTOCOL default 'http') with no HSTS, forced-upgrade, or warning when MCP OAuth/token/API-key/Bearer-credential endpoints are exposed over plain HTTP on a broadly-bound listener.
  • SSRF protection (SsrfProtectionService) is disabled by default (N8N_SSRF_PROTECTION_ENABLED=false), allowing workflows built from untrusted webhook input to reach internal services and cloud metadata endpoints via the HTTP Request node and similar URL-taking nodes.
  • Dependency scan (Trivy) found 10 CRITICAL / 42 HIGH known-CVE vulnerabilities in resolved lockfile versions with fixed releases available, including actively-imported simple-git/@simple-git/argv-parser (used by Enterprise Source Control), handlebars, jsonata, and tar.
  • @n8n/mcp-browser exposes browser_evaluate (arbitrary JS in the real logged-in browser) and browser_cookies/browser_storage with no per-origin allowlist or capability scoping; a prompt-injected page could direct these tools to read session tokens from any site the user is authenticated to.
  • @n8n/mcp-browser MCP server defaults to a remote streamable-HTTP transport authenticated only by a static bearer token with no OAuth 2.1/PKCE and no built-in TLS, though materially mitigated by loopback-only default binding and constant-time token comparison.
  • Claude Code plugin (.claude/plugins/n8n) auto-registers a PostToolUse hook (track-skill-usage.mjs) that fingerprints the local machine (SHA-256 of username+hostname+platform+arch+release) and POSTs it plus the invoked skill name to https://telemetry.n8n.io with a hardcoded write key, undisclosed in the plugin README or plugin.json manifest.
  • The AI workflow-builder's web_fetch tool returns fetched third-party web content directly into LLM context without a structural envelope or explicit untrusted-content annotation, creating prompt-injection risk despite SSRF validation and per-turn fetch budget mitigations.
  • Container hardening lacks HEALTHCHECK directives in production Dockerfiles, and an isolated benchmark-only Terraform module opens unrestricted internet SSH ingress (0.0.0.0/0 / '*') to a load-test VM.

MCP server rules 0 of 11 flagged

4 rules passed · 7 did not applyShow
PassedTool Definition Integrity & Versioning (ETDI-style)ATR-M0001Details

Why it got this result

The only artifact this component serves to the model/host is the pre-built, bundled HTML for the 'workflow-preview' app. loadAppHtml reads it from the package's own dist/apps/ directory at a path derived from a compile-time-typed AND runtime-enforced allow-list (APP_HTML_FILE_NAME_SET, derived from the single source-of-truth apps-manifest.ts), never from a network fetch or a caller-supplied path. There is no hot-reload-over-HTTP, no remote tool-manifest fetch, and no post-publish mutation path within this package.

Why it matters

Prevents rug-pull tool poisoning.

Evidence

const APP_HTML_FILE_NAME_SET: ReadonlySet<string> = new Set(
	Object.values(MCP_APPS).map((app) => app.htmlFile),
);
const appHtmlCache = new Map<McpAppHtmlFileName, string>();
export async function loadAppHtml(fileName: McpAppHtmlFileName): Promise<string> {
	if (!APP_HTML_FILE_NAME_SET.has(fileName)) {
		throw new Error(`Unknown MCP app HTML file: ${fileName}`);
	}
	const cached = appHtmlCache.get(fileName);
	if (cached !== undefined) return cached;
	const html = await readFile(join(getPackageRoot(), 'dist', 'apps', fileName), 'utf8');
	appHtmlCache.set(fileName, html);
	return html;
}
PassedUnrestricted Tool Permissions (Overprivileged Tools)ATR-M0002Details

Why it got this result

This package does not define any broad/generic exec-style tool. registerMcpAppTool is a thin typed wrapper over the host SDK's registerTool that only normalizes _meta tagging; the one concrete resource it registers (workflow-preview) is a read-only static HTML resource load gated by a strict filename allow-list, with no filesystem, network, or credential reach beyond its own bundled asset.

Why it matters

Limits blast radius and privilege escalation.

Evidence

export function registerWorkflowPreviewApp(
	server: McpAppResourceServer,
	options: RegisterWorkflowPreviewAppOptions,
): void {
	const { instanceOrigin, telemetry, onResourceRead } = options;
	const uiMeta = getWorkflowPreviewUiMeta(instanceOrigin);
	server.registerResource(
		'workflow-preview',
		WORKFLOW_PREVIEW_APP_URI,
		{ description: 'Workflow preview shown after creating a workflow from code', mimeType: RESOURCE_MIME_TYPE, _meta: { ui: uiMeta } },
		async () => {
			const html = await loadAppHtml('workflow-preview.html');
			...
			return { contents: [{ uri: WORKFLOW_PREVIEW_APP_URI, mimeType: RESOURCE_MIME_TYPE, text: injectTelemetryConfig(html, telemetry), _meta: { ui: uiMeta } }] };
…
PassedTool Name Collision / SpoofingATR-M0006Details

Why it got this result

The only named registration this package performs is the workflow-preview resource (URI ui://workflow-preview/workflow-preview.html), a custom namespaced URI scheme, not a generic/popular tool name like read_file or send_message that could shadow a well-known server's tool. registerMcpAppTool itself takes a caller-supplied name from packages/cli, which is out of scope.

Why it matters

Prevents malicious tool shadowing.

Evidence

export const RESOURCE_MIME_TYPE = 'text/html;profile=mcp-app';
export const RESOURCE_URI_META_KEY = 'ui/resourceUri';

export const WORKFLOW_PREVIEW_APP_URI = 'ui://workflow-preview/workflow-preview.html';
PassedPrompt/Metadata Injection in Tool DescriptionsATR-M0007Details

Why it got this result

The only model/host-facing description string in this package is a short, neutral, functional sentence with no imperative directives, hidden control characters, or exfiltration instructions.

Why it matters

Prevents covert model manipulation.

Evidence

		{
			description: 'Workflow preview shown after creating a workflow from code',
			mimeType: RESOURCE_MIME_TYPE,
Does not applyOAuth 2.1 + PKCE Correctness (MCP Authorization)ATR-M0005Details

Why it got this result

This package exposes no authorization surface at all (no transport, no OAuth flow, no token handling); the OAuth/PKCE implementation lives entirely in packages/cli (McpProtectedResource, out of scope for this component).

Why it matters

Misconfig enables token theft and confused-deputy attacks.

Evidence

{
  "name": "@n8n/mcp-apps",
  "version": "0.21.0",
  "main": "dist/server/index.js"
Does not applyNo Authentication or Authorization Checks (General)ATR-M0010Details

Why it got this result

This package implements no listener and no tool dispatch path of its own; it is a pure helper/resource-registration library invoked by the packages/cli MCP server, which performs the actual authn/authz. Nothing in this component accepts a network request.

Why it matters

Prevents unauthorized tool use and data access.

Does not applyOAuth Proxy Per-Client Consent (Confused Deputy Defense)ATR-M0011Details

Why it got this result

This package is not an OAuth proxy and forwards no authorization requests to an upstream IdP; it contains no OAuth-related code at all.

Why it matters

Shared upstream client + dynamic MCP-client registration + no per-client consent enables attacker-registered MCP clients to receive bridge codes carrying victim upstream tokens — the documented MCP Confused Deputy attack.

Does not applyCORS / Origin / Host Validation (DNS Rebinding Defense)ATR-M0003Details

Why it got this result

This component ships no HTTP/SSE/streamable-HTTP listener of its own -- it is a registration-helper/resource library consumed by the actual MCP server in packages/cli (out of the evaluated scope). No listen(), CORS middleware, or Origin/Host check exists anywhere in packages/@n8n/mcp-apps/src. Per the scan-workflow NOT_APPLICABLE guidance, a rule gated on requires_remote_transport is N/A when the component itself exposes no such surface.

Why it matters

Prevents DNS rebinding/browser hijack of local or remote MCP.

Evidence

{
  "name": "@n8n/mcp-apps",
  "version": "0.21.0",
  "description": "MCP Apps UI resources and server helpers for n8n",
  "main": "dist/server/index.js",
Does not applyBinding to 0.0.0.0 Without Auth/TLSATR-M0004Details

Why it got this result

No bind/listen call exists anywhere in this package -- it never opens a network listener; the consuming host server (packages/cli) owns the actual bind address.

Why it matters

LAN/Internet exposure enables remote exploitation.

Evidence

export {
	RESOURCE_MIME_TYPE,
	RESOURCE_URI_META_KEY,
	WORKFLOW_PREVIEW_APP_URI,
} from './constants';
export { registerMcpAppTool, mcpAppToolMeta, type McpAppToolConfig } from './register-mcp-app-tool';
export {
	registerWorkflowPreviewApp,
	type McpAppResourceServer,
	type RegisterWorkflowPreviewAppOptions,
} from './apps/workflow-preview';
export {
	injectTelemetryConfig,
	MCP_APP_TELEMETRY_GLOBAL,
…
Does not applyCORS Misuse: Wildcard with CredentialsATR-M0008Details

Why it got this result

No CORS configuration exists in this package; it ships no HTTP listener.

Why it matters

Prevents credentialed cross-origin abuse.

Does not applyTransport Security: TLS Everywhere (Remote)ATR-M0009Details

Why it got this result

This package never creates an HTTP/HTTPS server; it only produces static resource content for an external server to serve. TLS termination is the host's concern.

Why it matters

MitM can steal tokens and tamper responses.

Show its 2 tools
ToolIt can
registerMcpAppTool
Helper that wraps McpServer.registerTool to mark a tool as backed by an MCP App UI resource (adds _meta.ui.resourceUri / legacy key). Not itself a model-facing tool; used by packages/cli to register the create_workflow_from_code tool with a resourceUri pointing at the workflow-preview app.
workflow-preview (MCP resource: ui://workflow-preview/workflow-preview.html)
Registers a static HTML/Vue MCP App resource rendered by MCP hosts after create_workflow_from_code returns; shows a sandboxed iframe preview of the created workflow, loaded via postMessage from a fixed preview-service origin, with an 'open in n8n' link-out.
Read dataReturns untrusted content

Before you put it to work

  1. Replace the static 'Access-Control-Allow-Origin: *' + 'Access-Control-Allow-Credentials: true' pairing in McpController.setCorsHeaders() with an explicit origin allowlist (or drop credentialed CORS since auth is Bearer-token based), and add Origin/Host validation to defend against DNS rebinding on the MCP streamable-HTTP endpoint.
  2. Add an 'allowedOrigins' option to the MCP Trigger node (mirroring the generic Webhook node) and default new MCP Trigger nodes to a non-'none' authentication mode, or surface an explicit warning/confirmation at workflow activation time when authentication='none' and the instance is not bound to loopback.
  3. Document and enforce that MCP OAuth/token/API-key Bearer endpoints and the @n8n/mcp-browser server must sit behind a TLS-terminating reverse proxy in production; consider warning or refusing plaintext-HTTP startup when bound beyond loopback.
  4. Consider defaulting N8N_SSRF_PROTECTION_ENABLED=true for new installs, or prompting operators to opt in during first-run setup, given the mature guard infrastructure already exists.
  5. Bump simple-git, @simple-git/argv-parser, handlebars, jsonata, and tar to the patched versions identified by Trivy and regenerate lockfiles.
  6. Add an optional per-session/per-task origin allowlist gating browser_evaluate, browser_cookies, and browser_storage in @n8n/mcp-browser to approved domains, extending the existing sensitivity-refusal mechanism.
  7. Disclose the n8n Claude Code plugin's PostToolUse telemetry hook in its README and plugin.json manifest (documenting what is collected and where it is sent), consider making it opt-in, narrow the fingerprint to drop hostname/OS-version components, and declare explicit capability/permission scope in plugin.json.
  8. Add a host-enforced content envelope, control-character/injection-marker sanitization, and an explicit untrusted-content annotation to the web_fetch tool's returned content before it re-enters LLM context.
  9. Add HEALTHCHECK directives to production Dockerfiles and restrict the benchmark VM Terraform SSH ingress rule to a known CIDR instead of 0.0.0.0/0 / '*'.
  10. Consider implementing MCP Authorization (OAuth 2.1 + PKCE) for the @n8n/mcp-browser HTTP transport, or explicitly document the static-bearer-token model as an accepted deviation with guidance on token rotation and secure storage.

Repository-wide rules

Checked once for the whole repository: dependencies, secrets, inputs, networking, CI and more. 4 of 15 flagged.

11 rules passedShow
PassedRuntime Command Injection via Process ExecutionATR-C0003Details

Why it got this result

The repo's shipped runtime does contain a process-execution sink that runs a fully attacker/user-supplied command string with a shell: the 'Execute Command' node (packages/nodes-base/nodes/ExecuteCommand/ExecuteCommand.node.ts) calls spawn(command, { shell: true, ... }) where command is the node's 'Command' parameter. However this is the node's explicitly declared purpose ('Executes a command on the host') -- it is a documented, opt-in automation primitive that a workflow author deliberately adds to a workflow, analogous to a code-execution tool whose stated job is running code; it is not reachable from an unauthenticated network boundary or from third-party data without the workflow owner's own design. This is within_declared_contract and not an unintended injection bug. All other process-execution call sites reviewed (packages/cli/src/modules/source-control.ee/source-control-git.service.ee.ts execSync('git --version'), execSync('ssh -V'); @n8n/computer-use shell sandbox which wraps commands through an OS-level sandbox by default and fails closed on sandbox-init failure; @n8n/node-cli dev-server utilities) either use fixed literal commands with no attacker influence, or are developer-tooling/build-time paths excluded from this rule's execution_contexts.

Why it matters

Unsanitized command construction in shipped runtime code can give a remote attacker arbitrary code execution with the application's privileges.

Evidence

		const detached = process.platform !== 'win32';
		// `windowsHide` stops Windows from opening a visible console window for the command.
		const child = spawn(command, {
			cwd: process.cwd(),
			shell: true,
			detached,
			windowsHide: true,
		});
Show 2 more locations
export class ExecuteCommand implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Execute Command',
		name: 'executeCommand',
		...
		description: 'Executes a command on the host',
		...
		properties: [
			...
			{
				displayName: 'Command',
				name: 'command',
				...
				description: 'The command to execute',
…
	private preInitCheck(): boolean {
		this.logger.debug('GitService.preCheck');
		try {
			const gitResult = execSync('git --version', {
				stdio: ['pipe', 'pipe', 'pipe'],
			});
PassedWeaponization: Malicious-Purpose Artifact (Offensive Capability by Design)ATR-R0001Details

Why it got this result

n8n is a general-purpose workflow-automation platform. Its AGENTS.md, SKILL.md files and agent-framework code (packages/@n8n/agents, instance-ai, ai-workflow-builder) describe automation/orchestration capabilities (workflow building, Slack/Teams/WhatsApp integrations, CI tooling, code-review helpers) with no offensive-security framing. The repo does ship a sandboxed 'computer-use' shell-execution capability and an 'Execute Command' node, but both are general-purpose automation primitives (declared, documented, and gated by the workflow owner's own design) with no malware/ransomware/credential-harvesting/exploit-generation instructions, no C2/beacon/dropper tooling, and no instructions directing the agent to evade security products or operate covertly/suppress logging. No YARA match in the provided scan indicated an operational-attack-tooling cluster; the flagged clusters were reviewed as metadata/skill-instruction content for other rules (tool-poisoning/consent-suppression signals), not weaponization content.

Why it matters

An artifact whose purpose is to weaponize the agent -- to produce malware, steal credentials, evade defenses, or run attacks at scale -- is dangerous regardless of how securely it is built; posture controls (auth, input validation, least privilege) cannot make a weapon safe, which is why a confirmed weaponization finding caps the trust verdict to `blocked` rather than being averaged against a good posture score. Catching malicious PURPOSE is a distinct safety control, and it must be conservative because offensive capability is dual-use: legitimate pentest, CTF, defensive, and research work must abstain (UNKNOWN), never block -- unless the artifact itself hides from the people who install, run or audit it.

Evidence

(AGENTS.md documents n8n's own agent-development conventions and skill authoring guidance for contributors building workflow-automation nodes; no offensive-capability directives present)
PassedStatic SecretsATR-C0002Details

Why it got this result

Gitleaks (408 findings), Semgrep secrets rules, and the ATR-C0002 credential-to-log-sink Zeus pack were reviewed. All non-test gitleaks matches are public client-side analytics write-keys (by design, not secrets), a redaction-pattern regex (detection code, not a leaked key), or a test RSA public key in a browser-extension test manifest. The 6 Zeus credential-to-log-sink pack hits were individually read: all are either explicit debug/opt-in prints of a locally-generated MCP bearer token to the operator's own console (server.ts), structured log calls logging config/metadata field *names* and non-secret derived values rather than secret values (task_runner_config.py, task_analyzer.py), or developer-only local-stack / codespace-preview console output that prints locally-generated test credentials for the operator's own convenience, never persisted or sent off-host. No committed .env file or config ships a real production API key, password, or token value.

Why it matters

Plaintext secrets are easily exfiltrated.

Evidence

# Anonymous failure report (RudderStack): offered only when an install or
# upgrade fails, and sent only after the user answers yes. The payload is the
# script version, OS name, install|upgrade mode and the failed step — nothing
# else. The write key is a public client-side key. DO_NOT_TRACK=1 disables
# the offer entirely.
TELEMETRY_URL="https://nnrry.dataplane.rudderstack.com/v1/track"
TELEMETRY_WRITE_KEY="[REDACTED]
Show 2 more locations
		if (authTokenGenerated) {
			console.error(`Auth token: ${authToken}`);
			console.error('Pass this token as "Authorization: Bearer <token>" on every request.');
		}
    @classmethod
    def from_env(cls):
        grant_token = read_str_env(ENV_GRANT_TOKEN, "")
        if not grant_token:
            raise ConfigurationError(
                "Environment variable N8N_RUNNERS_GRANT_TOKEN is required"
            )

+25 more locations in the finding JSON

PassedMissing or Inadequate Input ValidationATR-C0008Details

Why it got this result

n8n's REST/public API and webhook layers use schema validation extensively: the public API (packages/cli/src/public-api) is generated from and validated against an OpenAPI schema (openapi-gen), the body-parser enforces size limits and a strict allowlist of content-types before any parsing, and query/path params for most controllers use class-validator/zod-based DTOs (seen throughout packages/cli/src/controllers via @n8n/decorators). Node parameters (e.g. ExecuteCommand's 'command', HTTP Request's 'url') are typed and validated through n8n's own INodeTypeDescription property-type system, with runtime guards like the SSRF/host-validation layer and the Python task-runner's dedicated task_analyzer.py AST-based input/import allowlist (BLOCKED_ATTRIBUTES/BLOCKED_NAMES validation on any code sent to the sandboxed runner) providing defense-in-depth beyond simple type checks.

Why it matters

Frontline control to prevent multiple classes of vulns.

Evidence

export const parseBody = async (req: Request) => {
	if (req.contentType?.startsWith('multipart/')) {
		return;
	}
	await req.readRawBody();
	const { rawBody, contentType, encoding } = req;
	if (rawBody?.length) {
		try {
			if (contentType === 'application/json') {
				req.body = jsonParse(stripBom(rawBody.toString(encoding)));
			} else if (contentType?.endsWith('/xml') || contentType?.endsWith('+xml')) {
				req.body = await createXmlParser().parseStringPromise(rawBody.toString(encoding));
			} ...
		} catch (error) {
…
Show 1 more location
            if attr in BLOCKED_ATTRIBUTES:
                self._add_violation(
                    node.lineno, ERROR_MATCH_PATTERN_ATTRIBUTE.format(attr=attr)
                )
        self.generic_visit(node)

    def _check_format_string(self, s: str, lineno: int) -> None:
        for token in find_blocked_format_tokens(s):
            self._add_violation(
                lineno, ERROR_DANGEROUS_STRING_PATTERN.format(attr=token)
            )

    def _scan_string_literal(self, s: str, lineno: int) -> None:
        if s in BLOCKED_NAMES:
PassedInsecure DeserializationATR-C0009Details

Why it got this result

No use of Python pickle.load/loads or unsafe yaml.load on untrusted/network-sourced data was found in application runtime code (bandit and manual grep across packages/@n8n/task-runner-python and packages/core found none outside tests). JSON parsing throughout the HTTP body-parser and workflow engine uses n8n's own jsonParse (JSON.parse-based, no prototype-pollution-prone custom reviver observed in the body-parser path) rather than a code-executing deserializer. The two 'vm.Script'/'new Function' sites found (packages/workflow/src/safe-regex.ts, packages/workflow/src/workflow-data-proxy.ts) execute fixed, hardcoded script text (not deserialized attacker data) inside a constrained regex/feature-detection sandbox, not a general deserialization sink.

Why it matters

Can lead to arbitrary code execution.

Evidence

if (rawBody?.length) {
	try {
		if (contentType === 'application/json') {
			req.body = jsonParse(stripBom(rawBody.toString(encoding)));
		}
PassedPath Traversal & Symlink Bypass (Filesystem Tools)ATR-C0011Details

Why it got this result

Framework-level filesystem endpoints enumerated: the icon-serving (/icons/:packageName/*) and node-schema-serving (/schemas/...) static-asset routes resolve caller-supplied path segments through resolveIcon/resolveSchema, both gated by isContainedWithin(loader.directory, filePath) before res.sendFile. The remaining semgrep-flagged path.join sites in load-nodes-and-credentials.ts are not attacker-reachable: line 232 joins a directory name from glob() over the trusted nodeModulesDir; line 267 is inside resolveIcon's own path.resolve, itself gated by isContainedWithin below it; line 371 (loadPackage) joins a packageName validated upstream against a strict NPM_PACKAGE_NAME_PATTERN and reserved-name denylist; line 851 calls fs.realpath on a path built from entry.name returned by readdir() of the local custom-nodes directory (filesystem-sourced, not externally supplied) purely to resolve a symlink for a file-watcher, with failures swallowed via .catch(() => null) and no onward use as a request-serving path.

Why it matters

Traversal exposes arbitrary host files; can lead to code execution.

Evidence

export function isContainedWithin(parentPath: string, childPath: string): boolean {
	parentPath = path.resolve(parentPath);
	childPath = path.resolve(childPath);
	if (parentPath === childPath) {
		return true;
	}
	return childPath.startsWith(parentPath + path.sep);
}
Show 2 more locations
	private async loadNodesFromNodeModules(
		nodeModulesDir: string,
		packageName: string,
	): Promise<void> {
		const installedPackagePaths = await glob(packageName, {
			cwd: nodeModulesDir,
			onlyDirectories: true,
			deep: 1,
		});
		for (const packagePath of installedPackagePaths) {
			try {
				await this.runDirectoryLoader(
					LazyPackageDirectoryLoader,
					path.join(nodeModulesDir, packagePath),
…
	resolveIcon(packageName: string, url: string): string | undefined {
		const isCustom = packageName === CUSTOM_NODES_PACKAGE_NAME;
		const loader = this.loaders[packageName];
		if (!loader || !(loader instanceof DirectoryLoader)) {
			return undefined;
		}
		const resolvePath = (iconPath: string) => {
			return path.resolve(loader.directory, iconPath);
		};
		...
		const filePath = isCustom ? resolvePathCustom(urlFilePath) : resolvePath(urlFilePath);
		return isContainedWithin(loader.directory, filePath) ? filePath : undefined;
	}

+7 more locations in the finding JSON

PassedCI/CD Workflow Script InjectionATR-C0014Details

Why it got this result

Reviewed every workflow using a dangerous trigger (pull_request_target, issue_comment, workflow_run, merge_group, workflow_dispatch) for direct interpolation of attacker-controlled fields into a shell run: block. ci-cla-check.yml, ci-pr-quality.yml, ci-owners-required-reviews.yml and ci-pull-request-review.yml all consistently pass any github.event.* / inputs.* values through env: indirection (consumed as quoted shell variables, e.g. SHA="${{ ... }}" then used as "$SHA") or through conditional if: boolean expressions rather than direct ${{ }} interpolation inside a run: block; none of the matched 'run-shell-injection' semgrep hits (ci-master.yml, clean-stale-branches.yml, docker-sbom-probe.yml, release-push-to-channel.yml, test-benchmark-nightly.yml, test-e2e-helm.yml) interpolate an attacker-controlled PR title/body/comment/branch-name expression directly into a run: block -- they interpolate workflow_dispatch 'inputs' (which require repo write/maintain access to trigger) or fixed metadata (github.run_id, github.repository). sec-publish-fix.yml and sec-publish-fix-1x.yml interpolate github.event.pull_request.title into an env: var (PR_TITLE) consumed only as a quoted gh CLI --title argument, not re-parsed by a shell.

Why it matters

CI/CD script injection can execute attacker-controlled commands with workflow tokens, repository secrets, trusted-runner access, artifact or cache authority, and release or deployment privileges.

Evidence

      - uses: n8n-io/github-actions/cla-check@ea082925743265a61d9fd0a69a32b211551f88f0 # v1.0.2
        with:
          app-id: ${{ secrets.N8N_ASSISTANT_APP_ID }}
          pr-number: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number || github.event.issue.number }}
Show 2 more locations
          gh pr create \
            --repo n8n-io/n8n \
            --base master \
            --head "$BRANCH_NAME" \
            --title "$PR_TITLE" \
            --body "Cherry-picked from n8n-private. Original PR: $PR_URL"
        env:
          PR_TITLE: ${{ github.event.pull_request.title }}
      - name: Assert OCI image index
        run: |
          node .github/scripts/docker/assert-manifest-format.mjs \
            "${IMAGE}:${SCRATCH_TAG}" --expect-platforms 2
      - name: Report scratch tag
        run: |
          echo "### SBOM probe (sbom=${{ inputs.sbom }})"
PassedDoS Protections: Timeouts, Concurrency, Streaming LimitsATR-C0004Details

Why it got this result

The HTTP server applies a configurable payload size limit on every request body (payloadSizeMax, defaulting from GlobalConfig) via raw-body's limit option before any parsing occurs, preventing unbounded body reads. The outbound HTTP client layer (@n8n/backend-network OutboundHttp/HttpTransport) supports per-client and per-request timeout configuration and undici dispatcher-level timeout overrides. The AI workflow-builder's web_fetch tool enforces a per-turn fetch budget (WEB_FETCH_MAX_PER_TURN) guarding against unbounded tool-driven fetch loops. Execution of the ExecuteCommand node output is capped via appendCapped()/MAX_OUTPUT_SIZE (10MB) to bound memory growth from command output.

Why it matters

Resource exhaustion can crash or stall MCP.

Evidence

				try {
					req.rawBody = await getRawBody(stream, {
						length: contentLength,
						limit: `${String(payloadSizeMax)}mb`,
					});
Show 2 more locations
const SIGKILL_GRACE_MS = 5000;
const MAX_OUTPUT_SIZE = 10 * 1024 * 1024;

function appendCapped(current: string, data: string): string {
	return (current + data).slice(-MAX_OUTPUT_SIZE);
}
export const WEB_FETCH_MAX_PER_TURN = 5; // per-turn fetch budget enforced in web-fetch.tool.ts
PassedLogging Hygiene (No Secrets/PII/Tokens) & Audit TrailATR-C0005Details

Why it got this result

Production backend logging (Logger service used throughout packages/cli/src) logs structured event metadata (clientId, userId, action names) rather than secret values; reviewed OAuth/SSO/token-service logger calls confirm no raw token/password/authorization-header values are logged. The one console.error of a bearer token (@n8n/mcp-browser/src/server.ts) is a deliberate one-time operator-facing print of a locally auto-generated MCP access token at stdio-server-startup time (not a persisted/forwarded log sink) so the operator can copy it to configure their client -- analogous to printing a generated admin password at first-run. Developer-only local-stack / codespace-preview scripts print locally-generated test credentials to the console for the operator's own convenience; these are dev tooling, not the production audit/log path. A dedicated per-tool-invocation audit trail is present for OAuth token issuance/revocation (clientId/userId only).

Why it matters

Prevents sensitive data leakage; enables forensics.

Evidence

			this.logger.info('Refresh token rotated and new access token issued', {
				clientId,
				userId: refreshTokenRecord.userId,
			});
Show 1 more location
		if (authTokenGenerated) {
			console.error(`Auth token: ${authToken}`);
			console.error('Pass this token as "Authorization: Bearer <token>" on every request.');
		}
PassedLack of Output Encoding/SanitizationATR-C0010Details

Why it got this result

The editor-ui frontend (Vue) was flagged by semgrep for a small number of v-html / avoid-v-html usages; these are UI-rendering concerns in the separately-built frontend bundle, largely for rendering already-sanitized markdown/HTML content. No server-side reflected-output sink (e.g., an Express route echoing raw user input into an HTML response without escaping) was identified in the reviewed packages/cli/src HTTP handlers, which emit JSON via res.json/sendFile rather than templated HTML strings.

Why it matters

Prevents UI-level injection and operator compromise.

Evidence

const respondWithPrivateFile = (res: express.Response, filePath: string, assetMaxAge = 0) => {
	const maxAgeSeconds = Math.floor(assetMaxAge * Time.milliseconds.toSeconds);
	res.setHeader('Cache-Control', `private, max-age=${maxAgeSeconds}`);
	return res.sendFile(filePath, { maxAge: assetMaxAge, dotfiles: 'allow' });
};
PassedDenial of Wallet / Cost ControlsATR-C0012Details

Why it got this result

The AI workflow-builder's web_fetch tool enforces a hard per-turn budget (WEB_FETCH_MAX_PER_TURN) before issuing any external fetch, and AI node credentials/usage are gated behind n8n's licensing and instance configuration. The Python task runner enforces configurable task timeouts and an auto-shutdown timeout. No evidence of unbounded, unthrottled loops invoking metered external AI/LLM APIs without a cap was found in the reviewed AI tool-invocation code paths.

Why it matters

Prevents cost-based disruption.

Evidence

				// 2. Check per-turn budget
				if (!security.hasBudget()) {
					const message = `Maximum of ${WEB_FETCH_MAX_PER_TURN} web fetches per turn reached. Please continue without additional fetches.`;
					reporter.error({ message });
					return createSuccessResponse(config, message);
				}
From build to runtime

You know what it is. Now see what it does.

A scan helps you understand what you’re building with. When your agent runs, BlueRock shows you what actually happens across tools and systems. Follow actions in context, understand behavior as it unfolds and add controls when you need them.

BUILDRegistry + scan
RUNSee actual execution
OPERATEObserve, control, optimize

Results reflect the code at the commit and date shown.