Skip to content

release: prepare npm v0.5.0 — npm-only, CLI + skill first - #17

Merged
CuriosityOS merged 1 commit into
mainfrom
release/npm-v0.5.0
Jul 15, 2026
Merged

release: prepare npm v0.5.0 — npm-only, CLI + skill first#17
CuriosityOS merged 1 commit into
mainfrom
release/npm-v0.5.0

Conversation

@CuriosityOS

@CuriosityOS CuriosityOS commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Removes Python support entirely — the PyPI package, python/ tree, publish-pypi workflow, and all dual-language machinery (worker byte-sync, policy/prompt parity tests, wheel smoke test). The shared policy vectors are kept as a JS conformance suite (tests/node/policy-vectors.test.mjs).
  • Adds betterwright skill (and skill --claude): prints a paste-ready agent skill (CLI usage + operator guidance) so any agent with a shell tool integrates without MCP, a server, or an SDK — Claude Code, Codex, Hermes, custom agents.
  • Opens network defaults: private network and loopback reachable by default; the cloud-metadata floor remains non-configurable. --block-private-network / --block-loopback and BETTERWRIGHT_BLOCK_* env vars re-harden.
  • Ships the MCP stdio server in the npm package behind the optional @modelcontextprotocol/sdk peer dep.
  • README + SETUP.md restructured to lead with CLI + skill and per-host install instructions.

Verification

  • npm run release:check green (versions, lint, unit, types, package smoke — 37-file tarball installs, imports, typechecks, CLI doctor runs).
  • Full browser suite green: 131 pass / 1 skip with BETTERWRIGHT_REQUIRE_BROWSER=1.
  • E2E on a live browser: one-shot run (~0.8 s including navigation), repl state persistence across snippets, proof screenshot rendered and visually verified.
  • MCP stdio smoke: initialize → tools/list (3 tools) → browser call returns Example Domain.
  • Pi harness e2e: pi --extension ./src/pi-extension.mjs completed a browse+screenshot task.
  • CLI + skill e2e: a real agent given only betterwright skill output in its system prompt drove the browser via bash successfully.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added betterwright skill with optional Claude-compatible output.
    • Added MCP stdio server support with browser, download approval, and diagnostics tools.
    • Added betterwright doctor readiness reporting and improved CLI-first workflows.
  • Changed
    • Private networks and loopback are now allowed by default; metadata and secret-bearing URLs remain blocked.
    • JavaScript credential APIs now use camelCase naming.
  • Removed
    • Python package distribution, documentation, examples, and PyPI publishing.
  • Documentation
    • Updated setup, integration, agent, network policy, and JavaScript guidance.

Make BetterWright npm-only and CLI-first:

- Remove the Python package, PyPI publish workflow, and all dual-language
  machinery (worker sync, policy/prompt parity, wheel checks). The policy
  parity vectors live on as a JS conformance suite.
- Add `betterwright skill` (+ `--claude` for SKILL.md frontmatter): a
  paste-ready agent skill so any agent with a shell tool — Claude Code,
  Codex, Hermes, custom frameworks — drives the browser through the CLI
  with no server or SDK.
- Open network defaults: private network and loopback reachable by default;
  cloud-metadata floor stays. New --block-private-network/--block-loopback
  flags and BETTERWRIGHT_BLOCK_* env vars re-harden.
- Ship the MCP stdio server in the npm package (optional SDK peer dep).
- Restructure README and SETUP.md around CLI + skill as the primary
  integration path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

BetterWright 0.5.0 shifts integration toward the Node CLI, skill output, and MCP stdio server. It updates network defaults and transport enforcement, centralizes worker result handling, removes Python packaging and publishing, and revises documentation, CI, release checks, and package validation.

Changes

Node runtime and MCP integration

Layer / File(s) Summary
Diagnostics, CLI, and MCP entry points
src/doctor.mjs, bin/betterwright.mjs, src/mcp-server.mjs, types/mcp-server.d.ts
Adds shared runtime diagnostics, betterwright skill, betterwright mcp, MCP environment parsing, browser tools, result content conversion, and typed exports.
MCP package contract and validation
package.json, scripts/check-package.mjs, tests/node/mcp-server.test.mjs
Exports the MCP server, declares its optional SDK peer dependency, and validates configuration and packaged imports.

Network enforcement and worker transport

Layer / File(s) Summary
Policy classification and guarded transport
src/policy.mjs, src/guard-proxy.mjs, src/worker.mjs
Allows private and loopback networks by default, expands non-public address classification, and routes browser transport through a policy-checked SOCKS5 proxy.
Worker result and secret handling
src/worker.mjs
Bounds tracked secrets, centralizes result envelopes, updates credential and execution responses, and closes the proxy during shutdown.
Policy conformance and integration coverage
tests/fixtures/policy-vectors.json, tests/node/policy*.mjs, tests/node/browser.test.mjs
Adds conformance vectors and tests for default-open policy behavior, strict blocking, metadata protection, host matching, secret URLs, and browser readiness.

JavaScript-first migration and release updates

Layer / File(s) Summary
Release and CI alignment
.github/workflows/*, package.json, scripts/check-versions.mjs, CHANGELOG.md, CONTRIBUTING.md
Removes worker-sync and PyPI release steps, requires browsers in integration jobs, updates version checks, and documents the 0.5.0 release model.
Integration documentation
README.md, SETUP.md, docs/*, examples/README.md
Rewrites setup, agent, MCP, policy, credentials, attach-mode, and JavaScript API guidance around the CLI-first Node integration path.
Python integration retirement
python/*, examples/python/*, python/tests/*
Removes the Python package implementation, examples, tests, packaging metadata, and Python-specific reference material.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main release changes: npm v0.5.0, removal of Python support, and the new CLI/skill-first focus.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/npm-v0.5.0

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@CuriosityOS
CuriosityOS merged commit a760d8b into main Jul 15, 2026
2 of 3 checks passed
@CuriosityOS
CuriosityOS deleted the release/npm-v0.5.0 branch July 15, 2026 17:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (5)
docs/agent-prompt.md (1)

29-29: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add the bw. prefix for consistency.

For clarity and consistency with the surrounding text and the actual JS API, include the bw. prefix before generateAndFillCredential.

💡 Proposed refactor
-  trusted host-side fill (`bw.fillCredential` / `generateAndFillCredential`);
+  trusted host-side fill (`bw.fillCredential` / `bw.generateAndFillCredential`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/agent-prompt.md` at line 29, Update the API reference in the trusted
host-side fill text to use the fully qualified `bw.generateAndFillCredential`
name, matching the existing `bw.fillCredential` notation and the actual
JavaScript API.
README.md (1)

138-138: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Use npx -y to prevent potential interactive hangs in MCP configurations.

Using npx without the -y flag in an MCP client configuration can cause the server handshake to hang indefinitely if the package is not found globally or if npx attempts to prompt for confirmation. Adding -y ensures robust, non-interactive execution across all environments.

  • README.md#L138-L138: Update the Claude config example to claude mcp add betterwright -- npx -y betterwright mcp.
  • SETUP.md#L176-L176: Update the Claude config example to claude mcp add betterwright -- npx -y betterwright mcp.
  • SETUP.md#L186-L188: Update the JSON arguments array to "args": ["-y", "betterwright", "mcp"].
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 138, Update the Claude MCP command in README.md lines
138-138 and SETUP.md lines 176-176 to invoke npx with the -y flag; also update
the JSON arguments array in SETUP.md lines 186-188 to include "-y" before
"betterwright" and "mcp".
types/mcp-server.d.ts (1)

24-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Type this parameter as RunResult<unknown>.

Accepting unknown permits calls such as contentForResult(null), although the implementation immediately dereferences result fields.

Proposed type correction
-import type { DownloadPolicy, HeadlessMode } from "./common.js";
+import type { DownloadPolicy, HeadlessMode, RunResult } from "./common.js";

-export function contentForResult(result: unknown): Promise<McpContentBlock[]>;
+export function contentForResult(
+  result: RunResult<unknown>,
+): Promise<McpContentBlock[]>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@types/mcp-server.d.ts` around lines 24 - 25, Update the contentForResult
parameter type from unknown to RunResult<unknown> in the MCP server
declarations, preserving its Promise<McpContentBlock[]> return type. Ensure
callers must provide a valid run result rather than nullable or arbitrary
values.
scripts/check-package.mjs (1)

60-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep Python paths forbidden in the npm tarball.

Removing Python support should strengthen, not remove, this package invariant. Retaining python catches accidental publication of retired sources in future releases.

-    .filter((name) => /(^|\/)(node_modules|tests|artifacts|\.betterwright)(\/|$)/.test(name));
+    .filter((name) => /(^|\/)(node_modules|tests|artifacts|python|\.betterwright)(\/|$)/.test(name));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-package.mjs` at line 60, Update the exclusion filter in the
package-checking logic to retain python as a forbidden path alongside
node_modules, tests, artifacts, and .betterwright. Preserve the existing
path-boundary matching behavior so Python directories or files anywhere in the
npm tarball are still detected.
tests/fixtures/policy-vectors.json (1)

605-609: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Suppress the known test-vector secret finding narrowly.

Checkov flags this AWS-shaped test value as CKV_SECRET_2. Add an exact fixture-level allowlist/suppression rather than weakening repository-wide secret scanning.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/fixtures/policy-vectors.json` around lines 605 - 609, Add a narrowly
scoped, fixture-level suppression for the AWS-shaped value in the “aws access
key in query” test vector so Checkov ignores only this known test secret.
Preserve repository-wide secret scanning and avoid broad patterns or global
configuration changes.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/credentials.md`:
- Around line 70-95: Update the credential-storage link text in
docs/getting-started.md to describe the vault as a pluggable backend used by
trusted host-side code, replacing the stale “encrypted storage” wording while
preserving the existing link destination.

In `@docs/getting-started.md`:
- Around line 12-17: Update the getting-started instructions so the Node.js
installation warning appears before the npx betterwright doctor command. Tell
users to install Node.js first, then rerun setup followed by doctor; keep the
existing CloakBrowser recovery guidance unchanged.

In `@docs/network-policy.md`:
- Around line 60-66: Update the allowHosts description in the network policy
table to state that matching hosts are allowed unless blocked by the metadata
floor. Keep the existing exact/parent-domain matching and port-pinning behavior,
and preserve the documented evaluation order.
- Around line 24-28: Update the network policy image caption or alt text near
the private-network description to state that private networks and loopback are
allowed by default, removing the stale claim that the policy blocks private
addresses.

In `@examples/README.md`:
- Around line 3-8: Update the examples README command block to include the
required browser setup step by adding npx betterwright setup between npm install
betterwright and node examples/javascript/quickstart.mjs, keeping the
prerequisite text consistent.

In `@package.json`:
- Around line 91-98: Constrain the `@modelcontextprotocol/sdk` entry in
peerDependencies to the v1 range by changing its version requirement to >=1.13.0
<2; keep the existing optional peerDependenciesMeta configuration unchanged.

In `@src/guard-proxy.mjs`:
- Around line 22-24: Update transportUrl and its callers to stop deriving the
scheme from port 80 versus other ports. Use the existing host/port
transport-policy operation directly so HTTP on nonstandard ports remains HTTP
and scheme-sensitive policies receive the actual transport decision.
- Around line 78-93: Update the resolved-address validation loop in the proxy
connection flow to preserve the original hostname allowlist authorization when
calling guardUrl for each candidate. Ensure allowlisted hostnames remain
permitted even when resolving to private or loopback literals, while metadata
addresses continue to be rejected unconditionally; use the existing attribution
or decision context rather than weakening general address checks.

In `@src/mcp-server.mjs`:
- Around line 209-214: Centralize parsing of BETTERWRIGHT_BROWSER and use the
shared parser in both sites: update src/mcp-server.mjs lines 209-214 to validate
the environment value and pass the parsed result through BetterWright’s browser
option, and update src/doctor.mjs lines 117-122 to reuse that parser and report
unsupported values as not ready instead of treating them as Cloak.

In `@src/policy.mjs`:
- Around line 141-147: Canonicalize hostnames by removing a terminal DNS dot
before the metadata-floor classification used by the policy checks, so dotted
forms such as metadata.google.internal. remain blocked under the open defaults.
Update the relevant hostname classification logic near allowPrivateNetwork and
allowLoopback, and add a conformance vector covering the trailing-dot metadata
hostname.

In `@src/worker.mjs`:
- Around line 1839-1840: Make buildEnvelope’s pages resolution non-throwing by
safely handling failures from summarizeSessionPages(session), especially on the
execute failure path. Preserve the existing pages value when available and use a
safe fallback when summarization fails, ensuring the error envelope is still
returned to the client.
- Around line 105-118: The MAX_ACTIVE_SECRETS eviction in trackSecret must not
remove secrets while the associated browser page or session remains capable of
exposing them. Tie eviction to page/session disposal, or restart and invalidate
that browser state before removing the oldest secret; preserve redaction for all
still-live browser state.

---

Nitpick comments:
In `@docs/agent-prompt.md`:
- Line 29: Update the API reference in the trusted host-side fill text to use
the fully qualified `bw.generateAndFillCredential` name, matching the existing
`bw.fillCredential` notation and the actual JavaScript API.

In `@README.md`:
- Line 138: Update the Claude MCP command in README.md lines 138-138 and
SETUP.md lines 176-176 to invoke npx with the -y flag; also update the JSON
arguments array in SETUP.md lines 186-188 to include "-y" before "betterwright"
and "mcp".

In `@scripts/check-package.mjs`:
- Line 60: Update the exclusion filter in the package-checking logic to retain
python as a forbidden path alongside node_modules, tests, artifacts, and
.betterwright. Preserve the existing path-boundary matching behavior so Python
directories or files anywhere in the npm tarball are still detected.

In `@tests/fixtures/policy-vectors.json`:
- Around line 605-609: Add a narrowly scoped, fixture-level suppression for the
AWS-shaped value in the “aws access key in query” test vector so Checkov ignores
only this known test secret. Preserve repository-wide secret scanning and avoid
broad patterns or global configuration changes.

In `@types/mcp-server.d.ts`:
- Around line 24-25: Update the contentForResult parameter type from unknown to
RunResult<unknown> in the MCP server declarations, preserving its
Promise<McpContentBlock[]> return type. Ensure callers must provide a valid run
result rather than nullable or arbitrary values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 126a96ed-0a3e-4aeb-8ab8-53a2e282f1e3

📥 Commits

Reviewing files that changed from the base of the PR and between 31f9343 and 93e9df7.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (72)
  • .github/workflows/ci.yml
  • .github/workflows/publish-npm.yml
  • .github/workflows/publish-pypi.yml
  • CHANGELOG.md
  • CONTRIBUTING.md
  • README.md
  • SETUP.md
  • bin/betterwright.mjs
  • docs/agent-prompt.md
  • docs/architecture.md
  • docs/attach-mode.md
  • docs/browser-api.md
  • docs/credentials.md
  • docs/getting-started.md
  • docs/javascript.md
  • docs/network-policy.md
  • docs/python.md
  • examples/README.md
  • examples/python/local_dev.py
  • examples/python/login_with_vault.py
  • examples/python/onepassword_attach.py
  • examples/python/quickstart.py
  • examples/python/signup_with_generated_password.py
  • examples/python/solve_captcha.py
  • package.json
  • python/LICENSE
  • python/README.md
  • python/pyproject.toml
  • python/src/betterwright/__init__.py
  • python/src/betterwright/__main__.py
  • python/src/betterwright/_display.py
  • python/src/betterwright/_home.py
  • python/src/betterwright/_worker/challenges.mjs
  • python/src/betterwright/_worker/cloak.mjs
  • python/src/betterwright/_worker/downloads.mjs
  • python/src/betterwright/_worker/human.mjs
  • python/src/betterwright/_worker/snapshot.mjs
  • python/src/betterwright/_worker/worker.mjs
  • python/src/betterwright/bridge.py
  • python/src/betterwright/chrome.py
  • python/src/betterwright/cli.py
  • python/src/betterwright/client.py
  • python/src/betterwright/integrations/__init__.py
  • python/src/betterwright/integrations/mcp_server.py
  • python/src/betterwright/policy.py
  • python/src/betterwright/prompt.py
  • python/src/betterwright/runtime.py
  • python/src/betterwright/vault.py
  • python/tests/test_artifacts_and_display.py
  • python/tests/test_browser_integration.py
  • python/tests/test_chrome.py
  • python/tests/test_credential_fill.py
  • python/tests/test_mcp_download_approval.py
  • python/tests/test_policy.py
  • python/tests/test_prompt.py
  • python/tests/test_vault.py
  • scripts/check-package.mjs
  • scripts/check-versions.mjs
  • scripts/sync-worker.mjs
  • src/client.mjs
  • src/doctor.mjs
  • src/guard-proxy.mjs
  • src/mcp-server.mjs
  • src/policy.mjs
  • src/prompt.mjs
  • src/worker.mjs
  • tests/fixtures/policy-vectors.json
  • tests/node/browser.test.mjs
  • tests/node/mcp-server.test.mjs
  • tests/node/policy-vectors.test.mjs
  • tests/node/policy.test.mjs
  • types/mcp-server.d.ts
💤 Files with no reviewable changes (39)
  • scripts/sync-worker.mjs
  • python/src/betterwright/integrations/init.py
  • python/tests/test_mcp_download_approval.py
  • python/tests/test_prompt.py
  • examples/python/onepassword_attach.py
  • examples/python/solve_captcha.py
  • python/pyproject.toml
  • .github/workflows/publish-pypi.yml
  • python/src/betterwright/_home.py
  • docs/python.md
  • python/tests/test_browser_integration.py
  • python/src/betterwright/runtime.py
  • python/src/betterwright/_worker/downloads.mjs
  • python/src/betterwright/_display.py
  • python/src/betterwright/main.py
  • python/src/betterwright/cli.py
  • python/src/betterwright/init.py
  • python/tests/test_policy.py
  • python/src/betterwright/chrome.py
  • python/src/betterwright/policy.py
  • python/README.md
  • python/src/betterwright/prompt.py
  • python/src/betterwright/vault.py
  • python/tests/test_vault.py
  • examples/python/quickstart.py
  • examples/python/login_with_vault.py
  • python/tests/test_artifacts_and_display.py
  • examples/python/local_dev.py
  • python/LICENSE
  • python/src/betterwright/client.py
  • python/src/betterwright/_worker/challenges.mjs
  • python/src/betterwright/_worker/human.mjs
  • python/src/betterwright/_worker/snapshot.mjs
  • python/tests/test_credential_fill.py
  • python/tests/test_chrome.py
  • python/src/betterwright/_worker/cloak.mjs
  • examples/python/signup_with_generated_password.py
  • python/src/betterwright/bridge.py
  • python/src/betterwright/integrations/mcp_server.py

Comment thread docs/credentials.md
Comment on lines +70 to +95
## Providing a vault

Pass a `vault=` instance to `BetterWright(...)` to share one store, or
`vault=False` to disable the model-facing management helpers entirely. Trusted
host code can call `fetch_for_fill`, `reveal`, or `generate` directly, but must
not return those secret-bearing results to model-authored code — prefer
`bw.fill_credential(...)`, which keeps the secret inside the worker.
BetterWright has no built-in credential store. The `vault` option is a
pluggable backend: any object exposing
`handleRequest(action, payload, origin)` (and optionally `redact`) can back the
management helpers and the same `fillCredential` path, so a source such as a
1Password CLI/SDK backend can be dropped in without changing the fill code.

```js
new BetterWright({
vault: {
async handleRequest(action, payload, origin) { /* list|save|update|remove|fill|generate */ },
redact(value) { return value; }, // optional: scrub secrets from output
},
});
```

The `vault=` object is a pluggable backend: any object exposing
`handle_request(action, payload, origin)` (and optionally `redact`) can back the
same `fill_credential` path, so an alternate source such as a 1Password CLI/SDK
backend can be dropped in without changing the fill code.
Every request carries the canonical `http(s)` origin of the current page
(`scheme://host[:port]`, default ports and user-info stripped); scope records
to it so one origin cannot read another's credential. If the backend provides
`redact`, every value it has handled is scrubbed from `run()` output as a final
safety net — redaction is not treated as authorization and is not used to make
DOM filling safe. Trusted host code must not return secret-bearing vault
results to model-authored code — prefer `bw.fillCredential(...)`, which keeps
the secret inside the worker. Omit `vault` to run without credential
management helpers entirely.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the stale credential-storage wording.

This section now says BetterWright has no built-in store and requires a caller-supplied vault, but docs/getting-started.md still labels this as “encrypted storage.” Update that link text to describe the pluggable backend and trusted host-side use.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/credentials.md` around lines 70 - 95, Update the credential-storage link
text in docs/getting-started.md to describe the vault as a pluggable backend
used by trusted host-side code, replacing the stale “encrypted storage” wording
while preserving the existing link destination.

Comment thread docs/getting-started.md
Comment on lines +12 to +17
npx betterwright doctor # prints what resolved; should end with "BetterWright is ready."
```

If `doctor` reports Node missing, install it from <https://nodejs.org> and rerun
`setup`. If a JavaScript install reports CloakBrowser missing, rerun
`npx betterwright setup`; Python installs use `betterwright setup`.
`setup`. If `doctor` reports CloakBrowser missing, rerun
`npx betterwright setup`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,120p' docs/getting-started.md
printf '\n--- SEARCH ---\n'
rg -n "betterwright doctor|betterwright setup|doctor" -S .

Repository: CuriosityOS/betterwright

Length of output: 1934


Move the Node.js warning before doctor. npx betterwright doctor itself requires Node/npm, so users can’t get that message without first installing Node.js. Tell them to install Node.js first, then rerun setup and doctor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/getting-started.md` around lines 12 - 17, Update the getting-started
instructions so the Node.js installation warning appears before the npx
betterwright doctor command. Tell users to install Node.js first, then rerun
setup followed by doctor; keep the existing CloakBrowser recovery guidance
unchanged.

Comment thread docs/network-policy.md
Comment on lines +24 to +28
- **Allows the public internet, private networks, and loopback** — RFC 1918
ranges, `127.0.0.0/8`, `localhost`, IPv6 loopback/unique-local, link-local,
carrier-grade NAT, and `*.internal`/`*.local`/`*.lan` hosts are reachable, so
an agent can drive local dev servers, a home router, or an intranet host
without extra configuration.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the stale policy image caption.

The default posture now allows private networks and loopback, but the image description at Line [3] still says the policy “blocks private” addresses. Update the caption/alt text so the documentation does not contradict the new default.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/network-policy.md` around lines 24 - 28, Update the network policy image
caption or alt text near the private-network description to state that private
networks and loopback are allowed by default, removing the stale claim that the
policy blocks private addresses.

Comment thread docs/network-policy.md
Comment on lines +60 to 66
| `allowHosts` | Always allow these hosts. An entry matches a host exactly or as a parent domain (`example.com` also matches `sub.example.com`); add `:port` to pin a port. |
| `blockHosts` | Always block these hosts, evaluated before allowlists. |
| `blockSecretBearingUrls` | Refuse URLs that look like they carry a key/token. Default `true`. |
| `custom` | A hook, `custom(url, details)`, returning a decision or `null`, evaluated last. |

Evaluation order is: scheme check → `blockHosts` → `allowHosts` → metadata
floor → private-network rules → `custom`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Qualify the allowHosts guarantee with the metadata floor.

“Always allow these hosts” contradicts the stated rule that metadata endpoints can never be allowlisted. Document this as “allow unless blocked by the metadata floor” to avoid implying that an allowlist can bypass the cloud-credential protection.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/network-policy.md` around lines 60 - 66, Update the allowHosts
description in the network policy table to state that matching hosts are allowed
unless blocked by the metadata floor. Keep the existing exact/parent-domain
matching and port-pinning behavior, and preserve the documented evaluation
order.

Comment thread examples/README.md
Comment on lines 3 to 8
Runnable scripts. Each needs the runtime installed first (`betterwright setup`).

### Python

```bash
pip install betterwright
python examples/python/quickstart.py
```

- [`quickstart.py`](python/quickstart.py) — navigate, read, and capture proof.
- [`login_with_vault.py`](python/login_with_vault.py) — store and fill a
credential without the password reaching your code.
- [`signup_with_generated_password.py`](python/signup_with_generated_password.py) —
sign up with a generated password and a confirm-password field.
- [`onepassword_attach.py`](python/onepassword_attach.py) — log in with your own
1Password extension in an attached (auto-launched) real Chrome.
- [`local_dev.py`](python/local_dev.py) — drive a `localhost` dev server.
- [`solve_captcha.py`](python/solve_captcha.py) — native checkbox and text-challenge helpers.

### JavaScript

```bash
npm install betterwright
node examples/javascript/quickstart.mjs
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include the required browser setup command.

The text says examples require betterwright setup, but the copy-paste block only runs npm install and the script. Add npx betterwright setup before node examples/javascript/quickstart.mjs, or remove the setup prerequisite.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/README.md` around lines 3 - 8, Update the examples README command
block to include the required browser setup step by adding npx betterwright
setup between npm install betterwright and node
examples/javascript/quickstart.mjs, keeping the prerequisite text consistent.

Comment thread src/guard-proxy.mjs
Comment on lines +78 to +93
// Validate every answer, then connect to one of these exact literals. This
// closes both redirect-hop and DNS-rebinding gaps: Chromium never performs a
// second target lookup outside this guarded worker.
for (const candidate of addresses) {
const decision = await guardUrl(
transportUrl(candidate.address, port),
{
method: "CONNECT",
resourceType: "transport-address",
resolvedFrom: host,
},
attribution,
);
if (!decision?.allowed) throw proxyBlockedError(decision?.reason);
}
return addresses;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve hostname allowlist semantics during resolved-address checks.

Under strict mode, an allowlisted hostname resolving to 10.x, loopback, or another private literal passes the first check but fails the candidate check. This makes allowHosts: ["service.internal"] ineffective through the forced proxy. Carry the original allowlist authorization into resolution while still unconditionally rejecting metadata addresses.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/guard-proxy.mjs` around lines 78 - 93, Update the resolved-address
validation loop in the proxy connection flow to preserve the original hostname
allowlist authorization when calling guardUrl for each candidate. Ensure
allowlisted hostnames remain permitted even when resolving to private or
loopback literals, while metadata addresses continue to be rejected
unconditionally; use the existing attribution or decision context rather than
weakening general address checks.

Comment thread src/mcp-server.mjs
Comment on lines +209 to +214
const browser = new BetterWright({
policy: policyFromEnv(env),
headless: headlessFromEnv(env),
connectOverCdp: String(env.BETTERWRIGHT_CONNECT_OVER_CDP || "").trim() || undefined,
downloadPolicy,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Centralize and propagate BETTERWRIGHT_BROWSER.

The MCP runtime and diagnostics currently interpret this setting differently.

  • src/mcp-server.mjs#L209-L214: validate the environment value and pass it through the browser option.
  • src/doctor.mjs#L117-L122: reuse the same parser and mark unsupported values as not ready rather than treating them as Cloak.
📍 Affects 2 files
  • src/mcp-server.mjs#L209-L214 (this comment)
  • src/doctor.mjs#L117-L122
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mcp-server.mjs` around lines 209 - 214, Centralize parsing of
BETTERWRIGHT_BROWSER and use the shared parser in both sites: update
src/mcp-server.mjs lines 209-214 to validate the environment value and pass the
parsed result through BetterWright’s browser option, and update src/doctor.mjs
lines 117-122 to reuse that parser and report unsupported values as not ready
instead of treating them as Cloak.

Comment thread src/policy.mjs
Comment on lines +141 to +147
// Private networks and loopback are reachable by default; agents commonly
// drive local dev servers, routers, and intranet hosts. Pass
// `allowPrivateNetwork: false` / `allowLoopback: false` for a hardened
// deployment. The cloud-metadata floor below is NOT governed by these and
// stays blocked regardless.
this.allowPrivateNetwork = options.allowPrivateNetwork !== false;
this.allowLoopback = options.allowLoopback !== false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Canonicalize metadata hostnames before enabling open defaults.

A trailing DNS dot bypasses the metadata floor: metadata.google.internal. matches neither the metadata set nor .internal, so the new default-open policy allows it—especially dangerous in attach mode without the transport proxy. Strip a terminal dot before classification and add a conformance vector.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/policy.mjs` around lines 141 - 147, Canonicalize hostnames by removing a
terminal DNS dot before the metadata-floor classification used by the policy
checks, so dotted forms such as metadata.google.internal. remain blocked under
the open defaults. Update the relevant hostname classification logic near
allowPrivateNetwork and allowLoopback, and add a conformance vector covering the
trailing-dot metadata hostname.

Comment thread src/worker.mjs
Comment on lines +105 to +118
// Secrets are kept beyond the run that used them because later runs can still
// echo a previously typed value (console, DOM dumps). The cap only bounds
// memory and per-redaction cost in long-lived workers; eviction is oldest-first
// (Set iterates in insertion order), with re-tracked secrets refreshed to
// newest.
function trackSecret(value) {
const secret = String(value ?? "");
if (secret.length < 4) return;
activeSecrets.delete(secret);
activeSecrets.add(secret);
if (activeSecrets.size > MAX_ACTIVE_SECRETS) {
const oldest = activeSecrets.values().next().value;
activeSecrets.delete(oldest);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Do not evict secrets while browser state can still expose them.

After 201 distinct secrets, an older value can remain in the DOM, console, or queued events while no longer being redacted. Tie eviction to page/session disposal, or restart and invalidate browser state when the retention bound is reached.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/worker.mjs` around lines 105 - 118, The MAX_ACTIVE_SECRETS eviction in
trackSecret must not remove secrets while the associated browser page or session
remains capable of exposing them. Tie eviction to page/session disposal, or
restart and invalidate that browser state before removing the oldest secret;
preserve redaction for all still-live browser state.

Comment thread src/worker.mjs
Comment on lines +1839 to +1840
pages: pages ?? (await summarizeSessionPages(session)),
durationMs: Math.round((performance.now() - started) * 10) / 10,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep error-envelope construction non-throwing.

The execute failure path calls summarizeSessionPages() through buildEnvelope; if the original failure involved a crashed or closed browser, summarization can throw and prevent any result from reaching the client. Catch summary failures or pass a safely computed pages fallback.

Proposed fix
-    pages: pages ?? (await summarizeSessionPages(session)),
+    pages:
+      pages ??
+      (await summarizeSessionPages(session).catch(() => [])),

Also applies to: 2863-2873

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/worker.mjs` around lines 1839 - 1840, Make buildEnvelope’s pages
resolution non-throwing by safely handling failures from
summarizeSessionPages(session), especially on the execute failure path. Preserve
the existing pages value when available and use a safe fallback when
summarization fails, ensuring the error envelope is still returned to the
client.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant