← Commit history

0.3.2: nbrowser_state must report only OUR sessions - it was leaking the user's own windows

John Lauer ·71f3a4f5a0 ·2mo ago ·parent 5c312d7
5 files changed +31−19
bridge/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.3.1+0.3.2
bridge/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "native-browser",   "displayName": "Adom Native Browser",-  "version": "0.3.1",+  "version": "0.3.2",   "expectedExtensionVersion": "0.2.0",   "description": "Drives the user's REAL signed-in Chrome/Edge, in two layers. Layer 1 (nb) needs NOTHING installed and works on any machine: nbrowser_open_os_window (open a URL in the user's real profile, background+flashing, the OAuth/consent path), nbrowser_os_windows, nbrowser_profiles, nbrowser_wake_profile, nbrowser_force_close. Layer 2 (nbe, the Adom Native Browser Extension) unlocks the rest: CDP driving (navigate/click/read/screenshot) inside their logged-in profile. The bridge binds an ephemeral loopback port and the native-messaging host pumps stdio <-> bridge.",   "homepage": "https://wiki.adom.inc/adom/nb-bridge",
bridge/package.json+1−1
@@ -1,6 +1,6 @@ {   "name": "adom-native-browser-bridge",-  "version": "0.3.1",+  "version": "0.3.2",   "private": true,   "description": "Native-browser bridge for Bridge (ab): layer 1 (nb) plus layer 2 via nbe, the Adom Native Browser Extension (Chrome + Edge).",   "main": "server.js",
bridge/server.js+23−11
@@ -1847,27 +1847,39 @@ function dispatch(verb, args) {     // A caller must never title-guess a browser window: we OWN the windows we opened, so we return the     // exact hwnd + tabId + live url/title per session, and optionally the page bitmap inline (base64),     // so a WEB page or an AI can render it without a second round-trip or any window hunting.+    // nbrowser_state - "what am I driving right now, and what does it look like" in ONE call (issue #22).+    // SOURCE OF TRUTH = this bridge's OWN session registries. list_windows also returns the USER'S+    // windows (owner:"user"), and an early build leaked those into state - reporting the user's Gmail+    // as if nb were driving it. We now report ONLY sessions we opened, and only enrich them with live+    // url/title from rows we own. A caller must never title-guess a browser window: we own the ones we+    // opened, so we return the exact hwnd + tabId per session, and optionally the page bitmap inline.     if (verb === 'nbrowser_state') {       const sa = args || {};       guarded(resolve, 'nbrowser_state', async () => {         const want = sa.sessionId;-        const wins = await callSelf('nbrowser_list_windows', want ? { sessionId: want } : {});-        const rows = (wins.windows || wins.sessions || []).filter((w) => !want || w.sessionId === want);-        const sessions = rows.map((w) => {-          const sid = w.sessionId;-          return { sessionId: sid, hwnd: sessionHwnd.get(sid) || w.hwnd || null, windowId: w.windowId ?? null,-            tabId: w.tabId ?? w.activeTabId ?? null, url: w.url || null, title: w.title || null,-            profile: sessionProfile.get(sid) || w.profile || null, thread: sessionThread.get(sid) || null,+        const mine = [...new Set([...sessionHwnd.keys(), ...sessionProfile.keys(), ...sessionThread.keys()])]+          .filter((sid) => !want || sid === want);+        let live = [];+        try {+          const w = await callSelf('nbrowser_list_windows', { mine: true });+          live = (w.windows || w.sessions || []).filter((r) => r && r.sessionId && r.owner !== 'user');+        } catch {}+        const sessions = mine.map((sid) => {+          const r = live.find((x) => x.sessionId === sid) || {};+          return { sessionId: sid, hwnd: sessionHwnd.get(sid) || r.hwnd || null, windowId: r.windowId ?? null,+            tabId: r.tabId ?? r.activeTabId ?? null, url: r.url || null, title: r.title || null,+            profile: sessionProfile.get(sid) || r.profile || null, thread: sessionThread.get(sid) || null,             purpose: sessionPurpose.get(sid) || null, osOpened: osOpened.has(sid) };         });         let shot = null;-        if (sa.screenshot && sessions.length) {-          const s = await callSelf('nbrowser_screenshot', { sessionId: want || sessions[0].sessionId });-          if (s && s.base64) shot = { sessionId: want || sessions[0].sessionId, base64: s.base64, width: s.width, height: s.height };+        const shotSid = want || (sessions[0] && sessions[0].sessionId);+        if (sa.screenshot && shotSid) {+          const s = await callSelf('nbrowser_screenshot', { sessionId: shotSid });+          if (s && s.base64) shot = { sessionId: shotSid, base64: s.base64, width: s.width, height: s.height };         }         resolve({ success: true, output: JSON.stringify({ ok: true, count: sessions.length, sessions, screenshot: shot,           _hint: sessions.length-            ? `The EXACT windows this bridge is driving - hwnd is authoritative, so never match a browser window by TITLE (a title is the active tab and changes while loading; several windows can share a substring - that is what broke the web-control demo, issue #22). Pass screenshot:true to get the page bitmap inline (base64) in the same call. Act on a window with its hwnd (ab desktop_screenshot_window {hwnd} for the frame + native popups) or its sessionId (nbrowser_* for the page).`+            ? `The EXACT windows THIS bridge opened - hwnd is authoritative, so never match a browser window by TITLE (a title is the active tab, it changes while loading, and several windows share substrings - that is what broke the web-control demo, issue #22). The user's OWN windows are deliberately excluded (nbrowser_os_windows shows every browser window on the machine, with ownership attribution). screenshot:true returns the page bitmap inline in this same call.`             : 'This bridge is driving NO windows right now. Open one with nbrowser_open_window {sessionId, thread, purpose, profile, url} - it returns the hwnd + tabId of exactly that window.' }) });       });       return;
native-browser-bridge-manifest.json+5−5
@@ -1,8 +1,8 @@ {   "manifest_version": 1,-  "version": "0.3.1",-  "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.3.1/native-browser-bridge-v0.3.1.zip",-  "sha256": "de4a1252d3658cf71460a8bb6333c2a6c18613d588e7f7ee677dc60a4487e444",-  "size": 106040,-  "released_at": "2026-08-19T08:16:46Z"+  "version": "0.3.2",+  "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.3.2/native-browser-bridge-v0.3.2.zip",+  "sha256": "1e4c38dc7a08130301db381609f62f9415efeeb064589be65756dfa3275be372",+  "size": 106310,+  "released_at": "2026-08-19T08:20:40Z" }