From 809c73c3a37fb7371fe66998739ab8cf10b16169 Mon Sep 17 00:00:00 2001 From: Daniel Dolezal Date: Sun, 14 Jun 2026 15:13:55 +0200 Subject: [PATCH] chore: remove ServiceLink integration - Drop the ServiceLink submodule, link-serve command, and serve --rpc mode. - Remove the now-unused httpx dependency and regenerate uv.lock. - Add a privacy policy for Chrome Web Store publication. - Refresh extension icons and bump package/extension version to 0.14.2. - Keep the native TCP serve path as the only remote-control endpoint. --- .gitmodules | 3 - PRIVACY.md | 23 ++++ browser_cli/cli.py | 2 - browser_cli/commands/link_serve.py | 163 ----------------------------- browser_cli/commands/serve.py | 53 +--------- extension/icon.svg | 31 +++--- extension/icons/icon-128.png | Bin 6506 -> 5845 bytes extension/icons/icon-16.png | Bin 1475 -> 576 bytes extension/icons/icon-32.png | Bin 2089 -> 1262 bytes extension/icons/icon-48.png | Bin 3260 -> 1948 bytes extension/manifest.json | 2 +- pyproject.toml | 3 +- servicelink | 1 - uv.lock | 73 +------------ 14 files changed, 46 insertions(+), 308 deletions(-) delete mode 100644 .gitmodules create mode 100644 PRIVACY.md delete mode 100644 browser_cli/commands/link_serve.py delete mode 160000 servicelink diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 36e5f81..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "servicelink"] - path = servicelink - url = git@git.yiprawr.dev:submodules/servicelink.git diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..5c44e4d --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,23 @@ +# Privacy Policy for browser-cli +Last updated: 2026-06-14 + +browser-cli does not collect, transmit, sell, or share user data with the developer or any third party. + +browser-cli is a local browser automation tool. The browser extension communicates with the locally installed browser-cli native messaging host so the user can control their own browser through the command line or Python SDK. + +## Local data access +Depending on the command explicitly run by the user, browser-cli may locally access browser data such as: +- tab URLs, titles, status, and window or tab group information +- page content, links, images, HTML, text, screenshots, or DOM data +- cookies, local storage, session storage, and saved browser-cli session data + +This access happens only to perform the command requested by the user. The data stays on the user's device unless the user explicitly configures browser-cli to connect to another machine they control. + +## Remote control mode +browser-cli includes an optional remote control mode. If the user enables this mode, command data may be transmitted between the user's configured browser-cli client and server endpoints. This is user-configured infrastructure. The developer does not receive or operate these endpoints. + +## No analytics or tracking +browser-cli does not use analytics, telemetry, advertising, behavioral tracking, or remote code. The extension does not send data to the developer. + +## Contact +For privacy questions or security reports, please open an issue in the project repository or contact the project maintainer through the repository hosting platform. diff --git a/browser_cli/cli.py b/browser_cli/cli.py index f8aceff..567932f 100755 --- a/browser_cli/cli.py +++ b/browser_cli/cli.py @@ -23,7 +23,6 @@ from browser_cli.commands.storage import storage_group from browser_cli.commands.perf import perf_group from browser_cli.commands.extension import extension_group from browser_cli.commands.serve import cmd_serve -from browser_cli.commands.link_serve import cmd_link_serve from browser_cli.commands.auth import auth_group from browser_cli.commands.clients import clients_group from browser_cli.commands.completion import cmd_completion @@ -128,7 +127,6 @@ main.add_command(storage_group) main.add_command(perf_group) main.add_command(extension_group) main.add_command(cmd_serve) -main.add_command(cmd_link_serve) main.add_command(clients_group) main.add_command(cmd_completion) main.add_command(cmd_install) diff --git a/browser_cli/commands/link_serve.py b/browser_cli/commands/link_serve.py deleted file mode 100644 index d3099d2..0000000 --- a/browser_cli/commands/link_serve.py +++ /dev/null @@ -1,163 +0,0 @@ -"""Expose this browser over a ServiceLink HTTP /rpc endpoint. - -This lets the other nodes (picoshare, website) drive the browser through the -shared servicelink envelope, reusing browser-cli's own wire commands verbatim: -a link method like `tabs.list` forwards straight to `send_command_async`. - -It is separate from `serve` (the Ed25519/TCP remote-control daemon); use this -when you want a node in the mesh to call the browser with a bearer token. - -servicelink (and its httpx dependency) is imported lazily inside the command so -that a missing optional dependency never breaks the rest of the CLI. -""" -import asyncio -import hmac -import sys -from pathlib import Path - -import click - -from browser_cli.client.core import send_command_async -from browser_cli.errors import BrowserNotConnected - -# Curated set of browser commands exposed over the mesh. Method name == command. -EXPOSED_COMMANDS = [ - "tabs.list", "tabs.open", "tabs.close", "tabs.active", "tabs.query", - "nav.open", "nav.reload", "nav.back", "nav.forward", - "dom.query", "dom.text", "dom.attr", "dom.exists", "dom.click", - "extract.links", "extract.images", "extract.text", "extract.markdown", "extract.html", - "page.info", - "session.save", "session.load", "session.list", -] - -def _import_servicelink(): - """Import servicelink lazily; the flat submodule sits at the repo root.""" - repo_root = Path(__file__).resolve().parents[2] - if str(repo_root) not in sys.path: - sys.path.insert(0, str(repo_root)) - try: - import servicelink - return servicelink - except ImportError as exc: - raise click.ClickException( - "servicelink could not be imported. Initialise the submodule " - "(`git submodule update --init`) and ensure httpx is installed." - ) from exc - -def _build_router(sl, profile): - router = sl.Router("browser-cli") - - def make_handler(command): - async def handler(params, ctx): - try: - return await send_command_async(command, params or None, profile=profile) - except BrowserNotConnected as exc: - raise sl.Unavailable(f"browser not connected: {exc}") - except (RuntimeError, ConnectionError) as exc: - raise sl.LinkError(str(exc)) - return handler - - for command in EXPOSED_COMMANDS: - router.register(command, make_handler(command)) - return router - -def _make_verifier(sl, token): - if not token: - return None - - async def verify(authorization, request): - presented = (authorization or "").split(" ", 1)[-1].strip() - # Constant-time compare so a wrong token can't be timed out character by character. - if not presented or not hmac.compare_digest(presented, token): - raise sl.Unauthorized("invalid or missing token") - return sl.Principal(subject="mesh", scopes=frozenset({"all", "mesh"})) - - return verify - -def _http_response(status, body, content_type): - head = ( - f"HTTP/1.1 {status}\r\n" - f"Content-Type: {content_type}\r\n" - f"Content-Length: {len(body)}\r\n" - "Connection: close\r\n\r\n" - ).encode("latin-1") - return head + body - -async def _handle_connection(sl, router, verify, reader, writer): - try: - request_line = await reader.readline() - if not request_line: - return - headers = {} - while True: - line = await reader.readline() - if line in (b"\r\n", b"\n", b""): - break - key, _, value = line.decode("latin-1").partition(":") - headers[key.strip().lower()] = value.strip() - length = int(headers.get("content-length", "0") or "0") - body = await reader.readexactly(length) if length else b"" - - if not request_line.upper().startswith(b"POST"): - writer.write(_http_response(405, b'{"error":"only POST /rpc is supported"}', "application/json")) - else: - status, payload, content_type = await sl.handle_envelope( - router, - body, - authorization=headers.get("authorization"), - verify=verify, - content_type=headers.get("content-type", "application/json"), - ) - writer.write(_http_response(status, payload, content_type)) - await writer.drain() - except Exception: # noqa: BLE001 - never let one connection kill the server - pass - finally: - writer.close() - -async def _serve(sl, host, port, profile, token): - router = _build_router(sl, profile) - verify = _make_verifier(sl, token) - server = await asyncio.start_server( - lambda r, w: _handle_connection(sl, router, verify, r, w), host, port - ) - click.echo(f"servicelink browser node listening on http://{host}:{port}/rpc") - async with server: - await server.serve_forever() - -_LOOPBACK_HOSTS = {"127.0.0.1", "::1", "localhost"} - -@click.command("link-serve") -@click.option("--host", default="127.0.0.1", show_default=True, help="Address to bind.") -@click.option("--port", default=8770, show_default=True, type=int, help="HTTP port for /rpc.") -@click.option("--token", default=None, metavar="SECRET", - help="Shared bearer token required from callers (sent as 'Authorization: Bearer ...').") -@click.option("--insecure", is_flag=True, default=False, - help="Run with NO token. Grants full browser control to anyone who can reach the port.") -@click.pass_context -def cmd_link_serve(ctx, host, port, token, insecure): - """Serve this browser to the ServiceLink mesh over HTTP /rpc. - - Exposes the running browser (open/scrape pages, read storage), so - a token is required by default. Bind to loopback and keep the port off the - public network. - """ - if not token and not insecure: - raise click.ClickException( - "Refusing to start without --token (this endpoint can control your browser " - "and read page/storage data). Pass --insecure to override on a trusted host." - ) - if host not in _LOOPBACK_HOSTS: - click.echo( - f"WARNING: binding to {host} (not loopback). Anyone who can reach this " - "address may control the browser; ensure it is firewalled.", - err=True, - ) - if insecure and not token: - click.echo("WARNING: --insecure set; the endpoint is UNAUTHENTICATED.", err=True) - sl = _import_servicelink() - profile = ctx.obj.get("browser") if ctx.obj else None - try: - asyncio.run(_serve(sl, host, port, profile, token)) - except KeyboardInterrupt: - pass diff --git a/browser_cli/commands/serve.py b/browser_cli/commands/serve.py index 64611dc..268a1b5 100644 --- a/browser_cli/commands/serve.py +++ b/browser_cli/commands/serve.py @@ -45,32 +45,9 @@ __all__ = [ default=False, help="Disable response compression / msgpack even for clients that support it.", ) -@click.option( - "--rpc", - is_flag=True, - default=False, - help="Also expose a ServiceLink HTTP /rpc endpoint (for mesh nodes) in the same process.", -) -@click.option("--rpc-port", default=8770, show_default=True, type=int, help="Port for the /rpc endpoint (with --rpc).") -@click.option( - "--rpc-token", - default=None, - metavar="SECRET", - help="Bearer token required on /rpc (with --rpc).", -) -@click.option( - "--rpc-insecure", - is_flag=True, - default=False, - help="Allow --rpc with no token (DANGEROUS: full browser control to anyone who can reach the port).", -) @click.pass_context -def cmd_serve(ctx, host, port, no_auth, auth_keys_file, no_compress, rpc, rpc_port, rpc_token, rpc_insecure): - """Expose this browser over TCP so remote hosts can control it. - - With --rpc, additionally serve the ServiceLink mesh over HTTP /rpc on - --rpc-port, so the native TCP protocol and the node mesh share one daemon. - """ +def cmd_serve(ctx, host, port, no_auth, auth_keys_file, no_compress): + """Expose this browser over TCP so remote hosts can control it.""" profile = ctx.obj.get("browser") if ctx.obj else None compress = not no_compress @@ -84,40 +61,16 @@ def cmd_serve(ctx, host, port, no_auth, auth_keys_file, no_compress, rpc, rpc_po if auth_keys_path is False: sys.exit(1) - if rpc and not rpc_token and not rpc_insecure: - console.print( - "[red]Error:[/red] --rpc requires --rpc-token (this endpoint can control your " - "browser and read page/storage data). Use --rpc-insecure to override on a trusted host." - ) - sys.exit(1) - _print_startup(host, port, profile, auth_keys_path, compress) - if rpc: - console.print(f" Mesh: [green]ServiceLink HTTP[/green] [cyan]{host}:{rpc_port}/rpc[/cyan]") - if not rpc_token: - console.print("[yellow] /rpc auth disabled (--rpc-insecure)[/yellow]") try: - if rpc: - asyncio.run(_serve_with_rpc(host, port, profile, auth_keys_path, compress, rpc_port, rpc_token)) - else: - asyncio.run(_serve_async(host, port, profile, auth_keys_path, compress)) + asyncio.run(_serve_async(host, port, profile, auth_keys_path, compress)) except OSError as e: console.print(f"[red]Cannot bind to {host}:{port}:[/red] {e}") sys.exit(1) except KeyboardInterrupt: console.print("[yellow]Stopped.[/yellow]") -async def _serve_with_rpc(host, port, profile, auth_keys_path, compress, rpc_port, rpc_token): - """Run the native TCP server and the ServiceLink HTTP /rpc server together.""" - from browser_cli.commands import link_serve - - sl = link_serve._import_servicelink() - await asyncio.gather( - _serve_async(host, port, profile, auth_keys_path, compress), - link_serve._serve(sl, host, rpc_port, profile, rpc_token), - ) - def _resolve_auth_keys_path(auth_keys_file: str | None, no_auth: bool) -> Path | None | bool: if auth_keys_file: from browser_cli.auth import load_authorized_keys diff --git a/extension/icon.svg b/extension/icon.svg index ba5f28d..76705f9 100644 --- a/extension/icon.svg +++ b/extension/icon.svg @@ -1,28 +1,31 @@ browser-cli icon - + - + - - - - - - + + + - - - + + + + + - - - + + + + + + + diff --git a/extension/icons/icon-128.png b/extension/icons/icon-128.png index 592605807341af49408547c63c10e1fb21d67cae..b527d38757fe7878b2b51ba69a476542fc2af699 100644 GIT binary patch literal 5845 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 diff --git a/extension/icons/icon-16.png b/extension/icons/icon-16.png index 30ef6b935549c44ffbd2e69ec4756bf84179e42e..17cc38b54205d925895e7378921d90ab15bdc407 100644 GIT binary patch delta 562 zcmV-20?qxy3%~@B8Gi-<001BJ|6u?C00v@9M??Vs0RI60puMM)0005?NklH8Jpg^c-5w%Kb z8Pu*Iq(RWM88m7T$ES_Gnbetk?rky7L`<#gz~P?F`ObIFg@1n?R=pUUoNNylKaK^f z#X+OQM+B4xkysBY{_~eheM$f>{{{%w*3+h{xOr*_cUK!qG^O$;Ta{`?gXMC2Tv13=ZjEuM(;&6@HMny34k!Bu zKpTRr&72!ys@?FTT&8nsp~;|e16wGdgtybvTpu4N>lp0*ZaNO_$BjBznH>4o??4H* zww9X!p6g6lS~nCAA3H+#o=&`#b%L>{R35#=EG}V*A(@Q5Xj;Kb`NMrmSXnNlQQCia z?FKvBS}Q3BMdBM<#P_B^Q}7ar39rk!%6}6-0VneVrEoHgQ2+n{07*qoM6N<$g72&e ATmS$7 literal 1475 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 diff --git a/extension/icons/icon-48.png b/extension/icons/icon-48.png index 020128e809c392efc6584563c43b81142f538b6a..c4f2df34e07babac84010a7b0df464a96102be4f 100644 GIT binary patch delta 1934 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", "rich>=13", "msgpack>=1", - "httpx>=0.28", ] [project.optional-dependencies] diff --git a/servicelink b/servicelink deleted file mode 160000 index 7b9a51e..0000000 --- a/servicelink +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7b9a51ee525862a6e5eb99732a20aa1927d3ae62 diff --git a/uv.lock b/uv.lock index 92b4ee4..0c90d1c 100644 --- a/uv.lock +++ b/uv.lock @@ -2,28 +2,13 @@ version = 1 revision = 3 requires-python = ">=3.10" -[[package]] -name = "anyio" -version = "4.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, -] - [[package]] name = "browser-cli" -version = "0.14.1" +version = "0.14.2" source = { editable = "." } dependencies = [ { name = "click" }, { name = "cryptography" }, - { name = "httpx" }, { name = "msgpack" }, { name = "rich" }, ] @@ -44,7 +29,6 @@ dev = [ requires-dist = [ { name = "click", specifier = ">=8" }, { name = "cryptography", specifier = ">=48" }, - { name = "httpx", specifier = ">=0.28" }, { name = "msgpack", specifier = ">=1" }, { name = "rich", specifier = ">=13" }, { name = "zstandard", marker = "extra == 'fast'", specifier = ">=0.22" }, @@ -58,15 +42,6 @@ dev = [ { name = "zstandard", specifier = ">=0.22" }, ] -[[package]] -name = "certifi" -version = "2026.5.20" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, -] - [[package]] name = "cffi" version = "2.0.0" @@ -357,52 +332,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[[package]] -name = "idna" -version = "3.18" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0"