cdp-mcp 0.1.3 → 0.2.0

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 (50) hide show
  1. package/README.md +67 -31
  2. package/dist/contract.d.ts +11 -0
  3. package/dist/contract.js +11 -0
  4. package/dist/contract.js.map +1 -0
  5. package/dist/index.d.ts +18 -0
  6. package/dist/locator.d.ts +108 -0
  7. package/dist/locator.js +176 -0
  8. package/dist/locator.js.map +1 -0
  9. package/dist/server.d.ts +2 -0
  10. package/dist/server.js +4 -0
  11. package/dist/server.js.map +1 -1
  12. package/dist/session/browser.d.ts +29 -0
  13. package/dist/session/browser.js +17 -2
  14. package/dist/session/browser.js.map +1 -1
  15. package/dist/session/buffers.d.ts +48 -0
  16. package/dist/session/pause.d.ts +21 -0
  17. package/dist/session/state.d.ts +53 -0
  18. package/dist/sourcemap/loader.d.ts +4 -0
  19. package/dist/sourcemap/normalize.d.ts +2 -0
  20. package/dist/sourcemap/store.d.ts +57 -0
  21. package/dist/tools/_locator_runtime.d.ts +31 -0
  22. package/dist/tools/_locator_runtime.js +243 -0
  23. package/dist/tools/_locator_runtime.js.map +1 -0
  24. package/dist/tools/_register.d.ts +2 -0
  25. package/dist/tools/breakpoints.d.ts +4 -0
  26. package/dist/tools/console.d.ts +2 -0
  27. package/dist/tools/dom.d.ts +2 -0
  28. package/dist/tools/dom.js +3 -221
  29. package/dist/tools/dom.js.map +1 -1
  30. package/dist/tools/execution.d.ts +29 -0
  31. package/dist/tools/forms.d.ts +8 -0
  32. package/dist/tools/forms.js +256 -0
  33. package/dist/tools/forms.js.map +1 -0
  34. package/dist/tools/inspect.d.ts +2 -0
  35. package/dist/tools/nav.d.ts +2 -0
  36. package/dist/tools/network.d.ts +2 -0
  37. package/dist/tools/session.d.ts +2 -0
  38. package/dist/tools/session.js +1 -1
  39. package/dist/tools/session.js.map +1 -1
  40. package/dist/tools/source.d.ts +2 -0
  41. package/dist/tools/storage.d.ts +2 -0
  42. package/dist/tools/storage.js +296 -0
  43. package/dist/tools/storage.js.map +1 -0
  44. package/dist/util/browser-resolve.d.ts +19 -0
  45. package/dist/util/errors.d.ts +7 -0
  46. package/dist/util/format.d.ts +20 -0
  47. package/dist/util/log.d.ts +6 -0
  48. package/docs/chromium-sandboxing.md +6 -0
  49. package/docs/local-l3-e2e-setup.md +199 -0
  50. package/package.json +13 -1
@@ -16,7 +16,7 @@ export function registerSessionTools(server) {
16
16
  sandbox: z
17
17
  .boolean()
18
18
  .optional()
19
- .describe("Enable Chromium's sandbox. Default false we add --no-sandbox. On Ubuntu 23.10+ AppArmor restricts the unprivileged user namespace Chromium's sandbox depends on, so unsandboxed launch is the working default for automation. Pass true only on a host with a working sandbox path (AppArmor userns allowance or SUID chrome_sandbox helper)."),
19
+ .describe("Enable Chromium's sandbox. When omitted, defaults from the CDP_SANDBOX env ('true' or '1' enable it; default false we add --no-sandbox). On Ubuntu 23.10+ AppArmor restricts the unprivileged user namespace Chromium's sandbox depends on, so unsandboxed launch is the working default for automation. Pass true only on a host with a working sandbox path (AppArmor userns allowance or SUID chrome_sandbox helper)."),
20
20
  }, async (input) => {
21
21
  return await launchChrome({
22
22
  url: input.url,
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/tools/session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/tools/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerSourceTools(server: McpServer): void;
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerStorageTools(server: McpServer): void;
@@ -0,0 +1,296 @@
1
+ import { z } from "zod";
2
+ import { readFile, writeFile, rename, rm } from "node:fs/promises";
3
+ import { randomBytes } from "node:crypto";
4
+ import { requireSession } from "../session/state.js";
5
+ import { ToolError } from "../util/errors.js";
6
+ import { registerJsonTool } from "./_register.js";
7
+ /**
8
+ * Session-portability tools (issue #12 items 4, 5).
9
+ *
10
+ * Cookies go through CDP `Network.*` (NOT `document.cookie`) so HttpOnly
11
+ * auth/session cookies — the whole reason to resume a session — round-trip.
12
+ * localStorage is read/written via `Runtime.evaluate` (the `Storage.*` domain
13
+ * is out of scope per AGENTS.md). The on-disk shape is the de-facto Playwright
14
+ * `storageState` JSON so a flow can be handed to/from mainstream tooling.
15
+ *
16
+ * v1 localStorage scope is the **current page origin** only: localStorage is
17
+ * origin-partitioned and CDP can only read/write it for a document that is
18
+ * actually on that origin. Cookies (the load-bearing part for auth resume) are
19
+ * captured/restored for all origins. Multi-origin localStorage restore would
20
+ * require navigating to each origin first; origins in the file that don't match
21
+ * the current page are reported in `origins_skipped` rather than silently lost.
22
+ *
23
+ * File read/write mirrors `screenshot path=`: no extra path filter — the
24
+ * operator gate is the same `--allow-remote`/non-loopback rule (README §SSE).
25
+ */
26
+ const SENSITIVE_NAME = /(sess|sid|token|auth|csrf|xsrf|jwt|secret|api[-_]?key)/i;
27
+ const sameSiteSchema = z.enum(["Strict", "Lax", "None"]);
28
+ const storageStateSchema = z.object({
29
+ cookies: z
30
+ .array(z.object({
31
+ name: z.string(),
32
+ value: z.string(),
33
+ domain: z.string(),
34
+ path: z.string(),
35
+ expires: z.number().optional(),
36
+ httpOnly: z.boolean().optional(),
37
+ secure: z.boolean().optional(),
38
+ sameSite: sameSiteSchema.optional(),
39
+ }))
40
+ .default([]),
41
+ origins: z
42
+ .array(z.object({
43
+ origin: z.string(),
44
+ localStorage: z.array(z.object({ name: z.string(), value: z.string() })).default([]),
45
+ }))
46
+ .default([]),
47
+ });
48
+ const cookieParamSchema = z.object({
49
+ name: z.string(),
50
+ value: z.string(),
51
+ url: z.string().optional().describe("Cookie URL (provide this OR domain)."),
52
+ domain: z.string().optional(),
53
+ path: z.string().optional(),
54
+ secure: z.boolean().optional(),
55
+ httpOnly: z.boolean().optional(),
56
+ sameSite: sameSiteSchema.optional(),
57
+ expires: z.number().optional().describe("Expiry as seconds since epoch; omit for a session cookie."),
58
+ });
59
+ /**
60
+ * Write `data` to `dest` at mode 0o600, overwrite-safe and TOCTOU-safe.
61
+ *
62
+ * The storage-state file holds plaintext cookie secrets, so the 0o600
63
+ * postcondition must hold even on a shared, world-writable directory. Two traps:
64
+ * - Node's writeFile `mode` only applies when it *creates* the file, so writing
65
+ * straight to `dest` would leave an existing 0644 file world-readable.
66
+ * - A *predictable* temp path lets an attacker pre-create (or symlink) it at
67
+ * 0644, so the secret gets written through their file and renamed into place.
68
+ *
69
+ * So: create a temp with an **unpredictable** same-directory suffix using
70
+ * exclusive create (`flag: "wx"` ⇒ O_CREAT|O_EXCL — fails on any pre-existing
71
+ * file and refuses to follow a symlink), which makes 0o600 a real creation-time
72
+ * guarantee, then atomically rename it over the destination (same filesystem, so
73
+ * the secret never exists at the destination path in a half-written/0644 state).
74
+ * Retry on the astronomically-unlikely name collision.
75
+ */
76
+ async function writeSecretFileAtomic(dest, data) {
77
+ let lastErr;
78
+ for (let attempt = 0; attempt < 5; attempt++) {
79
+ const tmp = `${dest}.${randomBytes(8).toString("hex")}.tmp`;
80
+ try {
81
+ await writeFile(tmp, data, { flag: "wx", mode: 0o600 });
82
+ }
83
+ catch (e) {
84
+ if (e.code === "EEXIST") {
85
+ lastErr = e;
86
+ continue; // collision (or planted file/symlink) — try a fresh random name
87
+ }
88
+ throw e; // ENOENT (missing parent dir), EACCES, etc.
89
+ }
90
+ try {
91
+ await rename(tmp, dest);
92
+ }
93
+ catch (e) {
94
+ await rm(tmp, { force: true }).catch(() => { });
95
+ throw e;
96
+ }
97
+ return;
98
+ }
99
+ throw lastErr;
100
+ }
101
+ export function registerStorageTools(server) {
102
+ registerJsonTool(server, "export_storage_state", "Save the browser session (all cookies including HttpOnly, plus the current page origin's localStorage) to a JSON file in the de-facto Playwright storageState shape, so a flow can be resumed in a fresh session or handed to other tooling. The file preserves full cookie values (it exists to resume auth) — treat it as a secret. File write is gated by the same operator rule as screenshot path= / --allow-remote.", { path: z.string().describe("Absolute path to write the storageState JSON to.") }, async (input) => {
103
+ const s = requireSession();
104
+ const { cookies } = (await s.client.send("Network.getAllCookies"));
105
+ const probe = await s.client.send("Runtime.evaluate", {
106
+ expression: READ_ORIGIN_AND_LOCALSTORAGE,
107
+ returnByValue: true,
108
+ });
109
+ const probed = (probe.result?.value ?? { origin: "null", localStorage: [] });
110
+ const origins = probed.origin && probed.origin !== "null"
111
+ ? [{ origin: probed.origin, localStorage: probed.localStorage }]
112
+ : [];
113
+ const state = { cookies: cookies.map(cdpCookieToState), origins };
114
+ // The file holds full cookie values (incl. HttpOnly auth/session tokens) —
115
+ // the description says "treat it as a secret". writeSecretFileAtomic writes
116
+ // it 0o600 and overwrite-safe (see the helper for the threat model).
117
+ try {
118
+ await writeSecretFileAtomic(input.path, JSON.stringify(state, null, 2));
119
+ }
120
+ catch (e) {
121
+ if (e.code === "ENOENT") {
122
+ throw new ToolError("not_found", `could not write storage-state file (does the parent directory exist?): ${e.message}`);
123
+ }
124
+ throw e; // EACCES/EISDIR/etc. surface as internal_error via registerJsonTool
125
+ }
126
+ return { saved: input.path, cookies: state.cookies.length, origins: origins.length };
127
+ });
128
+ registerJsonTool(server, "load_storage_state", "Restore a session from a Playwright-shaped storageState JSON file: sets all cookies (no navigation needed), then restores localStorage for the entries whose origin matches the current page. Cookies are added on top of the existing jar (additive, not a clean-context replace), so prefer a fresh session for Playwright-equivalent semantics. Origins that don't match the current page are returned in origins_skipped (restoring them would require navigating there first). File read is gated by the same operator rule as screenshot path= / --allow-remote.", { path: z.string().describe("Absolute path to a storageState JSON file (as written by export_storage_state).") }, async (input) => {
129
+ const s = requireSession();
130
+ let raw;
131
+ try {
132
+ raw = await readFile(input.path, "utf8");
133
+ }
134
+ catch (e) {
135
+ if (e.code === "ENOENT") {
136
+ throw new ToolError("not_found", `could not read storage-state file: ${e.message}`);
137
+ }
138
+ throw e; // EACCES/EISDIR/etc. surface as internal_error rather than masquerading as not_found
139
+ }
140
+ let parsed;
141
+ try {
142
+ parsed = JSON.parse(raw);
143
+ }
144
+ catch {
145
+ throw new ToolError("invalid_arg", "storage-state file is not valid JSON");
146
+ }
147
+ const result = storageStateSchema.safeParse(parsed);
148
+ if (!result.success) {
149
+ throw new ToolError("invalid_arg", `storage-state file is not a valid storageState: ${result.error.message}`);
150
+ }
151
+ const state = result.data;
152
+ if (state.cookies.length > 0) {
153
+ await s.client.send("Network.setCookies", { cookies: state.cookies.map(stateCookieToCdp) });
154
+ }
155
+ const restore = await s.client.send("Runtime.evaluate", {
156
+ expression: restoreLocalStorageExpr(state.origins),
157
+ returnByValue: true,
158
+ });
159
+ const restored = (restore.result?.value ?? { restored: [], skipped: state.origins.map((o) => o.origin) });
160
+ return {
161
+ loaded: input.path,
162
+ cookies: state.cookies.length,
163
+ origins_restored: restored.restored,
164
+ origins_skipped: restored.skipped,
165
+ };
166
+ });
167
+ registerJsonTool(server, "get_cookies", "List cookies (via CDP, so HttpOnly cookies are included) with their flags. For safe printing, the VALUE of likely session/auth cookies is redacted (httpOnly cookies, or names matching sess/sid/token/auth/csrf/jwt/secret/api-key); only value_length is shown for those. Full values are obtainable only through export_storage_state.", {
168
+ urls: z
169
+ .array(z.string())
170
+ .optional()
171
+ .describe("Restrict to cookies visible to these URLs; omit for all cookies in the browser."),
172
+ }, async (input) => {
173
+ const s = requireSession();
174
+ const res = (input.urls && input.urls.length > 0
175
+ ? await s.client.send("Network.getCookies", { urls: input.urls })
176
+ : await s.client.send("Network.getAllCookies"));
177
+ return { cookies: res.cookies.map(redactForDisplay) };
178
+ });
179
+ registerJsonTool(server, "set_cookies", "Set one or more cookies in the browser cookie jar via CDP. Each cookie needs a url OR a domain. For restoring a saved session, prefer load_storage_state; this is the lower-level primitive.", { cookies: z.array(cookieParamSchema).describe("Cookies to set.") }, async (input) => {
180
+ const s = requireSession();
181
+ if (input.cookies.length === 0)
182
+ throw new ToolError("missing_arg", "cookies array is empty");
183
+ const missing = input.cookies.find((c) => !c.url && !c.domain);
184
+ if (missing)
185
+ throw new ToolError("missing_arg", `cookie '${missing.name}' needs a url or domain`);
186
+ // Normalize the session-cookie sentinel the same way load_storage_state
187
+ // does, so an exported `expires: -1` piped in here isn't sent as a 1969 expiry.
188
+ await s.client.send("Network.setCookies", {
189
+ cookies: input.cookies.map(withSessionExpiryOmitted),
190
+ });
191
+ return { set: input.cookies.length };
192
+ });
193
+ }
194
+ function cdpCookieToState(c) {
195
+ return {
196
+ name: c.name,
197
+ value: c.value,
198
+ domain: c.domain,
199
+ path: c.path,
200
+ // CDP uses -1 (or a missing field) for session cookies; Playwright too.
201
+ expires: typeof c.expires === "number" ? c.expires : -1,
202
+ httpOnly: !!c.httpOnly,
203
+ secure: !!c.secure,
204
+ // Playwright requires sameSite; CDP may omit it. Lax is the browser default.
205
+ sameSite: c.sameSite ?? "Lax",
206
+ };
207
+ }
208
+ /**
209
+ * CDP's `Network.setCookies` reads `expires` as an absolute epoch-seconds
210
+ * timestamp, so the session-cookie sentinel (`-1`, which `export_storage_state`
211
+ * writes) must be dropped rather than forwarded — otherwise the cookie is set
212
+ * already-expired (1969) and silently rejected. Shared by `load_storage_state`
213
+ * and `set_cookies` so both restore paths normalize identically.
214
+ */
215
+ function withSessionExpiryOmitted(cookie) {
216
+ if (typeof cookie.expires === "number" && cookie.expires >= 0)
217
+ return cookie;
218
+ const copy = { ...cookie };
219
+ delete copy.expires;
220
+ return copy;
221
+ }
222
+ function stateCookieToCdp(c) {
223
+ const param = {
224
+ name: c.name,
225
+ value: c.value,
226
+ domain: c.domain,
227
+ path: c.path,
228
+ secure: !!c.secure,
229
+ httpOnly: !!c.httpOnly,
230
+ expires: c.expires,
231
+ };
232
+ if (c.sameSite)
233
+ param.sameSite = c.sameSite;
234
+ // -1 / undefined means a session cookie — omit expires entirely.
235
+ return withSessionExpiryOmitted(param);
236
+ }
237
+ function redactForDisplay(c) {
238
+ const redacted = !!c.httpOnly || SENSITIVE_NAME.test(c.name);
239
+ const out = {
240
+ name: c.name,
241
+ domain: c.domain,
242
+ path: c.path,
243
+ expires: typeof c.expires === "number" ? c.expires : -1,
244
+ httpOnly: !!c.httpOnly,
245
+ secure: !!c.secure,
246
+ sameSite: c.sameSite ?? null,
247
+ redacted,
248
+ value_length: (c.value ?? "").length,
249
+ };
250
+ if (!redacted)
251
+ out.value = c.value;
252
+ return out;
253
+ }
254
+ // ---------------------------------------------------------------------------
255
+ // In-page expressions
256
+ /** Read the current origin and its localStorage as { origin, localStorage:[{name,value}] }. */
257
+ const READ_ORIGIN_AND_LOCALSTORAGE = String.raw `(() => {
258
+ const items = [];
259
+ try {
260
+ for (let i = 0; i < localStorage.length; i++) {
261
+ const k = localStorage.key(i);
262
+ if (k === null) continue;
263
+ const v = localStorage.getItem(k);
264
+ if (v === null) continue;
265
+ items.push({ name: k, value: v });
266
+ }
267
+ } catch (e) {}
268
+ return { origin: location.origin, localStorage: items };
269
+ })()`;
270
+ /**
271
+ * For each origin in the file whose origin matches the current page, write its
272
+ * localStorage entries; report which origins were restored vs skipped.
273
+ */
274
+ function restoreLocalStorageExpr(origins) {
275
+ return String.raw `(() => {
276
+ const origins = ${JSON.stringify(origins)};
277
+ const current = location.origin;
278
+ const restored = [];
279
+ const skipped = [];
280
+ for (const o of origins) {
281
+ if (o.origin !== current) {
282
+ skipped.push(o.origin);
283
+ continue;
284
+ }
285
+ // If any setItem throws (quota exceeded, storage disabled), report the
286
+ // origin as skipped rather than misleadingly claiming it was restored.
287
+ let ok = true;
288
+ for (const it of o.localStorage) {
289
+ try { localStorage.setItem(it.name, it.value); } catch (e) { ok = false; }
290
+ }
291
+ (ok ? restored : skipped).push(o.origin);
292
+ }
293
+ return { restored, skipped };
294
+ })()`;
295
+ }
296
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/tools/storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export type BrowserChoice = "chromium" | "chrome";
2
+ export interface ResolvedBrowser {
3
+ /** Absolute path to the Chrome/Chromium executable. */
4
+ binaryPath: string;
5
+ /** Which logical browser this binary represents. */
6
+ choice: BrowserChoice;
7
+ /** True when the binary is snap-confined (/snap/bin/* on Linux). Triggers
8
+ * the user-data-dir workaround in launch_chrome. */
9
+ snapConfined: boolean;
10
+ /** Diagnostic — which resolution step produced this. */
11
+ source: "CDP_TEST_BROWSER_PATH" | "which-chromium" | "playwright-cache" | "chrome-launcher-default";
12
+ }
13
+ export declare function getBrowserChoice(): BrowserChoice;
14
+ export declare function resolveBrowser(choice?: BrowserChoice): ResolvedBrowser;
15
+ /** True when the given binary is the snap-marker returned by step 4. */
16
+ export declare function isChromeLauncherDefault(b: ResolvedBrowser): boolean;
17
+ /** Determine the user-data-dir for snap-confined Chromium. Snap confinement
18
+ * rejects /tmp/... paths; only ~/snap/<app>/current/ is writable. */
19
+ export declare function snapUserDataDir(binaryPath: string): string;
@@ -0,0 +1,7 @@
1
+ export declare class ToolError extends Error {
2
+ code: string;
3
+ constructor(code: string, message: string);
4
+ }
5
+ export declare const noSession: () => ToolError;
6
+ export declare const notPaused: () => ToolError;
7
+ export declare const alreadySession: () => ToolError;
@@ -0,0 +1,20 @@
1
+ import type { Protocol } from "devtools-protocol";
2
+ export declare function truncate(s: string, max?: number): string;
3
+ export declare function previewRemoteObject(obj: Protocol.Runtime.RemoteObject): string;
4
+ export declare function describeRemote(obj: Protocol.Runtime.RemoteObject): {
5
+ type: string;
6
+ preview: string;
7
+ objectId?: string;
8
+ };
9
+ export declare function toolText(text: string): {
10
+ content: {
11
+ type: "text";
12
+ text: string;
13
+ }[];
14
+ };
15
+ export declare function toolJson(value: unknown): {
16
+ content: {
17
+ type: "text";
18
+ text: string;
19
+ }[];
20
+ };
@@ -0,0 +1,6 @@
1
+ export declare const log: {
2
+ debug: (msg: string, meta?: Record<string, unknown>) => void;
3
+ info: (msg: string, meta?: Record<string, unknown>) => void;
4
+ warn: (msg: string, meta?: Record<string, unknown>) => void;
5
+ error: (msg: string, meta?: Record<string, unknown>) => void;
6
+ };
@@ -107,6 +107,12 @@ Untrusted browsing:
107
107
  - Add host-level confinement such as AppArmor, a container, VM, or Bubblewrap.
108
108
  - Do not treat `bwrap + --no-sandbox` as equivalent to Chromium sandboxing.
109
109
 
110
+ This outer containment also pairs with the agent-operator threat (prompt-injected
111
+ page content steering the agent into actions or unscoped filesystem writes); see
112
+ the agent-operator threat model and deployment hardening in
113
+ [SECURITY.md](../SECURITY.md). A containerized outer-sandbox run mode that would
114
+ make this contained posture the default is a direction under consideration.
115
+
110
116
  ## Validated hosts
111
117
 
112
118
  Hosts where `sandbox: true` has been verified working against this project, with the supporting posture:
@@ -0,0 +1,199 @@
1
+ # Local L3 e2e setup (Playwright Chromium + AppArmor)
2
+
3
+ **Last updated: 2026-06-09**
4
+
5
+ A step-by-step runbook for getting `npm run test:e2e` (the L3 real-browser
6
+ suite) passing on a local Linux machine **with Chromium's sandbox on**. This is
7
+ the practical companion to [`chromium-sandboxing.md`](./chromium-sandboxing.md);
8
+ read that for the full `--no-sandbox` / `sandbox: true` threat model and the
9
+ validated-hosts table.
10
+
11
+ Assumes Ubuntu (23.10+/24.04). Other distributions may need no host-side work at
12
+ all — see "Other distributions" below.
13
+
14
+ ## Why this is needed on Ubuntu
15
+
16
+ The L3 e2e harness launches Chromium with the sandbox **on** when running
17
+ locally; it only adds `--no-sandbox` when the `CI` env var is set
18
+ (`test/e2e/setup/global.ts`). That is deliberate — locally we want the sandbox
19
+ when the host can provide it.
20
+
21
+ But recent Ubuntu releases ship:
22
+
23
+ ```sh
24
+ kernel.apparmor_restrict_unprivileged_userns = 1
25
+ ```
26
+
27
+ which blocks the unprivileged **user namespace** that Chromium's sandbox needs.
28
+ Playwright-bundled Chromium does not ship a SUID `chrome_sandbox` helper, so on a
29
+ stock Ubuntu host a sandbox-on launch fails before the DevTools port opens:
30
+
31
+ ```text
32
+ zygote_host_impl_linux.cc: No usable sandbox!
33
+ ```
34
+
35
+ From `chrome-launcher` this usually surfaces as a startup port-poll timeout or
36
+ `ECONNREFUSED`.
37
+
38
+ The fix is an AppArmor profile that grants `userns,` to the Playwright Chromium
39
+ binary, giving it a stable named label that is allowed to create the user
40
+ namespace. The steps below install Chromium, confirm the resolver finds it,
41
+ attach the profile, and run the suite.
42
+
43
+ ## 1. Install Playwright Chromium
44
+
45
+ From the repo:
46
+
47
+ ```sh
48
+ npx --yes playwright install chromium
49
+ ```
50
+
51
+ This drops a managed Chromium into the per-user cache:
52
+
53
+ ```text
54
+ ~/.cache/ms-playwright/chromium-<rev>/chrome-linux*/chrome
55
+ ```
56
+
57
+ The leaf directory varies by Playwright version and arch — `chrome-linux` on
58
+ ARM64 and older builds, `chrome-linux64` on x86_64 with the newer
59
+ Chrome-for-Testing layout (the resolver and the AppArmor glob below cover both).
60
+ Install it for **each OS user** that will run the suite — the cache is
61
+ per-`$HOME`.
62
+
63
+ ## 2. Verify the resolver finds it
64
+
65
+ The launcher resolver (`src/util/browser-resolve.ts`) finds Chromium in this
66
+ order: an explicit `CDP_TEST_BROWSER_PATH`, then a system `chromium` on `PATH`,
67
+ then the Playwright cache. After a build, confirm what it picks:
68
+
69
+ ```sh
70
+ npm run build
71
+ node --input-type=module \
72
+ -e "import('./dist/util/browser-resolve.js').then(m => console.log(JSON.stringify(m.resolveBrowser(), null, 2)))"
73
+ ```
74
+
75
+ This runbook targets the **Playwright-cache** binary, so expect
76
+ `source: "playwright-cache"` and a `binaryPath` under `~/.cache/ms-playwright/`:
77
+
78
+ ```json
79
+ {
80
+ "binaryPath": "/home/<user>/.cache/ms-playwright/chromium-<rev>/chrome-linux/chrome",
81
+ "choice": "chromium",
82
+ "snapConfined": false,
83
+ "source": "playwright-cache"
84
+ }
85
+ ```
86
+
87
+ If you have a system Chromium on `PATH` (apt `/usr/bin/chromium`, snap
88
+ `/snap/bin/chromium`), the resolver returns that first with
89
+ `source: "which-chromium"` — that binary is *not* covered by the AppArmor
90
+ profile below (snap brings its own confinement; apt Chromium is a separate
91
+ sandbox story). To exercise the Playwright binary under this profile, point the
92
+ suite at it explicitly:
93
+
94
+ ```sh
95
+ export CDP_TEST_BROWSER_PATH="$(ls -d ~/.cache/ms-playwright/chromium-*/chrome-linux*/chrome | head -1)"
96
+ ```
97
+
98
+ ## 3. Attach the AppArmor profile
99
+
100
+ First confirm the kernel knob is the restrictive default:
101
+
102
+ ```sh
103
+ sysctl kernel.apparmor_restrict_unprivileged_userns # = 1 on stock Ubuntu 24.04
104
+ ```
105
+
106
+ If it is `0` (some hosts turn it off system-wide), Chromium's sandbox already
107
+ works and you can skip to step 5.
108
+
109
+ Create a profile that grants `userns,` to the Playwright Chromium binary path.
110
+ This mirrors the shape of Ubuntu's stock `chrome` / `msedge` / `brave` profiles
111
+ (a named-unconfined profile that opts into user namespaces):
112
+
113
+ ```apparmor
114
+ # /etc/apparmor.d/cdp-mcp-chromium
115
+ abi <abi/4.0>,
116
+ include <tunables/global>
117
+
118
+ profile cdp-mcp-chromium /home/*/.cache/ms-playwright/chromium-*/chrome-linux*/chrome flags=(unconfined) {
119
+ userns,
120
+
121
+ include if exists <local/cdp-mcp-chromium>
122
+ }
123
+ ```
124
+
125
+ The `chrome-linux*` component matches both the `chrome-linux` (ARM64/older) and
126
+ `chrome-linux64` (x86_64 Chrome-for-Testing) layouts — in AppArmor `*` matches
127
+ within a single path segment, so a too-specific `chrome-linux` would silently
128
+ fail to attach on x86_64. The `/home/*/` glob matches any user's Playwright
129
+ cache; if you prefer to scope it to specific accounts, replace `*` with a brace
130
+ list of usernames, e.g. `/home/{alice,bob}/.cache/...`.
131
+
132
+ Load it (profiles in `/etc/apparmor.d/` also auto-load at boot):
133
+
134
+ ```sh
135
+ sudo apparmor_parser -r /etc/apparmor.d/cdp-mcp-chromium
136
+ ```
137
+
138
+ ## 4. Verify the label attaches
139
+
140
+ Launch the bundled Chromium sandbox-on and read its AppArmor label — it must be
141
+ the named profile, not bare `unconfined`:
142
+
143
+ ```sh
144
+ BIN=$(ls -d ~/.cache/ms-playwright/chromium-*/chrome-linux*/chrome | head -1)
145
+ "$BIN" --headless=new --no-startup-window --remote-debugging-port=0 \
146
+ --user-data-dir=$(mktemp -d) about:blank & pid=$!
147
+ sleep 3; cat /proc/$pid/attr/current # -> cdp-mcp-chromium (unconfined)
148
+ kill $pid
149
+ ```
150
+
151
+ If this prints `cdp-mcp-chromium (unconfined)`, the profile is attached. A bare
152
+ `unconfined` means the binary path didn't match the profile's glob — re-check
153
+ the cache path against the profile.
154
+
155
+ ## 5. Run L3
156
+
157
+ ```sh
158
+ npm run test:e2e
159
+ ```
160
+
161
+ With the sandbox on and the profile attached, the suite should pass, e.g.:
162
+
163
+ ```text
164
+ Test Files 10 passed (10)
165
+ Tests 29 passed (29)
166
+ ```
167
+
168
+ ## Fallback (before AppArmor is configured)
169
+
170
+ The L3 harness adds `--no-sandbox` when `CI` is set, so you can run the suite
171
+ without the profile as a lower-security stopgap:
172
+
173
+ ```sh
174
+ env CI=1 npm run test:e2e
175
+ ```
176
+
177
+ This keeps work moving, but the AppArmor profile is the desired long-term
178
+ posture so that plain `npm run test:e2e` exercises sandbox-on Chromium. See
179
+ [`chromium-sandboxing.md`](./chromium-sandboxing.md) for why `--no-sandbox`
180
+ widens the blast radius of a compromised renderer.
181
+
182
+ ## Other distributions
183
+
184
+ This profile work is Ubuntu-specific. Distributions that don't restrict
185
+ unprivileged user namespaces by default (or use SELinux instead of AppArmor,
186
+ e.g. Fedora) generally run sandbox-on Chromium without a host-side profile.
187
+ Validate the actual host before assuming the Ubuntu steps are required — check
188
+ `sysctl kernel.apparmor_restrict_unprivileged_userns` (absent or `0` means no
189
+ AppArmor userns restriction to work around).
190
+
191
+ ## Related
192
+
193
+ - [`docs/chromium-sandboxing.md`](./chromium-sandboxing.md) — the canonical
194
+ `--no-sandbox` / `sandbox: true` threat model, the AppArmor / userns / snap /
195
+ Bubblewrap mechanism map, and the validated-hosts table.
196
+ - [`docs/known-chromium-gaps.md`](./known-chromium-gaps.md) — per-spec
197
+ Chromium-vs-Chrome gaps and host-OS workarounds.
198
+ - [README §L3](../README.md) — browser selection, `CDP_TEST_BROWSER_PATH`, and
199
+ the per-platform support matrix.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-mcp",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Chrome DevTools Protocol MCP server — a TypeScript-aware frontend debugger for AI agents.",
5
5
  "license": "MIT",
6
6
  "author": "Leonard Janke",
@@ -28,11 +28,23 @@
28
28
  "cdp-mcp": "dist/index.js"
29
29
  },
30
30
  "main": "dist/index.js",
31
+ "types": "dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ },
37
+ "./contract": {
38
+ "types": "./dist/contract.d.ts",
39
+ "import": "./dist/contract.js"
40
+ }
41
+ },
31
42
  "files": [
32
43
  "dist",
33
44
  "docs/launchd-service.md",
34
45
  "docs/systemd-service.md",
35
46
  "docs/chromium-sandboxing.md",
47
+ "docs/local-l3-e2e-setup.md",
36
48
  "docs/known-chromium-gaps.md"
37
49
  ],
38
50
  "scripts": {