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:
@@ -462,6 +462,48 @@ class TestPerKeyPolicy:
|
||||
client.close()
|
||||
t.join(timeout=2)
|
||||
|
||||
def _trust_and_query_keys(self, tmp_path, monkeypatch, server_policy):
|
||||
"""Authenticate, then send browser-cli.auth.keys; return the response dict."""
|
||||
from browser_cli.serve.security import ServeSecurity
|
||||
|
||||
path = tmp_path / "authorized_keys"
|
||||
pem, pub = generate_keypair()
|
||||
path.write_text(pub + " mykey\n")
|
||||
key_path = tmp_path / "client.key.pem"
|
||||
key_path.write_bytes(pem)
|
||||
priv = load_private_key(key_path)
|
||||
|
||||
client, server = _pair()
|
||||
t = _spawn(server, path, ServeSecurity(policy=server_policy))
|
||||
challenge = _recv_framed(client)
|
||||
nonce = bytes.fromhex(challenge["nonce"])
|
||||
msg = {"id": "x", "command": "browser-cli.auth.keys", "args": {}, "user_agent": FAKE_UA, "pubkey": pub}
|
||||
msg["sig"] = sign(priv, nonce, msg).hex()
|
||||
_send_framed(client, json.dumps(msg).encode())
|
||||
resp = _recv_framed(client)
|
||||
client.close()
|
||||
t.join(timeout=2)
|
||||
return resp
|
||||
|
||||
def test_key_management_blocked_without_keys_grant(self, tmp_path, monkeypatch):
|
||||
"""Even full control+dangerous can't list keys — the control command is gated."""
|
||||
from browser_cli.command_security import CommandPolicy
|
||||
|
||||
resp = self._trust_and_query_keys(
|
||||
tmp_path, monkeypatch,
|
||||
CommandPolicy(allow_read_page=True, allow_control=True, allow_dangerous=True),
|
||||
)
|
||||
assert resp["success"] is False
|
||||
assert "blocked" in resp["error"].lower() and "keys" in resp["error"].lower()
|
||||
|
||||
def test_key_management_allowed_with_keys_grant(self, tmp_path, monkeypatch):
|
||||
"""With allow_keys, the control command runs and returns the trusted-key list."""
|
||||
from browser_cli.command_security import CommandPolicy
|
||||
|
||||
resp = self._trust_and_query_keys(tmp_path, monkeypatch, CommandPolicy(allow_keys=True))
|
||||
assert resp["success"] is True
|
||||
assert resp["data"][0]["name"] == "mykey"
|
||||
|
||||
class TestRateLimit:
|
||||
def test_shared_rate_limiter_blocks_second_command(self, monkeypatch):
|
||||
"""A burst-1 limiter shared across connections allows the first command, denies the next."""
|
||||
|
||||
Reference in New Issue
Block a user