Version 3.0.0 implements authentication and removes the privileged page bridge that blocked the earlier source. Local regression and browser verification are documented in docs/VERIFICATION.md. This is not an independent security certification.
This document describes what the code is designed to do. It is not an audit, and it does not claim that ScriptBridge has no vulnerabilities.
Was: 127.0.0.1:9876 accepted any request with wildcard CORS and no token, so any web page in a local browser could create, run, schedule and delete scripts, read local files through file watch, and read script code, env vars and settings.
Now (companion/src/security.rs, one guard in front of every route):
Hostcheck. TheHostheader must be exactly127.0.0.1:9876orlocalhost:9876, and appear once. Other hosts get403, which blocks DNS rebinding.Origincheck. If a request carries anOrigin, it must be achrome-extension://<id>origin. Web origins get403. For every endpoint except/api/pair, the id must be the paired extension.- Mandatory token. Every
/api/…request, including/api/pair, must sendAuthorization: Bearer <token>. The token is 64 hex characters, compared in constant time. Missing or wrong tokens get401. There is no setting that turns this off. - No wildcard CORS. CORS headers name only the requesting extension origin. Preflights allow only the route's real methods and the
authorization/content-typeheaders. - First-extension pairing.
/api/pairneeds an extensionOriginand a valid token. The first extension that pairs is saved inpairing.json; another extension then gets409. Requests with noOrigin(local tools) can use the API with the token but cannot pair. - Local token file, no HTTP getter. The companion generates the token on first start and writes it to
pairing.jsonin its data folder. No endpoint returns it. The user copies it into the extension's Companion page. - Revocation. Delete
pairing.jsonwhile the companion is stopped, then start it: a new token is generated and the old pairing is gone. Forget Local Token in the extension removes only the extension's copy; it does not unpair the companion. - Schedules paused on migration and on pairing reset. When the companion starts without
pairing.json(the first 3.0.0 start, or after a reset) it disables every existing schedule, because those schedules may have been created by a web page. No schedule runs until an extension is paired. Re-create the ones you still want from the paired dashboard. - Legacy security settings removed. The old Settings page's API auth, API token, allowed origins and CORS values never did anything. They are now stripped from
settings.jsonon load and on save. - Legacy dashboard retired.
/is a static information page with a strict CSP. The old embedded dashboard, settings page and guide are no longer embedded or served; other non-API paths return404. - Input limits. Script ids are restricted to safe file names. Dependency checks run only a fixed allowlist of programs with fixed arguments. File watch accepts only drive-letter absolute paths (no UNC or device paths, which could send Windows credentials to a remote host, no drive-relative paths, no alternate data streams) and refuses the companion's own data folder, so
pairing.jsoncannot be read back through a script.
Was: content.js ran on every page and relayed any page script's postMessage to the extension to run external scripts, make GM_xmlhttpRequest calls with the extension's <all_urls> host permissions, use GM storage and show notifications.
Now the privileged page bridge is removed on purpose, not gated:
manifest.jsonregisters no content scripts and no longer requestsnotifications.content.jsandscriptbridge-api.jsare inert and not injected. Ifscriptbridge-api.jswere injected,ScriptBridge.run/runById/notifyall reject without contacting the extension.gm-polyfill.jshas no channel to the extension.GM_xmlhttpRequest,GM.xmlHttpRequestandGM_downloadreport an error;GM_notificationonly logs.background.jsanswerschrome.runtimemessages only from this extension's own top-frame UI pages (popup, dashboard, editor, install). Everything else getsForbidden.- External scripts run only from extension UI: the dashboard, the extension context menu, and chains or schedules created there. Only enabled scripts run.
What this does not change:
- Browser userscripts still run in the page's MAIN world. They are not sandboxed or isolated from the page. The site can observe and interfere with them, and they share the page's trust level.
- GM values are page-local and visible. Sync
GM_getValue/GM_setValueand asyncGM.getValue/GM.setValueboth use the page'slocalStorage(prefix_SB_GM_). Any script on that origin can read and change them. They are not a safe place for secrets.
@updateURLupdates are never applied silently, and update checks no longer open tabs on their own. The dashboard lists available updates; the install page shows a diff, and only the code you reviewed is saved when you confirm.- Install and update downloads (
script-url-policy.js) allow onlyhttp(s)URLs without credentials, refuse redirects, send no credentials, and blocklocalhost,*.localhostand literal loopback, private, link-local, CGNAT, documentation, multicast and reserved IPv4 addresses and non-global IPv6 addresses. Limit: a public hostname whose DNS resolves to a private address is not blocked. Update URLs that redirect (for example Greasy Fork) fail by design, and userscripts served fromlocalhostor LAN dev servers can no longer be installed or updated by URL. - The worker validates every saved script record (id, category, language, field types). Malformed legacy records are hidden and never run, but are left in storage.
- Imported external scripts are disabled, with a manual trigger, until you review and enable them. Companion imports also disable scripts; overwritten ids lose schedules. Deleting a script removes its schedules; resetting data clears schedules and history. Browser-category scripts cannot run natively through run, stream, chain or schedule routes. Chains validate every entry before starting and contain at most 20 scripts.
- Companion requests from the extension go only to the fixed loopback address, with no credentials, no redirects and no caching. The token is kept in
chrome.storage.localrestricted to trusted extension contexts.
The companion is designed for one trusted Windows account on one machine. Its goal is to stop web pages from reaching native execution. It does not protect against:
- Other processes running as the same user, or malware. Anything that can read your profile can read
pairing.jsonand use the API. - Privileged local processes, such as administrators or services.
- Impersonation of the companion. The API is plain HTTP on loopback. The token authenticates the client to the companion; it does not prove the server's identity. A process that takes port 9876 first can pose as the companion and receive the token the extension sends.
- Multi-user or shared setups. It is not a multi-user service. Do not share a data folder (
SCRIPTBRIDGE_DATA_DIR) between accounts.
The protection of pairing.json, your scripts and env vars relies on your Windows user profile's file permissions. Env vars and script code are stored as plaintext JSON.
External scripts are not sandboxed. They run with your full Windows user permissions. Review any script from an untrusted source before enabling it. Do not run the companion as Administrator. In addition:
- There is no run timeout and no output size cap. A script that hangs or prints without end keeps running.
- Closing a streamed (SSE) run in the UI does not stop the child process.
- Per-script
env_varsare passed to the runtime and can change what it loads or runs (for examplePATH,PYTHONPATH,NODE_OPTIONS). Treat env vars from an untrusted source like code. - The Rust/C build cache is keyed on a 64-bit FNV-1a hash of the source. It is a cache key, not an integrity check: it does not detect a tampered cached executable in the data folder.
- The old dashboard's execution settings (timeout, output limit, admin, working directory) did nothing and are removed, not implemented.
The release is built from tracked source, with dependency notices matched to Cargo.lock and SHA-256 checksums. See verification evidence and limits. Passing tests do not establish that all vulnerabilities are absent.
Report vulnerabilities privately to the maintainer, Chris Mangin. Do not put tokens, private scripts or exploit details in public issues. A dedicated private reporting channel is not configured.