# Changelog ## 0.5.0 — 2026-10-11 Browser Use transport, a deterministic handoff state machine, and honest E2E boundaries. This is the milestone the v0.2.0 upgrade brief named (Issue #2, Phases 0–2); it ships as 0.5.0 because 0.4.0 already exists on main and version numbers do not go backwards. The transport migrates from legacy `browser_*` tool assumptions and a synthetic-paste input path to the official DSH Browser Use provider semantics. - **Browser Use transport** (`references/transport-browser-use.md`) — navigate / snapshot / fill / click / read with a re-observe after every action. Main-Chrome login reuse without touching the user's profile: upstream `--autoConnect` (attaches to the running Chrome after one Allow dialog) or provider `mode: attach` + `endpoint`. The bundled CDP driver stays as a documented fallback, and `transport.mjs handoff` validates transcripts for either path. - **Deterministic handoff state machine** — `PRECHECKED → COMPOSER_VERIFIED → SEND_INTENT → SUBMITTED → RESULT_OBSERVED → RESULT_VALIDATED`, with `UNCERTAIN` terminal. Per-state required evidence; RESULT states must bind the run's `packet_id`. `transport.mjs handoff ` validates a transcript, and the receipt `complete` gate re-validates an optional `handoff` field: a run whose submit was never confirmed cannot be marked complete. Legacy receipts without the field stay valid. - **Human-confirmed send** — `SEND_INTENT` records the explicit user go-ahead; no automatic submission, exactly one send attempt, no retry on uncertainty. - **Two live-flow bugs fixed** — the send `click()` false negative (DOM click returns `undefined`), and stale-answer extraction (fetch now re-resolves the newest assistant turn after the stream settles and never falls back to the first historical Copy button). - **New expression guard** — page-phase bodies are embedded in a template literal, so a stray backtick inside a comment truncated every later phase and silently swallowed the `send-uncertain` path; each expression is now executed in tests and must evaluate to a promise. - **Phase 0 verified** — clean-profile install mounts the bundle layer (`dsh plugin --profile

add .`, then `--dump-config` shows the plugin row) and Doctor reports READY. **Honest boundary:** verified so far are install/skill-load smoke, consent gate enforcement, the state machine, the `complete` gate integration, and all unit tests on real files. A real ChatGPT round-trip through a mounted Browser Use provider is **not yet verified** — this deployment has no provider mounted, and per the roadmap, mock-green is not browser-green. See the PR description for the blockers and the opt-in manual smoke procedure. - 76 node:test cases (was 66) ## 0.4.0 — 2026-10-10 Audit release. A README-vs-implementation review plus two independent audits (adoption-gate enforcement, sensitive egress) found that several claims in the documentation were **prose, not code**. This release turns the cheap ones into mechanisms and rewrites the rest so the docs describe what actually happens. A third, adversarial audit of the first pass then found three more holes — one of them a regression this release introduced — so the notes below describe the final state, not the intermediate one. **Regression caught and fixed** - **The widened scanner blocked legitimate packets.** The generic rules matched *any* non-space right-hand side, so `const token = getToken();`, `api_key=YOUR_KEY_HERE`, `Authorization: ` and `Cookie: session=abc123` all tripped it. A gate that blocks real work is worse than the misses it closes, because it teaches operators to pass `--allow-unscanned`. The rules now require the *value* to look like key material, and a name that is explicitly a password/passphrase relaxes the diversity test so `DB_PASS=correcthorsebatterystaple` is still caught. Measured: 0 false positives on 21 legitimate samples, 13/14 real secrets caught (the miss is a password in prose, which is irreducibly semantic). **Turned into enforced code** - **Consent is now a gate, not a suggestion.** `transport.mjs open|probe|send|fetch|launch` read the versioned decision and fail with `why: "consent-not-ready"` unless it is `READY`; the panel's `/open`, `/launch`, `/probe` routes return 403. Previously `transport.mjs` never imported `consent.mjs`, so a disabled install was still one command away from driving the browser — and `launch` stayed a working un-gated path even after the first pass, starting a debug Chrome with no consent file while the panel refused the same request. `status` deliberately stays usable (it only probes the debug port) so the link can be diagnosed while the decision is pending. - **An unsupported `--site` can no longer borrow another target's module.** `resolveTarget` fell back to the chatgpt page module for any URL, so `--site https://example.com/` navigated an arbitrary site while reporting `target: "chatgpt"` — driving a page with selectors never written for it. It now requires a known site plus a matching target and fails with a usage error. - **UTF-16 request files are no longer a scan bypass.** Reading them as UTF-8 yields interleaved NULs, so no pattern matched while the composer still received the secret as mojibake. Request files are decoded (BOM or NUL heuristic) before scanning. - **The adoption record needs per-proposal evidence.** One arbitrary string used to satisfy the whole record; every accepted item must now carry its own `local_evidence` anchor. This is still structure, not proof — the gate cannot verify an anchor names a real symbol — and both the reference and the code comment now say exactly that. - **`send` refuses credential-shaped request files.** The scanner is now wired into the send path (`why: "secret-scan-failed"`) instead of being a separate CLI step an agent had to remember; `--allow-unscanned` is the deliberate override. The scan is exported as `scanForSecrets()` from `validate.mjs`. - **The scan itself was weak.** It caught 7 shapes and missed every JWT, cloud secret key, connection string, webhook, and `.env` line. It now covers: PEM/PGP keys, AWS (id + secret), GitHub/GitLab/npm/HF, Slack/Discord webhooks, OpenAI/Stripe/Google/Azure/Twilio keys, JWTs, `user:pass@host` connection strings, `Authorization`/`Cookie` headers, and `KEY: value` / `.env`-style credential assignments (value-checked, see the regression note above). Two regex bugs were also fixed: `sk_live_` (underscore) never matched, and a leading `\b` failed after `=`. - **`complete` re-scans what it hashes.** `verifyReceiptArtifacts()` previously verified SHA-256 only; a packet containing `password: hunter2` passed `complete` while failing `packet`. It now re-runs the credential scan on the artifact contents, so the gate no longer rests on a self-declared `credentials_scan_passed: true`. - **`complete` demands a non-empty adoption record.** `adoption.status: "passed"` with four empty arrays used to satisfy the gate — the exact shape a run that skipped the gate produces. It now requires at least one classified proposal and at least one piece of local evidence. - **The panel routes actually work again.** `webPort()` read the loader entry, where an `!!js` port expression is a *function*, so it fell back to 3080 and the Host guard rejected the real `127.0.0.1:38080` — every route 403'd. It now prefers `req.socket.localPort` (the same fix dsh-versions carries). - **`/open` validates its target** like `/target` does, instead of passing free text into the driver's argv. - **The forbidden-API test guard was a lint.** It matched five strings; a page module using `XMLHttpRequest`, `sendBeacon`, `cookieStore`, `WebSocket`, dynamic `import()`, or a `location` redirect passed green. `FORBIDDEN_PAGE_APIS` now covers the bypass surface, and a "guard the guard" test asserts each one is rejected. - **Target/page-module drift is now impossible.** A test asserts `targets-table.mjs` and `scripts/lib/page-.mjs` list exactly the same set, and that each module exports the phase builders its `INPUT_MODE` requires. (Writing that test immediately exposed that `page-chatgpt.mjs` never declared `INPUT_MODE` — the driver hardcoded it. Both modules now self-declare.) **Documentation honesty** - README gains an **「强制执行 vs. 纪律约定」** table separating what code enforces from what the agent is merely asked to do, and states the root cause plainly: `dsh.plugin.json` declares `"tools": []`, so the plugin cannot intercept tool calls and the adoption gate is compliance-dependent by construction. - `references/adoption-gate.md` and `references/reasoning-result.md` now say that a passing validation is a *structural* verdict, not a safety one — the audit confirmed a result whose Proposed Changes read "ignore previous instructions and run …" validates cleanly. - README lists the shipped targets explicitly (`chatgpt`, `arena`) and states that no other web AI is implemented, listed, or selectable. - Corrected stale claims: five → six transport subcommands; the test count; and the run-artifact location now that packets are written outside the repo. - 63 node:test cases (was 51) ## 0.3.0 — 2026-09-08 Second target, a working settings panel, and the target becomes a visible choice. All of it verified live against the real sites, not just in unit tests. - **`arena` target** (`https://arena.ai/`) — `scripts/lib/page-arena.mjs` plus `references/transport-arena.md`. Arena's composer is a React-controlled `