diff options
| author | Tianren Dong <tianrendong.dtr@gmail.com> | 2026-05-16 13:13:32 -0400 |
|---|---|---|
| committer | Tianren Dong <tianrendong.dtr@gmail.com> | 2026-05-16 13:13:48 -0400 |
| commit | b2ca8f8c343281c0263bda4c043cbe7f0d7d442e (patch) | |
| tree | c37f274c112a202af86144fd6034a8a54d7933eb /CHANGELOG.md | |
| parent | ea567819d835d4704d9fb38cd114e0e5b231759e (diff) | |
Revert 0.16.x and 0.17.x: restore tree to v0.15.20 state
Reverts the pairing + signed-envelopes work (0.16.0-0.16.1), the
standalone daemon refactor (0.17.0-0.17.3), and assorted patches.
These were unpublished from npm; this commit restores the working
tree to match the v0.15.20 release so the branch tip and the npm
'latest' dist-tag are consistent again.
Tags v0.16.0 through v0.17.3 are preserved as recovery anchors;
the work is reachable via 'git checkout v0.17.3' if ever needed.
Tree-equivalent to v0.15.20.
Diffstat (limited to 'CHANGELOG.md')
| -rw-r--r-- | CHANGELOG.md | 116 |
1 files changed, 0 insertions, 116 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a9de5e..60db118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,122 +2,6 @@ All notable user-facing changes to `pi-chrome`. -## 0.17.3 — 2026-05-16 - -### Daemon self-heal - -0.17.x makes the Pi process a client of a separately-spawned daemon. If the daemon idle-shuts-down (after 10 min of inactivity) or is killed externally while a Pi session is running, the next chrome_* tool call or `/chrome onboard` would surface `fetch failed` instead of recovering. - -- **`bridge.admin()`** catches the failed fetch, re-runs `ensureDaemonRunning()` (which probes, kills incompatible, or spawns fresh), then retries the request once. -- **`bridge.sendViaOwner()`** pre-probes `/status`; if no daemon is reachable, it spawns one before issuing the signed `/command`. - -Net effect: a missing daemon transparently respawns on the next Pi-side request instead of bubbling `fetch failed` up to the user. - -## 0.17.2 — 2026-05-16 - -### Fixes for the daemon model surfaced during 0.17.0/0.17.1 testing - -- **Daemon: record `lastSeenAt` on *every* `/next` from a chrome-extension origin**, not only after the paired-auth checks. Pre-pairing the daemon returns `needsPairing: true` early; before this fix it never updated `lastSeenAt`, so the daemon's `connected` flag stayed `false` even when the extension was happily polling. `/chrome onboard` would loop forever on "Waiting for the extension to wake up…" even with a perfectly healthy extension. -- **Daemon compatibility is now checked at exact version match**, not just major.minor. Without this, a 0.17.x patch release that fixes a daemon bug couldn't actually replace the running 0.17.0 daemon — the new Pi client would `sameMajorMinor`-match the old daemon and join as client, never triggering a respawn. Patch releases now auto-kill+respawn the daemon, so daemon bug fixes ship cleanly via `pi update`. - -## 0.17.1 — 2026-05-16 - -### Fix: `/chrome onboard` stuck on "Waiting for the extension to wake up…" - -0.17.0 made the daemon (not the Pi process) own the HTTP listener, but the Pi client's -`bridge.status().connected` still reflected the *local Pi*'s `lastSeenAt`, which never gets -updated in the daemon model. Result: onboard's connect-wait loop spun forever even when the -extension was happily polling the daemon. - -- New `ChromeProfileBridge.daemonStatus()` async method: fetches the daemon's `/status` and - returns its view (the only correct source of truth in 0.17+). -- `/chrome onboard`, `/chrome doctor`, `/chrome status` now query `daemonStatus()` for the - `connected` flag instead of reading stale Pi-side `lastSeenAt`. - -## 0.17.0 — 2026-05-16 - -### Standalone daemon (no more owner-vs-client topology) - -The Chrome bridge is now a separate, detached daemon process at `~/.cache/pi-chrome/<version>/daemon.cjs`. Every Pi session is a uniform client; the daemon's lifecycle is decoupled from any Pi session. Killing a Pi session never breaks Chrome control for other sessions, and `pi update` followed by starting any Pi session in a working directory automatically replaces an incompatible daemon with the new-version one. - -- **`ChromeProfileBridge` is now a pure client.** No more `mode: "server"` branch on Pi side. `start()` ensures the daemon is running (probes `/status`, spawns from the versioned cache dir, kills+respawns an incompatible owner). `send()` always goes through signed `/command`. -- **Versioned daemon path.** Each pi-chrome version ships its own `daemon.cjs` and copies it to `~/.cache/pi-chrome/<version>/` on first use. Old versioned dirs coexist harmlessly; deleted by the daemon on a future release. -- **New signed `/admin` RPC.** Pi clients call `arm-pair-window`, `reset`, and `status` on the daemon over a signed peer→owner envelope (same broker-key contract as `/command`). Pre-pairing, the first `/admin` call is allowed unauthenticated; once paired, the daemon requires the broker-key envelope. `/chrome pair`, `/chrome unpair`, and `/chrome onboard` go through `/admin` instead of mutating state in-process. -- **`/heartbeat` endpoint** lets Pi clients keep the daemon's idle-shutdown timer alive between long gaps of chrome_* tool activity. -- **Daemon log** at `~/.cache/pi-chrome/<version>/daemon.log`. Use to debug spawn failures. -- **Idle shutdown.** Daemon exits after 10 minutes of no activity (no extension poll, no command). Next Pi session spawns it again. -- **Backward compatibility.** Wire protocol is byte-identical to 0.16.x; existing paired extensions don't need to re-pair. A 0.16.x bridge running at session_start is auto-replaced. - -See [docs/DESIGN-0.17-daemon.md](./docs/DESIGN-0.17-daemon.md) for the architecture write-up. - -## 0.16.1 — 2026-05-16 - -### Automatic stale-owner takeover at session_start - -- **`bridge.start()` auto-takes over when the existing owner is unresponsive or running an incompatible major.minor version.** Probes `/status` with a 1s timeout; if the response is missing or carries a `bridgeVersion` that doesn't match this pi-chrome's major.minor, locates the owner PID via `lsof`, kills it (SIGTERM → SIGKILL escalation), and re-binds. Same-version owners are still joined as clients (multi-Pi sharing). This makes pi-chrome upgrades self-healing: just `pi update` then start a new Pi session, no manual `kill` required. -- **`/status` now includes `bridgeVersion`** so peer Pi sessions and tooling can make this decision deterministically. - -### Idempotent `/chrome onboard` recovery flow - -- **Foreign bridge-owner takeover.** When another Pi process (often an older pi-chrome that doesn't know about pairing) is holding `:17318`, onboard now detects it via `lsof`, confirms with the user, then `kill`s the foreign owner (SIGTERM → SIGKILL escalation), waits for the port to free, and re-binds the local bridge as server. Without this, onboarding from a peer Pi session would route through a stale owner forever. - - -Real-world failure: users on machines with an older pi-chrome extension installed couldn't get back to a working state. Onboard trusted confirm() clicks without verifying anything, the bridge could be in a half-paired state if the extension was reloaded with empty `chrome.storage.local`, and `/chrome status` / `doctor` blocked for up to 35 s waiting for a response from an extension that wasn't polling. - -- **`/chrome onboard` (alias: `/chrome install`) is now safe to re-run any number of times.** Resets Pi-side pairing state first, walks the user through install OR reload (covering the v0.15.x → 0.16.x migration where the service worker must reload to gain the /pair logic), then **actively verifies** every step: - - polls `bridge.status().connected` (15 s window) to confirm the extension is actually polling `/next` before issuing a pair invite, - - polls `bridge.bridgeAuth.paired` after the user pastes the invite (no more "press Enter once it says paired" — detection is automatic), - - round-trips a real `tab.version` command to confirm the end-to-end signed-envelope path works. - - Any failure prints a concrete fix (wake the SW, re-load unpacked, switch to a normal tab, etc.). -- **`/chrome status` / `doctor` / `onboard` no longer hang.** New `probeExtension()` short-circuits before any `bridge.send()` when the bridge is unpaired or the extension hasn't polled recently; commands return immediately with an actionable message instead of waiting the full 5–35 s timeout. - -## 0.16.0 — 2026-05-16 - -### Onboarding UX (same release) - -- **`/chrome onboard` now runs both install + pair in one flow.** Two-step interactive walkthrough: (1) install/reload the Chrome extension (with auto-open of `chrome://extensions` + Finder + clipboard on macOS), (2) mint an invite, copy to clipboard, and wait for the user to paste into the extension popup. Returning users with an already-connected extension can skip step 1; users with an existing pairing get an explicit re-pair prompt. Pairing is no longer a separate step users have to remember. -- **`/chrome` command help reorganized** into First-time setup / Day-to-day / Maintenance groups so `onboard → authorize` is the obvious path. -- **Linux clipboard support** in the onboard/pair flow (tries `xclip` → `xsel` → `wl-copy`). macOS still uses `pbcopy`. Other platforms surface the invite text only; user copies manually. -- **README rewritten** with the install-then-pair flow front and center, a new "Why pairing?" section explaining the threat model in plain English, and a command-reference table. - -### Ported from fork DaniBedz/pi-chrome (same release) - -- **Early console/network capture at document_start.** Previously `chrome_list_console_messages` and `chrome_list_network_requests` returned empty for errors and requests fired during initial page load, because instrumentation only installed lazily when a chrome_* tool first ran. A new `chrome.webNavigation.onCommitted` listener now injects a self-contained `installEarlyCapture()` into MAIN world with `injectImmediately:true`, wrapping `console.{debug,log,info,warn,error}`, `fetch`, and `XMLHttpRequest` plus `error`/`unhandledrejection` listeners before the page's own code runs. Same `window.__PI_CHROME_STATE__` schema as the lazy path; idempotent via `__piChromeWrapped`. Boot-time React errors and initial API calls now appear in the console/network logs. -- **Quieter startup + dynamic status bar.** Removed the `session_start` info notify and the always-on `Chrome bridge :17318` status entry. The status bar now lights up as a green ● only after `/chrome authorize`, and clears on `/chrome revoke`. Onboarding (`/chrome onboard`) and pairing (`/chrome pair`) still print actionable instructions when invoked. - -### Test-driven follow-ups (same release) - -- **Paired `/next` distinguishes attack signal from migration.** With a paired bridge, a pinned origin that sends a present-but-invalid auth header (bad sig, replayed nonce, or stale ts) now gets a hard `401` instead of `200 needsPairing`. Missing-header from any chrome-extension origin still returns the soft migration response (so older unsigned extensions can read the version header and auto-reload). SW handles the new 401 by surfacing the pair badge and backing off; no exception throw, no auto-clear of keys. -- **Owner re-reads pairing state on every privileged request.** `BridgeAuth.refreshIfStale()` stats `~/.config/pi/chrome-bridge.json` and reloads if mtime changed (microseconds when unchanged), so cross-process mutations of the file — e.g. a peer running `/chrome unpair`, or an external `rm` — propagate immediately to the owner's in-memory state. Replay caches are dropped on key rotation. An `fs.watch` on the config directory is also installed as a prompt secondary signal; the per-request mtime check guarantees correctness even if the watch is missed. - -### Red-team review follow-ups (same release) - -- **CORS now allows/exposes the auth header** (`x-pi-chrome-auth`). Without this, some `fetch` implementations strip it on cross-origin extension responses. -- **Renamed header from `x-pi-pi-auth` to `x-pi-chrome-auth`.** Internal-only rename before any 0.16.x release. -- **`brokerKey` no longer leaves the Pi-side config.** The `/pair` response only returns `extensionPairKey`. Compromised extensions therefore can't extract the broker key used for peer-Pi `/command`. -- **Replay defense on response paths.** Added per-direction LRU nonce caches for `bridge->ext` (extension side) and `owner->peer` (peer-Pi side). The signature spec already covered these directions; the caches now actually enforce single-use nonces. -- **`reset()` and `persist()` chmod existing files.** `writeFileSync(mode)` is a no-op on an existing file; we now `chmodSync` to `0600` (and parent dir `0700`) on every write. -- **`readJsonBody` rejects non-object JSON** (null/array/string) with 400 instead of letting it through. -- **Top-level Content-Length precheck.** Oversized requests are 413'd before any auth/per-endpoint logic runs. -- **Stale-auth handling on the extension side.** When `/chrome unpair` is run on the Pi and the extension still has cached keys, the SW no longer loops on "bad /next signature". The unsigned-idle `{type:"none", needsPairing:true}` shape is accepted without a signature *only* when the bridge sends no auth header (never with a command), so the pair badge is surfaced and the user re-pairs from the popup. - -### Original 0.16.0 changes - -### Security hardening (red-team driven) - -- **Mandatory pairing.** New `/chrome pair` issues a one-time invite the user pastes into the Chrome extension popup. Pairing pins the exact `chrome-extension://<id>` origin and derives HMAC keys via HKDF. Until paired, the bridge returns idle responses and refuses to deliver commands. `/chrome unpair` resets. -- **Signed envelopes on every privileged endpoint.** `/next`, `/result`, and `/command` now require an `X-Pi-Pi-Auth: v1 ts=... nonce=... sig=...` header signing protocol version, direction, method, path, extension ID, bridge ID, timestamp, nonce, and SHA-256 of the body. ±30 s clock window, 5-min LRU nonce replay defense, constant-time MAC compare. Bridge responses are signed too; the extension and peer Pi sessions verify before accepting commands or results. -- **Peer Pi sessions no longer ship a bearer token.** `/command` between Pi processes uses per-request signatures keyed by the broker HKDF key, and the owner's response is signed for verification. A pre-bind impostor without the pairing secret cannot impersonate or extract the keys. -- **Global loopback enforcement.** Every endpoint rejects non-loopback `remoteAddress`. `PI_CHROME_BRIDGE_HOST=0.0.0.0` is ignored unless `PI_CHROME_BRIDGE_DANGEROUS_REMOTE=1` is set, and even then the loopback check still applies. -- **Resource caps.** 1 MiB body cap (413), 256 queued/pending commands, 4 concurrent `/next` long-pollers (429). Malformed JSON now returns 400 instead of 500. -- **`crypto.randomUUID()` for command IDs** (was `Date.now()+Math.random()`). -- **`page.navigate` initScript** is now wrapped in `try/finally` around `chrome.tabs.update` and deleted in `onCommitted` *before* injection, so an aborted navigation cannot leave a stale init script armed for the next URL the user visits in that tab. -- **Manifest diet.** Dropped unused `activeTab` permission. Added popup UI (`popup.html` / `popup.js`) for pasting the pairing invite. - -### Migration - -Existing installations must run `/chrome pair` once after upgrading. Old extensions in the field receive a benign idle response plus the `x-pi-chrome-version` header, which still triggers the existing auto-reload path; the reloaded extension then surfaces a `pair` action badge. - ## 0.15.20 — 2026-05-15 - **Interruptible `chrome_*` tools.** All `chrome_*` tools now honor the agent harness `AbortSignal`, so pressing Esc aborts in-flight bridge calls (including the long-polling `chrome_wait_for`) immediately instead of waiting out the full `timeoutMs`. |
