OpenMausBot Audited: 18,250 Stars, 1.07M Lines, and a Sandbox That Switches On Only in the Cloud

OpenMausBot hit 18,250 stars in 89 days. Its bubblewrap sandbox is Apache-2.0, yet it defaults on only in the paid cloud; the self-host image lacks bwrap.

OpenMausBot is a chat app where every contact in the sidebar is an agent with its own model, its own thread memory and its own computer. It is Apache-2.0, it runs the claude and codex CLIs you already pay for, and it was created on 11 August 2026. On 9 November 2026 it had 18,250 stars, 9,211 commits, 106 published releases and 1,068,158 lines of code across 5,793 files.

Those numbers describe a project that grew faster than almost anything else in the category this year, and the interesting question is not whether it is real. It is. The interesting question is where the safety boundary sits, because the product’s whole pitch is that you hand a roster of agents a shell, a browser and your API keys, and then you approve what matters.

This audit reads the repository’s own files — the sandbox detector, the Dockerfile stages, the licensing carve-out, the security policy, the approval-levels document — and one number keeps falling out of them: the containment layer is 2,108 lines of non-test code, and it is off by default everywhere except the company’s own cloud.

The shape of 89 days

MetricMeasured (2026-11-09)
Stars / watchers18,250 / 44 (0.24%)
Forks / fork-to-star2,534 / 13.9%
Created → last push2026-08-11 → 2026-11-08
Commits9,211 (103.5 per day)
Commits by the top author5,304 (57.6%)
Releases106 (first 2026-09-01), tags up to v0.1.149
Release assets2,188 files, 322.5 GB
Source lines (non-test)558,362 in 3,275 files
Test lines509,796 in 2,518 files (0.91:1)
Docs251 Markdown files, 52,089 lines
Open issues / closed issues576 / 471
Open PRs / closed PRs252 / 3,708
LicenseApache-2.0, with an enterprise/ carve-out

The tag numbering deserves a note. Releases stop at 106, but the tag on the newest release is v0.1.149. Forty-three version numbers were consumed by builds that never produced a release, which is what a release pipeline looks like when a single person ships several times a day and some attempts fail. The largest installer in the newest release is 320,504,332 bytes (the Apple-silicon .dmg), and the .deb is 274,742,204 bytes. Across all 106 releases, 2,188 assets total 322.5 GB.

Two numbers do not fit the usual story. The first is 44 watchers: 0.24% of the star count. Watchers are the people who asked to be notified; stars are the people who clicked once. In the previous audits on this site that ratio has been the single most useful signal, and 0.24% is at the bottom of the range. The second is 576 open issues, filed at roughly 24 a day in the first nine days of November. Bookmark-star projects do not generate 24 support requests a day. Both facts are true at once, and neither cancels the other.

If you have seen “828 open issues” quoted, that figure comes from GitHub’s open_issues_count, which adds open pull requests to open issues. The real split is 576 issues and 252 PRs.

What the bot box actually is

docs/bot-box.md describes the containment layer without marketing. On a MausBot Cloud home, every bot turn runs in an unprivileged bubblewrap sandbox:

the engine (Claude Code, Codex, Grok, OpenCode, the ACP agents), every shell command it runs, the MCP servers it starts and OpenMausBot’s own helper proxies all live inside one unprivileged bubblewrap sandbox for that process’s lifetime. The server, its database, its settings, other bots’ folders and other turns’ processes are not in it.

Inside the box, /usr, /etc and /opt are read-only; HOME is writable so the engines keep their logins; the data folder ~/.openmausbot is replaced by “an empty private folder”; the turn’s working folder and workspaces/<bot> are put back read-write; /tmp, /run, /dev and /proc are private namespaces. The network is a namespace with loopback only, and two bridges: an egress proxy on 127.0.0.1:3128 that refuses this machine, the local network, link-local, the cloud metadata service and Fly’s private network by address, and a harness gate that lets the box reach only /api/internal/ and answers 403 to everything else itself, so the server is never asked.

The list of layers is unusually specific: a mount namespace, a PID namespace, the two network gates, an environment allow-list, a credential broker, a seccomp filter blocking 34 system calls, hidepid=invisible on the host’s /proc, and a root-only /.fly. The self-check is a real one: node dist-server/bot-box/box-check.js runs box-probe.ts inside a live box over a fixture laid out like a data folder, with a canary process and a fixture server, and counts what a hostile bot could still reach — 23 checks by default, 29 once the credential broker’s six are included.

And then the document does something most projects do not: it publishes a table of what each layer does not stop.

LayerAdmitted gap
Mount namespacereading HOME’s engine logins (the engine’s own identity); a path a driver puts back on purpose
PID namespacenothing inside the same box
Network namespace + gateswhat the harness routes allow with a valid per-turn bearer; a public destination
Environment allow-listthe engines’ own settings
Credential brokerkeys with no base-URL setting (named in the report); subscription logins
seccomp“anything an engine, shell, git, npm, python or Chrome uses: all allowed”

The document also explains what was deliberately not built and why: a separate Linux user per bot, because “the server runs as maus under a root supervisor and cannot switch uid”, and nftables rules, because they “would add nothing to a box with its own network namespace”. That is the kind of paragraph that tells you more about a project’s engineering than any feature list.

The whole layer is server/bot-box/: 3,953 lines across 19 files including tests, of which 1,845 lines are tests. 2,108 lines of actual code, against 558,362 lines of source in the repository. The sandbox is 0.38% of the codebase, and it is the part that decides whether an agent can delete your files.

The default is “not-cloud”

server/bot-box/detect.ts is 90 lines and decides everything. The relevant function:

export function detectBox(input: DetectInput): BoxDetection {
  const wanted = boxWanted(input.env);
  if (wanted === "off") return { box: "off", reason: "switched-off", tools: null };
  if (wanted === "default" && !cloudRole(input.env)) return { box: "off", reason: "not-cloud", tools: null };
  if ((input.platform ?? process.platform) !== "linux") return { box: "off", reason: "not-linux", tools: null };
  const which = input.which ?? ((name) => whichOnPath(name, input.env));
  const bwrap = which("bwrap");
  if (!bwrap) return { box: "off", reason: "no-bwrap", tools: null };
  if (!(input.probe ?? probeBwrap)(bwrap)) return { box: "off", reason: "userns-refused", tools: null };
  return { box: "bwrap", reason: null, tools: { bwrap, socat: which("socat") } };
}

cloudRole is true only when OMB_CLOUD_ROLE is home or team. With the variable unset — a desktop install, a VPS server started with npx openmausbot serve, a Docker container — the third line returns {box: "off", reason: "not-cloud"} and nothing below it runs.

The docs say this plainly: “on by default on a Cloud home (OMB_CLOUD_ROLE=home or team), off everywhere else unless OMB_BOT_BOX=1”. So a self-hoster can turn the box on. What happens next depends on a Dockerfile detail that is harder to see.

The image you would self-host with has no bwrap in it

The repository has one Dockerfile with six stages. runtime (line 72) installs Chrome’s libraries, git, curl, fonts-noto-cjk and an agent-browser binary with a pinned Chrome — and no bubblewrap. cloud-home (line 129) is FROM runtime and adds them:

&& apt-get install -y --no-install-recommends python3 poppler-utils unzip bubblewrap socat \

server (line 219) is FROM runtime again, and it is the last stage in the file — which makes it the default build target. The CI workflow runs docker build -t openmausbot:ci . (no --target), boots that container, and later tags it as ghcr.io/milind-soni/openmausbot:latest. The image behind deploy/local/Dockerfile, the one compose.yaml builds on, is FROM ${BASE_IMAGE} with ghcr.io/milind-soni/openmausbot:latest as the default base, and it adds three things: engine CLIs, Grok, and USER maus. It does not add bwrap.

The cloud image is published to a different repository altogether — ghcr.io/milind-soni/openmausbot-cloud-home, built with target: cloud-home, and the cloud Admin pins it by digest.

Put those two facts together and the self-host path resolves like this: set OMB_BOT_BOX=1 on the compose deployment and whichOnPath("bwrap") finds nothing, so detectBox returns {box: "off", reason: "no-bwrap"}. The flag is documented, the code path exists, and the image you were told to run cannot satisfy it. A bare-metal Linux install with bubblewrap installed from the distribution is the configuration where the box can work — plus a kernel that allows unprivileged user namespaces.

This machine, which is Debian 13 in a container, is a useful example of the second condition failing:

$ bwrap --version
bash: bwrap: command not found
$ unshare -Ur true
unshare: unshare failed: Operation not permitted

probeBwrap runs exactly that namespace set — --unshare-user --unshare-pid --unshare-net --unshare-ipc --unshare-uts plus --ro-bind /usr /usr and /bin/true — so on a host like this one the box reports userns-refused even after bwrap is installed. Distros that restrict unprivileged user namespaces by default (Ubuntu 24.04 and later among them) produce the same answer. The docs are honest about this too: “It is never on without a working unprivileged bubblewrap: if the kernel refuses user namespaces or the image has no bwrap, the box reports off with the reason”.

What none of the documentation connects is the four-step distance between “free local app” and “the containment the cloud has”. docs/self-hosting.md — 1,300-plus lines covering reverse proxies, pairing, sign-in lists and the safety floor — contains zero mentions of bot-box, OMB_BOT_BOX or bubblewrap. The only file that documents the box is docs/bot-box.md, and it is linked from two others (team-cloud.md and custom-mcp-servers.md), neither of which a self-hoster reads first.

There is a second-order effect in the issue tracker. Issue #4983 reports that with OMB_BOT_BOX=1 and the Local VM as the bot’s computer, a computer-use call fails with “MCP server exited”, because the box has no /var/run/docker.sock and container-mcp.js runs docker exec into the VM container. The box and the Local VM are, in practice, mutually exclusive. The reporter’s second request is telling: “The limitation is documented. docs/bot-box.md covers MausBot Cloud but not this case.”

The bot box is not the only protection, and OpenMausBot is careful to distinguish the two. The permission broker turns tool requests into approval cards; the safety floor is “a short never-list that neither can answer”. Both are documented with their gaps, and the gaps are large enough that a reader should know them before installing.

The safety floor is off by default. server/safety-floor-gate.ts says so in a comment and enforces it: return env.OMB_SAFETY_FLOOR?.trim() === "1" || cfg.safetyFloor?.enabled === true. server/config.ts carries the same note. Its never-list is narrow — recursive delete of /, of ~, of a top-level system directory, mkfs on a device, diskutil eraseDisk, dd of=/dev/sda, a redirection into a disk device, a fork bomb — and the documentation says plainly: “Making a filesystem in an image file or on a loop device is not on it.”

On Windows the floor is not merely off, it is a no-op that accepts the setting:

It is not applied on a Windows server yet. There the setting is accepted and does nothing: no command is checked, denied or held, and every approval behaves as it does with the setting off.

The server logs one line at startup and reports safetyFloor.enabled: false. The reason given is that it cannot tell cmd, PowerShell and Git Bash apart. And the floor only sees commands that are actually asked about:

An engine running its own no-ask mode never sends a request, so the floor does not see the command: at Full access that is Claude Code (bypassPermissions), Codex (approvals off) and the ACP agents started in their own yolo mode. Treat it as a net under saved commands and API-key bots, not as a sandbox.

The default approval level makes that sentence load-bearing. docs/approval-levels.md: “Every bot and conversation starts at Full access, the default T3 Code uses too: each engine runs in its own most permissive mode, and a real question or a missing credential still reaches you.” The constant is DEFAULT_APPROVAL_MODE in shared/approval-mode.ts. Full access “applies the moment you pick it: no confirmation dialog, no second step”, answers “residual native permission prompts” for you, and skips peer-review prompts inside the bot’s scope. A Chief of Staff’s level then “flows down” to every bot it delegates to, hop after hop.

So the default laptop configuration is: a bot at Full access, its engine in bypassPermissions, the safety floor off, no box because reason: "not-cloud" on macOS and Windows regardless, no box on Linux without an environment variable and a working user namespace — with API keys stored “as plaintext, not encrypted, in private config.json (0600 on Unix)”.

None of that is a secret. It is three separate documents being precise. It is also, from a reader’s point of view, a configuration where the phrase “permission broker turns every risky action into a decision you make” describes what happens when the engine chooses to ask, not what happens when it does not.

The risk the project wrote down itself

The strongest paragraph in the repository is in SECURITY.md, under “Scope notes for researchers”:

Known and documented, not yet closed: that caller keeps the Slack worker’s guarded routes, so a bot’s shell can post into an existing Full-access thread, or open one while shared Full access is on, and get Full-access work done without a card. A worker-only relay token is the planned fix.

The matching comment in server/request-auth.ts is longer and names the mechanism: on a hosted or shared workspace, loopback without a session gets service trust and may reach only SERVICE_ALLOW — but “a bot’s shell is a loopback caller too, so it can do everything the Slack worker does: post into any bot’s thread through the guarded route, including an existing Full-access thread, open new Full-access threads while the operator’s shared Full access is on, stop a request and decline a card.”

Read the secondary clause, because it is the part that matters: while the operator’s shared Full access is on. The documented mitigation is operational, not technical — turn shared Full access on only where every user may have Full access. The planned fix is a relay token only the Slack worker holds. There is no date.

Two things can be true here. A three-month-old project of a million lines publishing its own unfixed privilege-boundary bug, with the code comment naming the exact routes, is a practice most commercial products do not follow. And a reader deciding where to run agents should not treat a “known and not yet closed” note as a closed item.

Where the paid boundary actually is

Open-core projects usually put the best feature behind the licence. This one does something more structural: the carve-out is tiny and the money is somewhere else.

LICENSING.md is explicit. Everything under enterprise/ is source-available under the OpenMausBot Enterprise License, not Apache-2.0: “You may read, build, and evaluate it, and run it freely in development and test. Running its features in production needs a license key issued for your organisation. Hosting it for third parties or white-labelling the product needs a partner agreement.” Delete the folder and the server reports {"edition":"oss"}.

Then measure the folder. enterprise/ contains 10 files and 957 lines — 0.17% of the source. Its FEATURES file lists four entitlement ids that are shipped: whitelabel (product name, tagline, accent, logo, support link from brand.json), admin (installations and the hosted sign-in adapter), budgets (a monthly workspace spend cap, “one cap per workspace, not per bot”), and billing (sell prices per model, a billable column in Usage and its CSV). One more, sso, is still “planned”, and the file notes that “keys may carry ids that are not shipped yet; unknown ids grant nothing”.

The routing rule is stated better than most open-core policies manage: “could any open-source user want it? Then it goes in core, as a public pull request. Organisation-, admin- or tier-flavoured? Then it lives in enterprise/ behind an entitlement.”

So the product they sell is not the code. It is the machine:

PlanPriceMachineComputer-hoursNotes
Free$0your own computer—Apache-2.0, all models on your key
Starter$15/mo2 vCPU · 2 GB1002,000 app events/day, 20 GB storage
Pro$49/mo4 vCPU · 4 GB300$10/mo Claude credit, 100 GB
Max$99/mo8 vCPU · 8 GB800$20/mo Claude credit
Team$169/mo8 vCPU · 8 GB + 4 GB swap1,000up to 5 users
Business$599/mo8 vCPU · 8 GB + 4 GB swap3,000up to 20 users
Business self-hosted$299/moyour hardwareunlimited bot workup to 20 users

Overage is $0.05 per computer-hour, a small computer counts as half an hour, and fair use caps concurrent cloud computers at 15. Backups are kept 14 days on every tier. The cloud home is one Fly app with one machine, a volume at /data and TLS at https://<app>.fly.dev — one customer, one machine, which is why the sandbox can be default-on there and nowhere else.

That is a coherent business: you are not paying for the sandbox code, you are paying for a Linux machine where the sandbox can run. The problem is purely informational. A person who downloads the free app reads “local first” and “your own machine” and reasonably concludes that the safety posture is similar. It is not the same posture at all; it is a different code path returning a different reason string.

Where 18,250 stars come from

The usual explanation — a launch that found an audience — is missing its evidence here. Hacker News has three submissions of this project, on 28, 29 and 30 October, scoring 2, 1 and 3 points with zero comments. A project at 18,250 stars with a 44-watcher audience and no front-page discussion is not being discovered the way open-source projects are normally discovered.

What is measurable is the site. The landing page carries nineteen testimonials, including one that reads “I’ve contributed two PRs to it before”, and a pricing page that opens with “An AI team that works while you sleep”. The repository’s own README opens with a warning that most projects never need:

No affiliation with any cryptocurrency. OpenMausBot has no token. Any coin using the OpenMausBot, Maus, or SupaMaus name is not created, endorsed, or affiliated with this project or its maintainer.

A project that has to say that has met the internet. It is also a plausible partial explanation for a star count that outruns every other signal: token communities attach themselves to names, and the repository becomes a landmark whether or not anyone runs the code.

I am not asserting that the stars were bought; there is no evidence for that, and the commit history, the 471 closed issues and the 3,708 closed PRs are not the work of a vanity project. I am saying that stars are the only metric here that does not agree with the rest, and the rest is unusually well instrumented.

What the numbers say about the support load

A 24-per-day issue rate on an 89-day-old project is a load problem, and the tracker shows it.

MeasureValue
Open issues / ever filed576 / 1,047 (55% still open)
Sampled open issues with zero comments349 of 501 (69.7%)
Comments across 501 open issues239 (median 0, max 14)
Median age of an open issue11.1 days (max 60)
Issues filed 1–9 November217
Open issues carrying the bug label32 of 576
Closed PRs3,708

Fifty-two issues on the first page of the tracker, ten comments between them. The titles are specific and useful — “openai-compat never retries a transient failure: 7 of 8 createOpenAIChatRuntime callers leave retryScale undefined”, “SSH viewer tunnel exits immediately (exit 0) in same-host Docker deployments on Linux”, “iOS: the phone can auto-lock during a voice note, which silently stops the recording” — and most of them are unanswered. Title keywords skew toward platforms: 47 of 501 mention login or sign-in, 43 iOS, 36 MCP or connectors, 33 Cloud, 31 crashes, 23 Windows, 22 Android, 16 macOS, 13 Linux.

Labels are barely used, which is why “32 of 576 have bug” is not a measure of bug density but of triage. Three thousand seven hundred and eight closed PRs against 471 closed issues is the other half of the picture: this project merges changes far faster than it answers questions. For a reader, that changes the calculus of adopting it — the fix you need may already be merged, and the question you ask may never be answered.

Decision guide by profile

You want to try it on a laptop. Install it, then immediately do two things: set every bot to “Ask for approval”, and treat the app as a tool with your full user privileges, because that is what it has. On macOS and Windows there is no box and cannot be; on Linux there is one if you install bubblewrap and set OMB_BOT_BOX=1, and if your kernel allows user namespaces. Do not put it on a machine that holds credentials you would not hand to a shell script.

You want to self-host on a VPS and keep agents contained. Use a distribution with bubblewrap available, set OMB_BOT_BOX=1, set OMB_LOOPBACK_TRUST=service if anyone else shares the box, set OMB_SAFETY_FLOOR=1, and do not use the official openmausbot:latest image as your base without adding bubblewrap and socat yourself — the image is built from the server stage, which does not include them. Then run the self-check and believe its report, not this article.

You are a team. The paid boundary is the honest one: the cloud plan is where the box is on by default and where the operator’s shared Full-access policy exists. If you self-host for a team, the Slack-worker route gap in SECURITY.md applies to you, and the documented mitigation is to keep shared Full access off unless everyone may have it.

Verify it yourself

Every claim above is checkable in ten minutes, and the checks are the useful part of this article:

  1. Is the floor on? curl -s 127.0.0.1:8799/api/config | jq '.safetyFloor' — {"enabled": false} on a fresh install, and always false on a Windows server.
  2. Is the box on, and why not? node dist-server/bot-box/box-check.js prints the box kind, the reason code (not-cloud, no-bwrap, userns-refused, not-linux) and the check count. {"box": "off", "reason": "not-cloud"} is the answer on a local install with no flags.
  3. Can this kernel run it at all? bwrap --version and unshare -Ur true. A permissions error means probeBwrap will return false no matter what you set.
  4. Does your image contain the binary? docker run --rm <image> sh -c 'command -v bwrap || echo MISSING'. The published openmausbot:latest is built from the server stage; the cloud-home stage is the one that installs it.
  5. Which edition are you running? curl -s 127.0.0.1:8799/api/edition returns {"edition":"oss"} when enterprise/ is absent or unlicensed.
  6. What is the default approval level on a new bot? Create one and look before you chat. It should say Full access; if it does not, a policy is already in force and you should find out which.
  7. Are your keys encrypted? They are not. ls -l ~/.openmausbot/config.json should be 0600, and the file contains plaintext keys by design.

FAQ

Is OpenMausBot really open source? Yes, with a carve-out. Apache-2.0 covers everything except enterprise/, which is source-available under a licence that requires a key for production use and a partner agreement for hosting it for third parties. That folder is 10 files and 957 lines. Delete it and the server reports the OSS edition.

Is the bot box a container or a VM? Neither. It is a set of Linux namespaces created with bubblewrap, plus a seccomp filter, an environment allow-list, a credential broker and two loopback gates. There is no hypervisor boundary, and the documentation does not claim one.

Why is the sandbox off on my laptop? Because detectBox returns not-cloud when OMB_CLOUD_ROLE is unset. On macOS and Windows it would return not-linux even with the flag set, because bubblewrap is Linux-only. On Linux you can turn it on with OMB_BOT_BOX=1 if bwrap exists and the kernel allows unprivileged user namespaces.

If I self-host the Docker image, do I get the sandbox? Not as shipped. The default image target is server, built FROM runtime, and runtime installs no bubblewrap. The cloud-home target does, and it is published separately. Add bubblewrap and socat to your own image, or run on a host that has them.

Does Full access mean the bot can do anything? Within your user account, largely yes: it passes the provider’s permissive mode (bypassPermissions, approvals off, yolo), answers residual prompts for you, and skips peer-review prompts. The safety floor is a separate, off-by-default never-list that does not see commands from engines in those no-ask modes.

Is the 18,250 stars number trustworthy? It is what GitHub reports. It is also the only metric that disagrees with the others: 44 watchers, three Hacker News submissions peaking at 3 points, and a README paragraph disclaiming a cryptocurrency token. Treat the stars as a description of attention, not of adoption.

The boundary is the business model, and the docs say so

The engineering here is not the problem. A 2,108-line sandbox with a published “what it does not stop” table, a 29-check self-probe, a documented decision not to build per-bot users, and a security policy that names its own open privilege bug is more disclosure than most funded projects produce in a decade. The 0.91:1 test-to-source ratio and the 52,089 lines of documentation are not the work of someone shipping a demo.

The problem is a default that no single document states in one place. Read the README and you get “local first” and a permission broker that “turns every risky action into a decision you make”. Read bot-box.md and you get containment. Read detect.ts and you learn those are different configurations, separated by an environment variable, a binary that the self-host image omits, a kernel setting, and a subscription.

If you are evaluating this for a laptop, install it, set the approval levels, and assume the shell is yours. If you are evaluating it as a team, decide in advance whether you are buying the machine — because that is the only place the isolation was designed to hold, and at $15 a month it is cheaper than the engineering you would otherwise do to reproduce it.


Sources: the milind-soni/OpenMausBot repository at commit state 2026-11-08 (README.md, LICENSING.md, SECURITY.md, Dockerfile, compose.yaml, deploy/local/Dockerfile, docs/bot-box.md, docs/self-hosting.md, docs/approval-levels.md, server/bot-box/detect.ts, server/request-auth.ts, server/safety-floor-gate.ts, enterprise/FEATURES, .github/workflows/docker.yml), the GitHub API as read on 2026-11-09, and openmausbot.com/pricing. Related audits on this site: Meta’s Astryx, agent-desktop, gstack, Worktrunk and NVIDIA Auto-Ontology.