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
+42
View File
@@ -357,6 +357,48 @@ def test_serve_http_uses_compare_digest():
assert "compare_digest" in src
assert "== f\"Bearer" not in src
def test_serve_http_rate_limiter_blocks_when_exhausted():
"""A burst-1 limiter lets the first request through, then sends 429."""
from browser_cli.commands.serve_http import _Handler
from browser_cli.serve.security import RateLimiter
handler = _Handler.__new__(_Handler)
handler.client_address = ("203.0.113.5", 5000)
handler.rate_limiter = RateLimiter(rate=0.001, burst=1)
sent = []
handler._send = lambda status, payload: sent.append((status, payload))
assert handler._within_rate_limit() is True
assert handler._within_rate_limit() is False
assert sent and sent[-1][0] == 429
def test_serve_http_rate_limiter_none_never_limits():
from browser_cli.commands.serve_http import _Handler
handler = _Handler.__new__(_Handler)
handler.client_address = ("203.0.113.5", 5000)
handler.rate_limiter = None
assert all(handler._within_rate_limit() for _ in range(100))
def test_serve_http_default_rate_limit_active():
from browser_cli.commands.serve_http import _Handler
with patch("browser_cli.commands.serve_http.BrowserCLI"), \
patch("browser_cli.commands.serve_http.ThreadingHTTPServer") as server_cls:
server_cls.return_value.serve_forever.side_effect = KeyboardInterrupt
CliRunner().invoke(main, ["serve-http", "--no-auth"])
# The handler class passed to the server carries an active RateLimiter by default.
handler_cls = server_cls.call_args.args[1]
assert handler_cls.rate_limiter is not None
assert handler_cls.rate_limiter.rate == 100.0
def test_serve_http_non_loopback_warns_about_cleartext():
with patch("browser_cli.commands.serve_http.BrowserCLI"), \
patch("browser_cli.commands.serve_http.ThreadingHTTPServer") as server_cls:
server_cls.return_value.serve_forever.side_effect = KeyboardInterrupt
result = CliRunner().invoke(main, ["serve-http", "--host", "0.0.0.0", "--token", "x"])
assert "clear text" in result.output
def test_command_policy_allow_all_grants_everything():
policy = command_policy_from_options(
allow_read_page=False, allow_control=False, allow_dangerous=False, allow_all=True