Use Oxys DB Studio in a browser

Run the web gateway on your machine and use the app in a browser: one WebSocket, every database connection made by the gateway.

The browser version is the desktop app compiled to WebAssembly. The page itself reaches nothing but the web gateway, a small program that runs next to it: the gateway opens the MongoDB connections, SSH tunnels and files, with the same code as the desktop app, and the page talks to it over a single WebSocket. Your passwords and drivers never reach the browser.

Start it

oxysdb-web-gateway --open

The gateway listens on 127.0.0.1 on a free port and opens your browser on an address such as http://127.0.0.1:41234/#token=3f9c…. The part after # is an access token made for this launch only. The page keeps it for the tab, removes it from the address bar, and offers it when it connects. Do not share that address. Stop the gateway with Ctrl+C.

The gateway uses your usual Oxys DB Studio folders, so your saved connections are there. Only one page is connected at a time: opening the address in another tab disconnects the first one.

From a source checkout, cargo xtask web --open builds the WebAssembly bundle and starts the gateway on it.

Options

OptionEffect
--openopen the page in the default browser
--bind ADDR:PORTwhere to listen (default 127.0.0.1:0)
--assets DIRfolder with oxysdb_web.js and oxysdb_web_bg.wasm
--tls-cert FILE, --tls-key FILEserve HTTPS and WSS with this PEM certificate and key (reloaded when they change)
--tls-pq prefer or requirepost-quantum key exchange policy (see TLS)
--origin URLan address browsers will use, such as https://db.example.com:8443 (repeatable)
--staging DIRwhere files you open or save in the browser are kept during a session (default: the system’s temporary folder)
--max-file-mib Nthe largest file you can open or save in the browser (default 1024)
--files-quota-mib Nall the files of one session together (default 2048)
--allow-local-processeslet pages run this machine’s programs (IntelliShell, external tools, SQL drivers) and use its key files
--ai-endpoint URLan AI provider address pages may use besides the providers’ own (repeatable)
--allow-network-settingslet pages change the gateway’s proxy settings

Opening and saving files

File dialogs are your browser’s. When you open a file, such as an import source, a pipeline or a script, the browser asks you to choose it on your computer and uploads it to the gateway, for your session only. The banner shows the upload’s progress, and the dialog continues on its own when the file is there. Most dialogs do. If one does not, click its button again once the upload is done.

When you save a file, such as an export or a script, no file dialog opens: when the export or save is done, the file comes back to your browser as a download, under the name it was given.

Folders work too. When a dialog asks for a folder to open, such as a mongodump folder to import, the browser asks you to choose a folder and uploads every file in it (hidden files are left out), keeping its sub-folders. If one file cannot be uploaded, nothing of the folder is kept. When a dialog asks for a folder to save into, such as the export’s target folder, a mongodump folder export or Save To Disk of several GridFS files, no dialog opens: when the export is done, the whole folder comes back as one download, a zip file named after the folder.

The page never names a file on the gateway’s machine. A path typed into a file field is refused, and imports and exports read and write only the session’s own folder. That folder is private to the gateway’s user and deleted when the session ends. Commands that would reach other files of the gateway’s machine, such as log folders, fonts, reports, task definitions and Load the connections from a local folder, are not available in the browser.

Programs and key files

A browser page cannot start programs on the gateway’s machine: IntelliShell, script tasks, tasks, external mongodump and mongorestore, SQL drivers and startup scripts say they are not available (imports and exports use the built-in tools). A connection cannot use the machine’s TLS certificates, SSH keys, known_hosts file or SSH agent either: choose the key file with Browse…, which uploads it. Nor can it sign in with the machine’s own identity: AWS without an access key, a cloud workload identity (OIDC ENVIRONMENT) or Kerberos, whose tickets and keytabs are the machine’s.

When you save the connection, the gateway keeps each uploaded key file (root CA file, client certificate, SSH private key) in its credential store, with the connection, like a saved password: the field then shows the file’s name and says the gateway keeps it. The page can never read a kept key back, and exports, connection strings and shared connections never include it. A kept key goes only where it was saved for: if you change the connection’s server, SSH server or security settings, the gateway forgets the keys and you upload them again. To connect, the gateway writes the key to a private file that only it can read, removed when your session ends. Through an SSH tunnel, the connection keeps its own list of known host keys: the first time, you are asked to accept the SSH server’s key, and it is remembered for that connection. Oracle, DB2 and Sybase connections, which need a helper program, are not available. The menus of the browser version hide what the gateway refuses.

The gateway’s proxy settings, and the AI Helper’s proxy, belong to whoever runs it: a page cannot change them unless the gateway runs with --allow-network-settings. Everywhere, a saved password goes only to the server it was saved for: changing a proxy, a connection’s server, an SSH server or an AI provider’s address forgets the saved password or key, and asks for it again.

Saved passwords and passphrases never reach the page: exporting connections with their passwords, copying a connection string with its password and sharing a connection with its password are not available in the browser.

The AI Helper sends the gateway’s API key only to the providers’ own addresses. To use another address, such as a company proxy, start the gateway with --ai-endpoint https://llm.example.com.

--allow-local-processes lifts this. Use it only on a gateway that serves you alone: IntelliShell runs mongosh, which can read and run anything the gateway’s account can, so anyone who holds the page’s address and token can then do everything that account can do on that machine. It cannot be used with --public-demo.

Scheduled tasks

A browser page has no scheduler of its own: in the browser, Schedule hands the task to the remote task runner, which runs it on its schedule, whether a page is open or not. The Task schedule editor says so. When you click Save, the task, its schedule and the connections it uses go to the runner, and the status bar says the task is scheduled on the remote task runner. If the desktop app had a local schedule for that task, that schedule is turned off, so the task never runs twice. The task runs on the runner’s machine, so its files and exports are read and written there, in the runner’s files folder, not on your computer and not on the gateway’s.

The runner is set up by whoever runs the gateway, on the gateway’s machine: in the desktop app, Preferences > Remote Task Runner (its address and access token), or with oxysdb-cli run runner.configure. A page cannot see or change that setting, because the runner receives the saved passwords of the tasks’ connections. Until a runner is set up, Save says what to configure and changes nothing.

In the browser, the Tasks view lists your tasks and their runs, schedules them on the runner, sends them with Run on Remote Runner, and shows, runs and deletes the runner’s tasks. Running a task in the gateway itself, editing, unscheduling and deleting tasks are not available, even with --allow-local-processes. The public demo has no tasks.

When you open the Task schedule editor again for a task the runner holds, it shows the runner’s schedule, and says so. If the runner does not answer, the editor opens on a new schedule and says why.

Serving it to other machines

To bind anything other than the loopback address, you must pass a certificate. When you bind 0.0.0.0, you must also pass --origin with the name on the certificate. Without them, the gateway refuses to start. Anyone who has the printed address can use your connections, so send it only over a channel you trust.

What the gateway enforces

  • The WebSocket is accepted only from the gateway’s own page (Origin), and only with the token.
  • Requests for any other host name are refused, which blocks DNS-rebinding attacks.
  • The page may connect to the gateway only, and no other site may frame it (Content-Security-Policy).
  • Messages are limited in size and rate. A page that exceeds a limit is told why and disconnected.
  • Read-only connections refuse writes in the engine, as in the desktop app.

The website’s demo

The demo on this website is the same browser version, served by a gateway in public demo mode against a MongoDB server used for nothing else:

oxysdb-web-gateway --public-demo /etc/oxysdb/demo-admin.uri \
  --bind 0.0.0.0:8443 --tls-cert … --tls-key … --origin https://demo.example.org:8443 \
  --demo-scratch /var/lib/oxysdb-demo

Behind a reverse proxy, bind the gateway to an address only the proxy reaches, and give both the header and the proxy’s address or network: --client-ip-header X-Forwarded-For --trusted-proxy 10.0.0.5 (or 10.0.0.0/24). The header is ignored on connections from anyone else, so give the proxy a fixed address: otherwise every visitor counts as the proxy, and one of them can fill the demo.

For a container’s health check, give the gateway a listener of its own on the loopback, --health-port 127.0.0.1:9090, and ask it with oxysdb-web-gateway --health-check 127.0.0.1:9090. Visitors cannot reach or fill it. In the public demo, if the gateway stops responding for 30 seconds, it exits with an error, so a restart policy (restart: unless-stopped) brings it back. A paused machine does not count. Other gateways never exit on their own.

The gateway reads the page’s files when it starts: restart it after rebuilding them. If gzip and brotli are installed, cargo xtask demo also writes compressed copies, which the gateway sends to browsers that accept them. The page asks for the files under a folder named after their content (/pkg/<hash>/), so browsers can keep them until the next build. Behind a reverse proxy, let the proxy serve those files from disk, as the sample does: its deploy-bundle.sh publishes each build into a folder of its own, then restart the gateway. The TLS key file must be readable by its owner only (chmod 600).

The file holds the administrator’s connection string and must be readable by its owner only (chmod 600). Each visitor gets a MongoDB database of their own, with a user that may only read and write that database (and set its validators), filled with invented sample data. Visitors never share a database, and none can see another’s. The page has exactly one connection, to that database.

Inside that database a visitor uses the app as on the desktop: documents, queries, aggregations, SQL Query, indexes, schema, explain plans, value search, Data Compare between their own collections, saved queries and Collection History. What the session keeps (Collection History, comparison results, saved queries) is in its own small folder on the gateway, never shared, bounded, and removed when the session ends: Collection History keeps at most 2 MiB (512 KiB per action, larger deletes and updates ask to go ahead without keeping their documents), comparisons keep at most 4 MiB of results, and saving is refused once the folder holds 6 MiB.

Adding or editing connections, importing and exporting files, the shell, tasks, the AI helper, Map-Reduce (it needs JavaScript on the server, which the demo server runs without), users, roles, the profiler, server status and current operations, preferences, and anything else that would reach files, programs or other servers are not available in the demo. Their menu entries and toolbar buttons stay disabled, with a tooltip saying so (“Not available in the online demo — download the app”), and the gateway refuses them, whatever the page sends. Server-side JavaScript, $out and $merge are refused, wherever they are written. Every query, aggregation and explain is limited to 10 seconds, pipelines cannot spill to disk, and longer writes are stopped. A visitor can keep at most 12 tabs open, and each session’s writes and pages are bounded.

OptionDefaultEffect
--demo-max-sessions N50visitors at once
--demo-per-ip N5new sessions per address in 10 minutes (an IPv6 /64 counts as one address)
--demo-live-per-ip N2sessions open at once per address
--demo-idle-minutes N30a session without activity ends
--demo-max-minutes N120a session ends whatever happens
--demo-size-cap-mib N64past this size, the visitor’s database becomes read-only
--demo-scratch DIRtemporary folderwhere the sessions’ small working folders go; prefer a disk with a quota
--demo-allow-no-time-limitoffserve even if the server cannot limit reads’ time (MongoDB older than 8.0)
--client-ip-header NAMEnonethe header your reverse proxy sets to the visitor’s address
--trusted-proxy IP[/N]nonethe proxy’s address or network, the only one whose header is believed (repeatable)
--health-port ADDRnonea plain health listener on the loopback, GET /healthz
--demo-memory-mib N2048the gateway’s memory past which new demos wait and the newest ends
--demo-allow-server-jsoffserve even if the server runs JavaScript (not recommended)

When a session ends, its database and user are dropped. A database past 64 MiB, 40 collections or 200 indexes becomes read-only. When the gateway starts, and every 10 minutes while it runs, it drops any demo database no session owns. For that reason, run only one gateway per demo server.

Prepare the demo’s MongoDB server for strangers. deploy/public-demo/ in the source has a sample mongod.conf and compose.yaml that do all of this, with a reverse proxy in front (its README.md lists the steps). The compose file builds the gateway’s container image from deploy/docker/web-gateway.Dockerfile (docker compose build gateway, linux/amd64 or linux/arm64), or uses the one OXYSDB_GATEWAY_IMAGE names. The image runs as a non-root user without a shell, and holds the page’s files too; the sample runs it as the owner of its secrets (OXYSDB_DEMO_UID, OXYSDB_DEMO_GID). Its make-secrets.sh writes the passwords, the replica set key and the gateway’s certificate into secrets/, which git ignores. Its make-volume.sh makes the data’s filesystem. The server’s start script creates the replica set and the administrator whenever they are missing, so the demo comes back by itself after a restart, even on an empty volume. It refuses a data folder that make-volume.sh did not mark, so MongoDB never writes to the host’s own disk by mistake:

  • MongoDB 8.0 or later. The gateway refuses to start otherwise.
  • security.javascriptEnabled: false. The gateway checks this and refuses to start otherwise.
  • setParameter: allowDiskUseByDefault: false, so sorts and groups stop at their memory limit instead of writing to disk. The gateway checks this too. The sample also lowers those limits.
  • A small WiredTiger cache (storage.wiredTiger.engineConfig.cacheSizeGB).
  • Its own disk filesystem with a size limit, not tmpfs: tmpfs counts against the container’s memory and comes back empty after a restart. This is the hard cap: the gateway stops each visitor at 64 MiB, but only the filesystem’s size protects the server as a whole.
  • A small oplog (replication.oplogSizeMB), counted in that size.
  • A private network that only the gateway can reach.
  • A short expiry for change stream pre-images (changeStreamOptions).

Keyboard shortcuts

The browser version has the same keymap as the desktop app. While the app has the focus, the page keeps the shortcuts it binds for itself: Ctrl+E opens the value editor instead of the address bar, Ctrl+R refreshes the view instead of the page, F5 runs the query. Shortcuts the app does not bind stay the browser’s (zoom with Ctrl++, the developer tools, F11), and Ctrl+C, Ctrl+V, Ctrl+X copy and paste through the browser. If you remove a shortcut in Preferences › Keymap, the browser gets it back.

A few shortcuts never reach a web page: the browser keeps them for its own windows and tabs. The browser version binds an alternative instead. The menus show it, and Preferences › Keymap lists it after the shortcut it replaces:

Shortcut the browser keepsIn the browser versionUsed for
Ctrl+NAlt+NConnect (Connection Manager), New Task
Ctrl+Shift+NAlt+Shift+NAdd new stage (Aggregation Editor)
Ctrl+TAlt+THide/show columns, New mapping
Ctrl+F4, Ctrl+WAlt+WClose tab
Ctrl+Shift+WAlt+Shift+WClose tab without asking
Ctrl+PgDn, Ctrl+PgUpAlt+PgDn, Alt+PgUpNext tab, previous tab

On macOS, ⌘ replaces Ctrl and ⌥ replaces Alt. While you type in a text field on macOS, ⌥ with a letter types its character, as usual. If you give one of these alternatives to another action in the keymap, it belongs to that action.

In Chrome and Edge, two modes give the page the shortcuts above as well: full screen (F11), where the browser keeps only the keys that leave full screen or quit, and the page opened in its own app window (installed, or a shortcut opened as a window), where it keeps none. The alternatives keep working there. We have not checked this in Firefox or Safari.

Limits

The system clipboard and opening files with other programs work as well as the browser allows. Scheduled tasks run on the remote task runner, not in the gateway (see above). An upload under way when the connection drops must be started again.

When the connection drops (a network change, a laptop waking up), the page says so and reconnects on its own, a few times over about two minutes, to the same session: your open connections and tabs are still there. An action you started just as the connection dropped may say it was not delivered: run it again. If the gateway was restarted, or the page was away longer than that, reload the page.