feat: add n8n serve node and harden remote access

- Add the n8n community node package with credentials, command mapping, direct serve TCP client, and browser-cli protocol crypto helpers.

- Cover Ed25519 signing, canonical JSON, PQ transport encryption, request mapping, and security behavior with unit tests.

- Harden serve-http with per-address rate limiting, an 8 MB request body cap, and clear warnings when binding plain HTTP beyond loopback.

- Stop one-shot --key overrides from being persisted automatically; document explicit remote trust and keep key-management behind the keys policy tier.

- Make HTML-to-Markdown conversion safer by bounding tree depth and dropping unsafe link/image URL schemes.

- Bump package and extension release metadata to 0.16.3.
This commit is contained in:
2026-06-19 10:00:23 +02:00
parent 7fe0e27fec
commit cea8a7e994
28 changed files with 3687 additions and 164 deletions
@@ -0,0 +1,73 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { buildCommand, parseTabIds } from '../nodes/BrowserCli/request';
test('tab:list maps to tabs.list', () => {
assert.deepEqual(buildCommand('tab', 'list', {}), { command: 'tabs.list', args: {} });
});
test('client:list maps to clients.list', () => {
assert.deepEqual(buildCommand('client', 'list', {}), { command: 'clients.list', args: {} });
});
test('gateway:health pings with tabs.list (serve has no health route)', () => {
assert.deepEqual(buildCommand('gateway', 'health', {}), { command: 'tabs.list', args: {} });
});
test('tab:open sends navigate.open with background derived from focus', () => {
const bg = buildCommand('tab', 'open', { url: 'https://example.com', focus: false });
assert.deepEqual(bg, {
command: 'navigate.open',
args: { url: 'https://example.com', focus: false, background: true },
});
const fg = buildCommand('tab', 'open', { url: 'https://example.com', focus: true });
assert.equal(fg.args.focus, true);
assert.equal(fg.args.background, false, 'focused open sends background:false (matches SDK)');
});
test('tab:close supports ids, inactive, duplicates', () => {
assert.deepEqual(buildCommand('tab', 'close', { mode: 'ids', tabIds: '12, 34' }), {
command: 'tabs.close',
args: { tabIds: [12, 34] },
});
assert.deepEqual(buildCommand('tab', 'close', { mode: 'inactive' }).args, { inactive: true });
assert.deepEqual(buildCommand('tab', 'close', { mode: 'duplicates' }).args, { duplicates: true });
});
test('dom:type sends dom.type with selector + text', () => {
assert.deepEqual(buildCommand('dom', 'type', { selector: '#q', text: 'hello' }), {
command: 'dom.type',
args: { selector: '#q', text: 'hello' },
});
});
test('dom:eval includes tabId only when set', () => {
assert.deepEqual(buildCommand('dom', 'eval', { code: 'return 1', tabId: 0 }).args, { code: 'return 1' });
assert.deepEqual(buildCommand('dom', 'eval', { code: 'return 1', tabId: 7 }).args, { code: 'return 1', tabId: 7 });
});
test('page:extractMarkdown omits empty selector', () => {
assert.deepEqual(buildCommand('page', 'extractMarkdown', { selector: '' }).args, {});
assert.deepEqual(buildCommand('page', 'extractMarkdown', { selector: 'main' }).args, { selector: 'main' });
});
test('command:execute passes command and args through', () => {
assert.deepEqual(buildCommand('command', 'execute', { command: 'tabs.query', args: { search: 'docs' } }), {
command: 'tabs.query',
args: { search: 'docs' },
});
});
test('unknown operation throws', () => {
assert.throws(() => buildCommand('tab', 'nope', {}), /Unsupported operation/);
});
test('parseTabIds accepts array, json, and delimited strings', () => {
assert.deepEqual(parseTabIds([1, 2, 3]), [1, 2, 3]);
assert.deepEqual(parseTabIds('[4, 5]'), [4, 5]);
assert.deepEqual(parseTabIds('6, 7 8'), [6, 7, 8]);
assert.deepEqual(parseTabIds(''), []);
assert.deepEqual(parseTabIds(9), [9]);
});