feat: harden remote serve and reuse connections
Testing / remote-protocol-compat (0.9.5) (push) Successful in 56s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 59s
Testing / test (push) Successful in 1m1s
Build & Publish Package / publish (push) Successful in 33s
Package Extension / package-extension (push) Successful in 36s

- Gate TCP serve commands with safe-by-default policies, per-key allow tokens, per-key rate limiting, and audit labels.
- Reuse authenticated encrypted remote sessions and parallelize/caches multi-browser fanout to reduce repeated handshake roundtrips.
- Increase paged native-host batch size with extension-side byte budgeting to speed large tab listings safely.
- Point install output at public Chrome Web Store / Firefox AMO listings by default, with --dev preserving unpacked workflows.
- Share search-engine metadata between CLI and SDK and bump the package/extension version to 0.16.0.
- Cover the new security, pooling, paging, install, and fanout behavior with expanded Python and extension tests.
This commit is contained in:
2026-06-18 14:24:15 +02:00
parent 8dece7800f
commit 6fa931aa36
49 changed files with 3407 additions and 1878 deletions
+28 -2
View File
@@ -20,24 +20,50 @@ from browser_cli.remote.auth import (
from browser_cli.remote.socket import (
async_recv_all as _async_recv_all,
async_recv_exact_bytes as _async_recv_exact,
connect_socket as _connect_socket,
open_async_connection as _open_async_connection,
open_socket as _open_socket,
recv_all as _recv_all,
recv_exact_bytes as _recv_exact,
split_endpoint as _split_endpoint,
)
from browser_cli.remote import pool as _pool
def _send_remote(endpoint: str, msg: dict, private_key=None, *, warn_no_pq: bool | None = None) -> bytes | None:
# Reuse an already-authenticated connection when one is idle for this endpoint.
conn = _pool.checkout(endpoint)
if conn is not None:
try:
response = _pool.send_over(conn, msg)
_pool.checkin(endpoint, conn)
return response
except (OSError, ConnectionError, ValueError, EOFError):
_pool.discard(conn) # stale/closed — fall through to a fresh handshake
return _send_remote_handshake(endpoint, msg, private_key, warn_no_pq=warn_no_pq)
def _send_remote_handshake(endpoint: str, msg: dict, private_key=None, *, warn_no_pq: bool | None = None) -> bytes | None:
warn = _should_warn_no_pq(msg) if warn_no_pq is None else warn_no_pq
def build_auth(sync_msg: dict, challenge: dict | None, nonce_hex: str | None, key):
from browser_cli.auth import pq_kex_client_encapsulate
return _build_auth_message(sync_msg, challenge, nonce_hex, key, pq_kex_client_encapsulate, warn_no_pq=warn)
with _open_socket(endpoint) as sock:
sock = _connect_socket(endpoint)
try:
payload_msg, pq_shared_secret = _with_challenge(_recv_all(sock), msg, private_key, build_auth)
sock.sendall(frame(json.dumps(payload_msg).encode("utf-8")))
return _decode_pq_response(_recv_all(sock), pq_shared_secret)
response = _decode_pq_response(_recv_all(sock), pq_shared_secret)
except BaseException:
_pool._close(sock)
raise
# Only encrypted sessions are reusable — the server keeps those open, and a
# fresh AEAD nonce per frame keeps reuse of the shared secret safe.
if pq_shared_secret is not None:
_pool.checkin(endpoint, _pool.PooledConnection(sock, pq_shared_secret))
else:
_pool._close(sock)
return response
async def _send_remote_async(endpoint: str, msg: dict, private_key=None, *, warn_no_pq: bool | None = None) -> bytes | None:
reader, writer = await _open_async_connection(endpoint)