Repository navigation
Releases: pironjulien/NexusBrowser
Release list
NexusBrowser 2.12.1
This release also ships 2.12.0, which was not released on its own.
2.12.1
Security
- GitHub Actions pinned by commit SHA, checkouts without persisted credentials, release metadata passed through the environment (no template expansion in scripts), no build cache in the release workflow. The MCP registry upload is now a job of the PyPI workflow (no more
workflow_runtrigger). Dependabot (actions and uv, 7-day cooldown) and an OpenSSF Scorecard workflow added;zizmorreports no finding.
Fixed
- Document five historical synthetic network-response fixtures using exact Gitleaks fingerprints; no global rule or path exemption. New changes and history remain scanned.
- Installation freshness includes extension and client sources. Existing connected extensions are checked and refreshed automatically; the supervised Browser MCP worker is reloaded independently. Installation defers while an agent owns tabs, waits for the bridge's reconnect, and does not record a successful update when refresh fails.
- Use Chromium's computed full accessibility tree for role/name matching before the existing
queryAXTreefallback. The latter was measured waiting 1.5 seconds on the main document with iframes; computed accessible names and role filtering remain covered by regression tests. - Resolve the outward-action probe in the isolated context that owns a mark, including cross-origin and out-of-process iframes. The existing mark registry selects the context; the accord check remains active. Live ambiguity checks accept both cases in the English error message.
- Acknowledge the temporary CDP screencast frames used for background screenshots. Unacknowledged frames could stall repeated captures; frame pixels are never retained by the event handler. Loaded-code detection includes
capture-screencast-ack. - Shared daemon (
--http): a client that resets its connection no longer makes the Windows Proactor loop log a traceback (ConnectionResetErrorin_call_connection_lost); only that error is silenced, every other loop error is still logged. scripts/registre-reel.ps1: escape%as%%in generated.cmdbatch scripts so arguments like%1are not swallowed bycmd.exewhen setting registry values.
2.12.0
Added
- Outward gestures need the user's accord for the exact action (trinity agreement of 2026-10-19,
"ameliorations-outils", priority 1).browser_click,browser_semantic_click,browser_click_mark,
browser_press_key(Enter, Control+Enter, Space),browser_type_keys(line break or submitting keys),
browser_fillandbrowser_semantic_fill(submittingkeys) andbrowser_drag_and_droptakeconfirmand
accord. Before the gesture, the server reads the trigger without any gesture, through the bridge's existing read
paths only (find, and the read-onlyclient/trigger_probe.jsevaluated bycdp-sendin the isolated world; no
new extension action, works with the extension already loaded): label of the clicked control, or submit buttons of the focused field's form; that form's fields,
sensitive values masked; recipients from email/tel/"to" fields andemailchips; file attachments; tab origin,
path and title). When the shared policy sees an outward word (send, pay, buy, order, publish, post, call, transfer,
subscribe, book...), the gesture is refused with a summary of the exact action and a one-time token (10 min); it
runs only when sent again withconfirm=trueand that token, unchanged.confirm=truealone, a token read in a
page, a token for another action or a changed field, recipient, typed text or page are refused. Neutral links and
buttons stay free. Fails closed only when the read really fails. OneAccordsobject per server process. - The CLI runs the same check; since each command is its own process it cannot carry a token, so it refuses outward
gestures with the summary of the action and points to the MCP server (other gestures stay free). - Page text outputs (
browser_content,browser_axtree,browser_export,browser_export_gsuite,
browser_youtube_transcript,browser_network_response) carryuntrusted_notewhen the text looks like
instructions aimed at an AI (signal only), and are recorded as text read (provenance of a recipient). - The shared guardrail policy ships inside the package (
client/garde_fous.py,client/garde-fous.json, identical
copies of NexusHarness/shared; a test checks it when that folder exists). - Tests:
tests/test_outward_accord.py(offline mail page: send refused without confirm, token flow, changed field,
Enter toward a Send button, neutral link, concurrent paths, page injection, CLI across processes, embedded policy)
and a Node test oftrigger_probe.json a fake page (read-only, password never read).
Changed
browser_evalandbrowser_cdp_sendkeep full page access by choice; their descriptions, the server
instructions, README and SECURITY.md now say plainly that they do not go through the accord and must never be used
for an outward action.- The real validation (
test.ps1 -Full) clicks its "Valider la commande" and "Envoyer" buttons through the accord
flow. Extension and bridge code are unchanged (version number only): no reload needed for the check to work.
2.11.0
Changed
- Everything an MCP client receives is now in English: the 48 tool descriptions, parameter descriptions and every
message returned to the model (server, bridge, native host, extension: errors, refusals, warnings, reports).
Same meaning, same guardrails. Texts shown only to the user (side panel, on-page notices) stay in French. - Masked values read
[masked value](was a French placeholder). browser_adopt_tab: the formerjulien_askedalias is no longer exposed; useuser_asked=True. A tab nobody
drives has state"user"inbrowser_list_tabs(was"julien").
Added
tests/test_english.py:tools/list(descriptions, titles, input schemas) and a series of offline error and
refusal responses must contain no French. The anti-steering test now also covers parameter descriptions.
NexusBrowser 2.10.1
Changed
- Server instructions no longer compare this bridge with other tools ("Prefer this bridge to the client's own
browser extension"): they now list what it is for, in neutral words, as the Anthropic directory requires (a server
must not steer the model toward its tools or away from other ones). A test keeps such wording out.
Added
.github/workflows/registry.yml: after each release reaches PyPI,server.jsonis published to the official MCP
registry by GitHub OIDC (no token, no manual login), as NexusAndroid does.
NexusBrowser 2.10.0
Changed (neutral texts for any client, 2026-10-19)
- Server instructions sent to every MCP client are now in English and neutral ("the user"), no personal name.
Tool descriptions, parameter descriptions and messages returned to the model (server, bridge, extension) say
« l'utilisateur » too; they stay in French. Same rules and flow. A test checks thatinitializeand
tools/listname nobody. browser_adopt_tab(tab_id, user_asked=True): neutral name for the consent flag.julien_asked, its former name,
still works as an alias; the bridge sendsuserAsked(read by the extension from this version) andjulienAsked
(read by older extensions). CLI:adopt-tab --user-asked(--julien-askedkept).- Side panel greeting is « Bonjour »; a tab nobody drives is labelled « Utilisateur » in the control panel.
Changed (Codex plugin, 2026-10-19)
install.ps1no longer writes[mcp_servers.nexus-browser]to Codex'sconfig.tomlnor copies the skill to
~\.codex\skills: Codex gets both from a personal Codex plugin.
Fixed (2026-10-19)
- Native messaging host registered in the real
HKCUregistry (scripts\registre-reel.ps1:reg.exerun by a
one-shot hidden scheduled task). Run from a packaged (MSIX) app such as the Claude desktop app, the write went to the
app's private registry and Chrome reported « native messaging host not found »: the extension never connected. is_chrome_running:tasklistoutput read as bytes; withPYTHONUTF8=1its OEM code page (French « Numéro »)
crashed every tool call made while the extension was offline.icaclsoutput decoded with the OEM code page too.- Antigravity showed the nexus-browser skill twice (global copy + plugin):
install.ps1keeps it in the plugin only
and moves the old global copy to a local backup.
Changed (Claude Code plugin, 2026-10-19)
install.ps1no longer registers the server with Claude Code nor copies the skill to~\.claude\skills, and never
calls a Claude CLI: Claude Code gets both from a Claude Code plugin. Codex and Antigravity are unchanged.
Changed (YouTube transcript visible in Antigravity, 2026-10-19)
browser_export_youtube_transcriptis renamedbrowser_youtube_transcript: Antigravity publishes each tool as
mcp_nexus-browser_nexus-browser_<tool>and silently drops any name over 64 characters, so it never saw this one
(65). No client used the old name; the bridge actionexport_youtube_transcriptis unchanged. A test now checks
that every tool name fits once prefixed.
Added (faster Antigravity panel, 2026-10-18)
browser_fill_form(tab_id, fields): fills a whole form in ONE call, field by field with the same gestures as the
single-field tools (browser_fill,browser_select_option,browser_check): every
field is validated before the first gesture, the emergency stop is checked before each field, it stops at the
first field not confirmed (error, dialog opened by the page, value refused or put back), then reads the whole
form back with no gesture (new read-onlyfield-statebridge action, compared inside the page so a sensitive
value never leaves it):all_verifiedonly if every field still holds its value. It never submits. Measured
on the real panel bench: 5 fills and a separate read-back took 6 model turns (~18 s) of the 61 s.- Its fields are aimed at by their
markfrombrowser_interactive_maponly: a reference to the element itself, kept
from the gesture to every read-back. A label or selector would be evaluated again at each read and could name
another field after the page changed (Codex cross-review); those stay with the single-field tools. A field the page
removed or replaced fails its read-back, and a multiple<select>must hold the wanted option alone.
Each field is read back right after its gesture and again at the end; a mark on a label or a container (redirected
to its current control), or on anything that is not an input (a label left without its control), is refused at
read-back: the mark must be the field itself. A gesture's own refusal is never cleared by a later read-back.
When a batch stops, the fields already filled are read back again with no gesture (their state when filled is never
reported as their current state; unknown when a dialog blocks the page). browser_interactive_mapalso numbers rich editors (contenteditable,plaintext-onlyincluded,role=textbox), labelled by their
aria-labelor title only: their content is input, possibly sensitive, never a label.
Fixed (Codex cross-review, 2026-10-18)
- A sensitive field's state masks every representation of its value, not only
value:text,selectedValueand
selectedTexttoo (a card-expiry<select>or a passwordcontenteditableleaked them throughselect_option).
Added (shared daemon, 2026-10-19)
- Shared daemon mode:
client/mcp_server.py --http [port]serves MCP Streamable HTTP on 127.0.0.1 (port 16182), one
process for every Claude, Codex and Antigravity session instead of one per session. Bearer token required
(NEXUS_MCP_TOKENorNEXUS_MCP_TOKEN_FILE); the agent comes from the URL (?agent=codex, refused otherwise);
each MCP session gets its own identity (its tabs), is heartbeaten by the daemon while open and goes silent once
closed, so its tabs become orphans for its agent.install.ps1declares the daemon to the Nexus supervisor and
registers the three clients by URL. A session idle for 161.8 s (no request, no open stream) is closed, so a client
that left without a DELETE frees its tabs in ~3 min instead of 30; a new session beats (and reclaims) within a
second (review by Antigravity).
Changed
browser_new_tab(group)andbrowser_group_tabs(title)default to the current session's agent name
(group="": no group).
Fixed
- Side panel shortcut is now Alt+Shift+Y: Chrome took Alt+Shift+N for split view ("Select a tab") and silently
dropped the extension's key. The shortcut handler also opens the panel at once with the tab Chrome gives it:
waiting for a tab query first lost the keyboard gesturesidePanel.openrequires, so the panel never opened.
An existing install keeps its old setting: set the key in chrome://extensions/shortcuts.
Added
- Antigravity side panel: Julien types a request in Chrome's side panel and Antigravity does the real work on the
tab (read, click, fill forms) with its nexus-browser tools, then its answer shows up in the panel. Path agreed by
Claude and Antigravity (blind debate "panneau-agy-chrome", 2026-10-18): panel -> service worker -> signed bridge
link (agy_askevent) -> Nexus Relay mailbox, from "julien" to Antigravity (its sidecar wakes it at once) ->
Antigravity adopts that tab (julien_asked=True: the typed request is Julien's explicit consent for that tab
only), acts, releases it, replies -> mailbox "julien" -> bridge -> panel (agy_panel_update, reserved to the
bridge: no agent can speak for Antigravity). Nothing is queued in the bridge's memory (what made the former chat
panel unreliable); without Nexus Relay installed, the panel says so. Thread kept inchrome.storage.local. - Panel look inspired by Google's assistants (greeting, suggestion chips, pill composer, light/dark), own palette and
no brand. The control station stays (tab "Contrôle": piloted tabs, release a tab, bridge state) and STOP is always
visible in the header.
Fixed (Codex cross-review of the Antigravity panel, 2026-10-18: three passes, each "Accord : non", then fixed)
- One request identity end to end: the panel's
localIdis written INSIDE the Nexus Relay message and comes back
with the reply. The relay files a request once per id, even if it died between filing and indexing (it finds the
message again by that id), so the extension resends an unconfirmed request every 15 s for as long as needed,
counting only sends that actually left Chrome, and never files it twice or restarts the work; a reply finds its
request even if the "filed" confirmation was lost. An unknown relay outcome (crash, timeout, non-zero exit even with
readable JSON) is never shown as "not sent"; only an explicit relay refusal is. - One request at a time, enforced in the service worker's single queue (test and reservation in one step, shared by
every panel of the profile); "Effacer l'affichage" keeps the active request; only an explicit "Abandonner" frees
the panel. The relay's transcript fallback never attributes a final answer when two requests overlap in one
conversation (neither of them gets one; only Antigravity's explicit reply does). Resends are scheduled by the
service worker itself, so they go on with the panel closed (and the link alarm catches up after a sleep). - Julien's consent is bound to HIS request: Antigravity adopts with
panel_request=<request id>; the extension checks
that this request exists, has not expired (30 min), targets that tab, and that the tab still shows the exact page
(full address, fragment included: hash routers carry the record id), before any other path ("already adopted"
included), and again after ownership is recorded (a navigation during the awaits is caught and ownership removed).
Missing or expired consent means refusal. Adoptions Julien asked for elsewhere are unaffected. The target tab is
frozen at the first keystroke (seen live: another agent switched the active tab while typing) and the page seen
while typing travels with the request: if the tab moved to another page, nothing is sent. If Julien himself
navigates a panel-adopted tab elsewhere (address bar, bookmark, back/forward:webNavigation), the tab is handed
back to him; the work's own navigations (links, form submits, reloads) go on. - The thread lives in
chrome.storage.local, restricted to trusted extension contexts (`setAc...
NexusBrowser 2.9.0
Project page: https://julienpiron.fr/nexusbrowser/
Re-run install.ps1, then RELOAD the extension (capabilities capture-field-masking and console-masking
advertised by ping).
Security
- Screenshots, PDFs and Set-of-Mark captures no longer show a password revealed by a "show" button, a card number or
a code: beforePage.captureScreenshot/Page.printToPDF(also throughbrowser_cdp_send), sensitive fields of
every frame (shadow DOM, same-origin and cross-origin iframes) get-webkit-text-security: disc, and their own
inline style is put back right after. A capture whose page cannot be masked (dialog open) is refused. Checked in
the real Chrome (test.ps1 -Full): two values of the same length give the same image, the same field shown as an
ordinary field gives a different one (screenshot, PDF, cross-origin iframe). - Sensitive fields are also recognized by name or id, camelCase included:
cardNumber,cc_number,cardCvc,
otp,pin,securityCode,iban... (ccalone,spinner,passenger,optionare not secrets). browser_logsmasks console messages like network bodies: secret keys, URL parameters, object previews
({name, value}), tokens quoted in a text (Bearer …,ghp_…, JWT).- Network log: card number, card code, one-time code, PIN and IBAN keys masked in bodies and URL parameters.
NEXUS_CHROME_EXTRA_ARGSis now an allowlist (--lang,--accept-lang,--variations-override-country,
--disable-featureswithGlic*features only,--force-dark-mode): the former blocklist let through flags that
weaken Chrome (--disable-web-security,--disable-features=IsolateOrigins...).
Fixed
- Element screenshots (
selector,mark) were blank when Chrome's page zoom is not 100% (seen at 90%): the clip is
now converted with the page zoom reported byPage.getLayoutMetrics.
Added
-
Real Chrome test of masking in a cross-origin iframe (reads and capture).
-
Real Chrome check that Julien's tabs are left intact now ignores tabs of other agents (they may close theirs during
the run). -
Every MCP tool has a readable
titlein its annotations, next to its four hints. -
Startup test: the MCP server answers
initializeandtools/listwith no Chrome, bridge or extension, on Windows
and on Linux (as MCP directories such as Glama check it); outside Windows the emergency stop keeps watching its
file and skips the global hotkey. -
GitHub Actions:
test.ps1on Windows and the startup test on Linux, with a Tests badge;glama.json. -
README: Privacy section.
Changed
- Published as NexusBrowser in the public repository
pironjulien/chrome-computer-use(formerly
antigravity-chrome-sidepanel, thenantigravity-browser-use; GitHub redirects the old URLs). README rewritten in
English for public use.
NexusBrowser 2.8.1
Neutral Chrome bridge shared by Antigravity, Claude and Codex (47 MCP tools). First public release since 2.4.0 ("Antigravity Browser Bridge"). Project page: https://julienpiron.fr/nexusbrowser/
2.8.1
Re-run install.ps1, then RELOAD the extension (capability capture-screencast advertised by ping).
Fixed
browser_screenshotandbrowser_vision_inspectfroze for 12 to 20 s (bridge timeout) in a background tab:
Chrome stops drawing frames for a hidden tab, soPage.captureScreenshotwaited after any scroll or change.
Measured on the real Chrome (October 18, 20 captures alternating with scrolls): 11 to 13 freezes out of 20 for the
plain capture,Emulation.setFocusEmulationEnabledandcaptureBeyondViewport(still 7/12), forcing the device
metrics (7/12);fromSurface: falseis refused by Chrome ("Only screenshots from surface are allowed"). A tiny
Page.startScreencast(16×16, never read) held only for the duration of the capture keeps the renderer drawing:
0 freezes out of 40, about 0.1 s per capture, correct image of the scrolled view; the tab stays in the background.
If Chrome refuses the screencast, the capture runs as before; it is always stopped, even when the capture fails.
Changed
- Chrome relaunch (
launch_chrome): only--profile-directory=Defaultby default. Extra flags are opt-in
through theNEXUS_CHROME_EXTRA_ARGSenvironment variable (--nameor--name=valueseparated by spaces);
anything else, and flags that load or run code (--load-extension,--remote-debugging-*, command prefixes,
--user-data-dir,--no-sandbox...), are ignored. - Agent guide and docstrings: repository-relative paths instead of the author's folders.
Removed
- Live validation: the 3 retries of
browser_vision_inspect(gels_de_capture) are gone, a single capture is
expected to succeed.
2.8.0
Re-run install.ps1 (with Antigravity closed), then RELOAD the extension in chrome://extensions (capabilities
sensitive-field-masking, cdp-reload-no-script and ax-value-masking advertised by ping), then open a new
conversation in each agent.
Security
- Plaintext passwords (severe):
browser_content(default interactive mode),browser_vision_inspect,
browser_findand the read-back ofbrowser_fillreturned field values, passwords included;browser_axtree
returned card numbers and codes (Chrome only masks passwords there, observed in a real run). The value is never
returned for atype=passwordfield or one whoseautocompleteis current-password, new-password,
one-time-code, cc-number, cc-csc or cc-exp (including a "show password" field switched totype=text):
[valeur masquée]is returned instead.browser_content(mode="html"): thevalueattribute of these fields is
masked in a copy of the DOM (React copies it from the input), the page itself is untouched.browser_fillstill
reads the real value back from the page (verifiedstays accurate); only the output is masked, including the
expected value and the JavaScript fallback. A single definition of sensitive fields, in the localization kit,
injected into every page script. - Antigravity review (October 18): the rendered text of a sensitive field (textarea, contenteditable) was not
masked; a "show password" field without a recognized autocomplete is now identified by its name or id (password,
pwd, mot de passe, mdp, cvc...), never a container; non-input components carrying a sensitive autocomplete are
masked in the accessibility tree and the HTML. browser_cdp_send:Page.reloadacceptedscriptToEvaluateOnLoadpassed through unchanged (a script injected
into the page world on load, likePage.addScriptToEvaluateOnNewDocument, which is refused). Now refused before
reaching Chrome, with a clear message; a plain reload is still allowed.
Fixed
- Incorrect promise: "cookies sealed outside localhost".
browser_evalandRuntime.evaluate(both worlds share the
document) can readdocument.cookie(non-HttpOnly cookies), localStorage, sessionStorage, IndexedDB and the result
of afetchsent with the user's session, without masking. Kept as is (maximum autonomy; pattern-based masking
would be bypassed by the slightestbtoa) and stated clearly in the tool, the server instructions, the skill and
the README: masking applies to the dedicated tools, not to a script nor to rawbrowser_cdp_sendresponses
(DOM.getOuterHTML,Accessibility.getFullAXTree).
Changed
- MCP SDK 2.3.0 (instead of 1.30.0; the 1.x branch only receives security fixes):
MCPServer(formerly
FastMCP), advertised version read fromVERSION. - MCP annotations on all 47 tools (
readOnlyHint,destructiveHint,idempotentHint,openWorldHint): page
reads, local reads, tab management, navigation, and page actions marked destructive (in the user's real sessions, a
click, an Enter key press, a checked box or a script can send, delete or confirm). A tool marked "read-only" only
sends the bridge commands from its read list (tested), with one deliberate exception:browser_get_cookies, which
modifies nothing but is still blocked by the emergency stop. - Tools are typed exactly (the decorator no longer turned them into
Any): mistyped test calls fixed.
Tests
- Node: content, visual map, search, HTML and accessibility tree contain no sensitive value; typing into a password
field, into a card field rewritten by the page, and through the JavaScript fallback returns no value;
Page.reloadwith a script refused before reaching Chrome. Mutation checked: without masking, the three masking
tests fail. - Python: SDK 2, complete annotations consistent with the bridge read list, accurate wording about
browser_eval
(tool, instructions, both copies of the skill, README). - Real-world validation (
--full-live): password and card never read (content, HTML, visual map, search, Chrome
accessibility tree), password entry verified and masked,Page.reloadwith a script refused.
Known issues
Page.captureScreenshotsometimes hangs for 12 to 20 s in a background tab (bothbrowser_screenshotand
browser_vision_inspect, measured on October 18, unrelated to this version): the real-world validation retries the
visual map up to 3 times and counts the hangs. Root cause to be addressed separately.