@sema-agent/connector-browser 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +229 -0
  3. package/package.json +3 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ All notable changes to this package are documented here. Released sections are
4
4
  frozen: once a version is published its section is never edited again; new text
5
5
  goes into the next version's section.
6
6
 
7
+ ## 0.1.2 (2026-10-21)
8
+
9
+ ### Documentation
10
+
11
+ - The connector's document ships inside the package as `README.md`, a byte copy of
12
+ the repository's `docs/CONNECTORS/<name>.md` made by the build (CN-056): a person
13
+ who installed the package reads its setup, what runs, the recommended policy and
14
+ the known limits without the repository.
15
+ - The recommended policy names four tools to leave out (`browser_run_code_unsafe`,
16
+ `browser_evaluate`, `browser_network_request`, `browser_network_requests`) and
17
+ `browser_file_upload` as a fifth a deployment may add back; the example list holds
18
+ 20 names, 21 with it.
19
+
7
20
  ## 0.1.1 (2026-10-16)
8
21
 
9
22
  ### Documentation
package/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # Browser connector
2
+
3
+ `@sema-agent/connector-browser` (CN-027) gives the engine a web browser. It runs the
4
+ [Playwright MCP server](https://github.com/microsoft/playwright-mcp) (`@playwright/mcp`) unmodified behind the
5
+ `sema-connector` launcher, driving a pinned Chrome for Testing, at the versions and sha256 values its `binaries.lock.json`
6
+ pins. The launcher's stages (scope, normalize, status, trace, the withholding of secret values) apply to it as to every
7
+ connector; see [`ARCHITECTURE.md`](../ARCHITECTURE.md). Its bin is `sema-connector-browser`; its subcommands are the shared
8
+ CLI kit's (CN-039) around the shared install of a pinned upstream program (CN-027) and this package's `auth`, and read the
9
+ configuration directory from `CONFIG_LOCAL_DIR` like every connector's
10
+ ([`INTEGRATION-SERVER.md`](../INTEGRATION-SERVER.md)).
11
+
12
+ The browser is headless and runs in a fresh, in-memory profile at every start; what it knows of a login is the login
13
+ state `auth` stored (see "The login state"). Chrome for Testing is pinned for `darwin-arm64` and `linux-x64` only.
14
+
15
+ ## Setting it up
16
+
17
+ ```sh
18
+ export CONFIG_LOCAL_DIR=/absolute/path/to/the/engine/configuration
19
+ sema-connector-browser install
20
+ echo '{}' | sema-connector-browser auth # a clean profile
21
+ sema-connector-browser mount
22
+ sema-connector-browser status
23
+ ```
24
+
25
+ On Linux the connector keeps Chromium's own sandbox on (`--sandbox`, never `--no-sandbox`), which needs user
26
+ namespaces: in a container that does not allow them (Docker's default seccomp profile, for one) the first
27
+ `browser_navigate` answers `Chromium sandboxing failed!` and nothing is browsed. Run the connector on a host or in a
28
+ container that allows user namespaces (KL-18 in the repository's `docs/KNOWN-LIMITS.md`); the connector never turns
29
+ the sandbox off.
30
+
31
+ Then restart the engine. To start from a login instead of a clean profile, save a Playwright storage state (for example
32
+ with Playwright's `context.storageState({ path })` after logging in) to a file only you can read, and give `auth` its path:
33
+ `echo '{"storageStateFile": "/absolute/path/state.json"}' | sema-connector-browser auth`. Delete the file afterwards; the
34
+ state is a secret (it holds the session cookies).
35
+
36
+ ## The pinned build
37
+
38
+ `binaries.lock.json` is the one place that names the build, in the shape "Pinned upstream programs" in
39
+ [`ARCHITECTURE.md`](../ARCHITECTURE.md) describes: the key `playwright-mcp`, version 0.0.83, and five archives.
40
+
41
+ | Archive | Platform | Source | Unpacked as |
42
+ |---|---|---|---|
43
+ | `playwright-mcp-0.0.83.tgz` | `*` | `https://registry.npmjs.org/@playwright/mcp/-/mcp-{version}.tgz` | `node_modules/@playwright/mcp` |
44
+ | `playwright-1.64.0-alpha-1790635538000.tgz` | `*` | `https://registry.npmjs.org/playwright/-/{file}` | `node_modules/playwright` |
45
+ | `playwright-core-1.64.0-alpha-1790635538000.tgz` | `*` | `https://registry.npmjs.org/playwright-core/-/{file}` | `node_modules/playwright-core` |
46
+ | `chrome-mac-arm64.zip` | `darwin-arm64` | `https://cdn.playwright.dev/builds/cft/155.0.8059.12/mac-arm64/{file}` | `chrome` |
47
+ | `chrome-linux64.zip` | `linux-x64` | `https://cdn.playwright.dev/builds/cft/155.0.8059.12/linux64/{file}` | `chrome` |
48
+
49
+ The three npm tarballs are the whole run-time closure of `@playwright/mcp` 0.0.83 (its lock file lists no other
50
+ package), each pinned with the sha256 of the tarball whose `sha512` integrity the registry gives. Chrome for Testing
51
+ 155.0.8059.12 is the build that `playwright-core` 1.64.0-alpha-1790635538000 names (Playwright's `chromium` 1247), fetched
52
+ from the host Playwright itself downloads it from. The program is the Node.js script
53
+ `node_modules/@playwright/mcp/cli.js` (`{"node": ...}`): it runs on the Node.js that runs the connector, and Node.js
54
+ finds `playwright` and `playwright-core` next to it. npm is never run.
55
+
56
+ ## install
57
+
58
+ `install` is the shared install of a pinned upstream program (see "Pinned upstream programs" in
59
+ [`ARCHITECTURE.md`](../ARCHITECTURE.md)): it fetches the four archives of this platform, compares each with its sha256
60
+ and checks what it gives before anything is unpacked, then unpacks each below
61
+ `<configuration directory>/connectors/browser/bin/` (0700) and keeps the archives there. About 580 MB on `darwin-arm64`.
62
+ When that directory already holds the pinned build, `install` changes nothing. On a platform the lock does not pin it
63
+ refuses (`binaries.lock.json pins no playwright-mcp build for <platform>`).
64
+
65
+ ## auth
66
+
67
+ `auth` reads exactly one line from standard input (never from an argument), a JSON object with at most one string member:
68
+
69
+ ```json
70
+ {}
71
+ {"storageStateFile": "<absolute path>"}
72
+ ```
73
+
74
+ - `{}` stores a clean profile, `{"cookies":[],"origins":[]}`. Empty input is refused rather than read as `{}`: a pipe
75
+ whose producer failed must not replace a stored login with an empty one.
76
+ - `storageStateFile` names a Playwright storage state file by its absolute path: at most 4 MiB of UTF-8 JSON holding one
77
+ object with exactly the keys `cookies` and `origins`, each a list. Its text is stored as it is.
78
+ - Any other key, a key given twice, a value that is not a string, or a file outside the rule is refused, and a refusal
79
+ never repeats what was read.
80
+
81
+ The state is stored through the secrets backend as the handle `BROWSER_STORAGE_STATE`: a 0600 file in
82
+ `<configuration directory>/connectors/browser/` (0700), a secret (no metadata). The write holds the lock of the
83
+ connector's directory as for every connector (see "Secrets" in [`ARCHITECTURE.md`](../ARCHITECTURE.md)): a connector
84
+ started while `auth` runs gets the old state or the new one, and an `auth` that fails leaves the old one.
85
+
86
+ ## mount and status
87
+
88
+ `mount` writes the connector's standalone catalog entry through the shared `mount` function, and `status` prints the
89
+ status the launcher serves, as for every connector. Before `auth` there is no handle, so the launcher does not start the
90
+ connector and answers itself (see "Parked session" in [`ARCHITECTURE.md`](../ARCHITECTURE.md)): `status` prints
91
+ `{"state":"unauthorized","tools":[]}`. After `auth` (a clean profile included) the status is `authorized` and lists the
92
+ 25 tools of playwright-mcp; reading them starts playwright-mcp but not the browser, which starts at the first tool that
93
+ needs a page.
94
+
95
+ ## What runs
96
+
97
+ With no arguments, `sema-connector-browser` is the connector process the launcher starts. It refuses to start (exit 1,
98
+ with a line on stderr naming what to do) on the shared start check ("Pinned upstream programs": without `CONNECTOR_DIR`,
99
+ without the `BROWSER_STORAGE_STATE` handle, when its per-launch copy is not a regular file in `CONNECTOR_DIR`, when `bin/`
100
+ does not hold the pinned build), when the stored state breaks the rule of "auth", and when Chrome for Testing is not in
101
+ `bin/chrome`. Otherwise it makes the launch's output directory (see "The output directory"), makes it the working
102
+ directory, and starts `node_modules/@playwright/mcp/cli.js` on its own Node.js as its child on the same standard input,
103
+ output and error, so every MCP message passes between the launcher and playwright-mcp untouched; it ends as playwright-mcp ends, with its exit code or by the signal that ended it, and passes its stop
104
+ signals on. playwright-mcp closes the browser when its input ends or it gets `SIGTERM` (measured well under a second,
105
+ inside the launcher's teardown steps); killed with `SIGKILL` before that, it leaves Chromium running (KL-22).
106
+
107
+ playwright-mcp gets the arguments of "Security baseline" and the environment the launcher gave the connector without
108
+ `CONNECTOR_DIR`, `BROWSER_STORAGE_STATE_FILE` and every `PLAYWRIGHT_MCP_*` variable (the variable twins of its options),
109
+ so its options are those arguments whoever starts the connector.
110
+
111
+ ## Security baseline
112
+
113
+ The arguments are one table in `src/upstream.ts`; none comes from the environment, standard input or a file.
114
+
115
+ | Argument | Why |
116
+ |---|---|
117
+ | `--headless` | no window; the browser is driven only through the tools. |
118
+ | `--isolated` | the profile lives in memory and is gone when the browser closes; nothing persists between starts but the stored login state. |
119
+ | `--browser chromium` | Chrome for Testing, the pinned build. |
120
+ | `--sandbox` | the Chromium sandbox on: playwright-mcp leaves it off on Linux for Chrome for Testing unless asked (KL-18). |
121
+ | `--block-service-workers` | a page cannot install a service worker that outlives it or intercepts its requests. |
122
+ | `--no-webmcp` | tools a page registers are not offered to the engine: a page cannot add tools. |
123
+ | `--timeout-navigation 60000` | the navigation timeout, stated rather than defaulted. |
124
+ | `--output-max-size 67108864` | the output directory holds at most 64 MiB; older files are evicted. |
125
+ | `--executable-path <bin>/chrome/...` | the pinned Chrome for Testing in the bin directory; playwright-mcp never looks for or downloads a browser. |
126
+ | `--output-dir <launch directory>/out` | screenshots, snapshots and other files land in the launch's own directory and go with it. |
127
+ | `--storage-state <per-launch copy>` | the login state, read when the browser context is created. |
128
+
129
+ Never given: `--no-sandbox`, `--caps` (none of the opt-in capabilities `vision`, `pdf`, `devtools`, `config`, `network`,
130
+ `storage` is on), `--user-data-dir`, `--save-session`, `--secrets`, `--allow-unrestricted-file-access`, `--proxy-server`,
131
+ `--port` and `--host` (MCP runs on standard input and output only), `--init-script`, `--init-page`, `--config`,
132
+ `--extension`, `--cdp-endpoint`, `--allowed-origins` and `--blocked-origins` (playwright-mcp itself says they are not a
133
+ security boundary and do not apply to redirects; the connector does not offer them as one).
134
+
135
+ ## The login state
136
+
137
+ The state is a secret handle: the launcher reads it before the connector starts and hands the connector a per-launch copy
138
+ (0600, inside the connector's directory, removed when the launcher ends), whose path the connector gives playwright-mcp
139
+ with `--storage-state`. playwright-mcp loads its cookies and local storage into each new browser context. The launcher
140
+ withholds the state's text as a whole from everything it forwards; a cookie value a page's script can read (one not
141
+ marked `HttpOnly`) reaches the engine like any page content, through `browser_evaluate` and the other tools that read the
142
+ page. A reply that carries the stored state's text as a whole, the clean profile's `{"cookies":[],"origins":[]}`
143
+ included, is withheld like any secret value (answered with `connector.internal`).
144
+
145
+ Logins made during a session are not written back: the profile is in memory (`--isolated`), so they end with the browser
146
+ (KL-15). To keep a login, store a new state with `auth`.
147
+
148
+ ## The output directory
149
+
150
+ Every launch has an output directory of its own: `out` (0700) in the launch's directory, the 0700 directory the launcher
151
+ makes inside the connector's directory for the per-launch copy of the login state and removes when the launch ends. It
152
+ holds what playwright-mcp writes (screenshots, page snapshots, console logs, downloads, at most 64 MiB) and goes with the
153
+ launch, so nothing a session wrote outlives it, and two launches at once (two engine sessions, or `status` while a
154
+ session runs) never touch each other's files. It is also playwright-mcp's working directory, and playwright-mcp confines
155
+ the files its tools read and write (`browser_file_upload`, a screenshot's file name, `browser_run_code_unsafe`'s
156
+ `filename`) to its output directory and its workspace, which is the working directory unless the MCP client names a root
157
+ (then its first root). `file:` URLs are not opened.
158
+
159
+ ## Tools
160
+
161
+ The connector offers every tool playwright-mcp lists; which of them a caller may use is the launcher's scope stage, and
162
+ standalone grants `tool:*` unless the configuration directory carries a local per-tool policy (CN-054, `ARCHITECTURE.md`,
163
+ "Stages on the launcher hop"). The 25 tools, with what each can reach:
164
+
165
+ | Tool | Reach |
166
+ |---|---|
167
+ | `browser_run_code_unsafe` | **Runs arbitrary JavaScript in the playwright-mcp process** (playwright-mcp calls it RCE-equivalent): it can read and write every file the user can, the tokens of the other connectors in the configuration directory included, start processes and connect anywhere. |
168
+ | `browser_evaluate` | Runs JavaScript in the page, with the page's access: its DOM, storage and script-readable cookies, requests as the page. |
169
+ | `browser_network_requests`, `browser_network_request` | The page's requests and responses, headers and bodies included (session cookies and authorization headers of a logged-in site). |
170
+ | `browser_file_upload`, `browser_drop` | Give files to a page: files in the output directory or the workspace (see "The output directory"). |
171
+ | `browser_navigate`, `browser_navigate_back`, `browser_tabs` | Open any `http` or `https` URL, as the logged-in user where the state has a login. |
172
+ | `browser_click`, `browser_type`, `browser_press_key`, `browser_fill_form`, `browser_select_option`, `browser_hover`, `browser_drag`, `browser_handle_dialog` | Act on the page as the user (submit forms, confirm dialogs, buy, post, delete). |
173
+ | `browser_snapshot`, `browser_find`, `browser_take_screenshot`, `browser_console_messages` | Read the page (its text, its look, its console); snapshots and screenshots are also written to the output directory. |
174
+ | `browser_resize`, `browser_emulate_media`, `browser_wait_for`, `browser_close` | Change the viewport or media features, wait, close the browser. |
175
+
176
+ ### A recommended policy
177
+
178
+ Mounting this connector grants every tool above by default (`tool:*`): nothing in this package writes a policy for
179
+ it. An operator who wants to keep the reach of `browser_run_code_unsafe`, `browser_evaluate`,
180
+ `browser_network_request` and `browser_network_requests` off, while still using the browser for everything else, writes `connectors/browser/tools.json` in the configuration directory by hand
181
+ (`ARCHITECTURE.md`, "Stages on the launcher hop"; no CLI subcommand writes this file):
182
+
183
+ ```json
184
+ {
185
+ "allow": [
186
+ "browser_navigate", "browser_navigate_back", "browser_tabs",
187
+ "browser_click", "browser_type", "browser_press_key", "browser_fill_form", "browser_select_option",
188
+ "browser_hover", "browser_drag", "browser_handle_dialog",
189
+ "browser_snapshot", "browser_find", "browser_take_screenshot", "browser_console_messages",
190
+ "browser_resize", "browser_emulate_media", "browser_wait_for", "browser_close", "browser_drop"
191
+ ]
192
+ }
193
+ ```
194
+
195
+ The example leaves a fifth tool out as well, `browser_file_upload`: a deployment that needs it adds it back (it
196
+ reaches only the output directory and the workspace, not arbitrary files), so the list above holds 20 names and a
197
+ deployment that keeps `browser_file_upload` lists 21. A tool left out of `allow` fails
198
+ `tools/call` with `connector.forbidden_scope` and is absent from `tools/list` and the status resource's `tools`;
199
+ nothing about the connector itself changes.
200
+
201
+ ## Outbound connections
202
+
203
+ The browser connects to whatever the engine asks it to open: that is what it is for. The standalone form has no egress
204
+ proxy (M3), and the connector gives playwright-mcp none. The negative-control gate (`run-browser-negctl-test`) checks
205
+ what else connects: playwright-mcp itself connects nowhere (every Node.js process runs under the gate's egress probe and,
206
+ on macOS, a sandbox that refuses every connection but the gate's own loopback page). Chrome for Testing 155 does contact
207
+ Google on its own when it starts, although Playwright passes switches meant to keep it quiet: the gate's proxied run
208
+ names `accounts.google.com`, `update.googleapis.com`, `www.google.com` and `android.clients.google.com`, and Chromium
209
+ also connects a UDP socket to `[2001:4860:4860::8888]:443` (KL-19); the gate fails on a host outside google.com and
210
+ googleapis.com. The browser follows the system's proxy settings.
211
+
212
+ playwright-mcp also publishes the running browser for other Playwright tools of the same user: a socket under
213
+ `/tmp/pw-<hash>/browser/` and an entry in Playwright's registry directory (`~/Library/Caches/ms-playwright/b` on macOS,
214
+ `~/.cache/ms-playwright/b` on Linux), removed when the browser closes. A process of the same user can attach to the
215
+ browser there (KL-21).
216
+
217
+ ## Known limits
218
+
219
+ - **Logins made in a session are not kept** (KL-15).
220
+ - **No egress policy in the standalone form** (KL-17).
221
+ - **The Chromium sandbox needs user namespaces on Linux** (KL-18): in a container without them Chromium does not start;
222
+ the connector never turns the sandbox off.
223
+ - **Chrome for Testing contacts Google on its own** (KL-19).
224
+ - **Two platforms**: `darwin-arm64` and `linux-x64` (KL-20).
225
+ - **Other processes of the user can attach to the running browser** (KL-21).
226
+ - **Chromium outlives a playwright-mcp killed with `SIGKILL`** before it closed the browser (KL-22).
227
+ - **Check, then start:** as for every pinned upstream program (see "Pinned upstream programs" in
228
+ [`ARCHITECTURE.md`](../ARCHITECTURE.md)): at each start the trees are compared with their archives by their files'
229
+ names, kinds, sizes, modes and link targets, the program by its content.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/connector-browser",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Browser connector: runs the pinned Playwright MCP server and a pinned Chrome for Testing behind the sema-connector launcher, in an isolated, headless session whose login state is a secret handle.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -15,7 +15,8 @@
15
15
  "dist",
16
16
  "CHANGELOG.md",
17
17
  "binaries.lock.json",
18
- "NOTICE"
18
+ "NOTICE",
19
+ "README.md"
19
20
  ],
20
21
  "scripts": {
21
22
  "build": "tsc -p tsconfig.json"