Skip to content

Security

This document covers the mossbear CLI and the three packages it depends on: @mossbear/protocol, @mossbear/eval-core and @mossbear/projections. It says what runs on your machine and when, what leaves it, how a release is built, how you can check a release yourself, and what is not covered yet. It covers the hosted dashboard only where the CLI talks to it.

Every claim names a command you can run or a file you can open. Paths like packages/cli/src/hooks-settings.ts are source files. Until the CLI’s source repository is public, the readable copy is the npm package itself. Its dist/ folder is the same files compiled by tsc with their comments kept, so dist/hooks-settings.js is src/hooks-settings.ts. Section 4 shows how to unpack it.

1. What runs on your machine

Installing the package runs nothing. None of the four packages declares an install script. Check each one with npm view <package> scripts, for example npm view @mossbear/protocol scripts.

Nothing runs on its own until you run mossbear init in a repository. For Claude Code, init writes five hook entries into that repository’s .claude/settings.json. The commands are bare names resolved through your PATH. Their spellings come from the constants in packages/cli/src/hooks-settings.ts, and the timeouts from packages/cli/src/commands/init.ts.

EventCommandTimeoutWhat it does
PostToolUse and PostToolUseFailuremossbear-hook --harness claude_code5sReads the hook payload Claude Code passes on stdin and appends one line to ~/.mossbear/runs/<session>.jsonl. It makes no network call and always exits 0, so it can’t fail a tool call (packages/cli/src/hook-bin.ts).
Stop, after each assistant turnmossbear report180sUploads the queued action metadata to your Mossbear account. If this repository subscribes to bundles, it also brings their files under .mossbear/ up to date, which can pull new versions and remove files a bundle no longer contains (never a file with unsynced local edits). mossbear report --dry-run lists what it would upload and sends nothing.
SessionEndmossbear complete180sUploads what is left, marks the session finished so it can be graded, and sends back the Mossbear-managed files you edited in this checkout. --dry-run works here too.
SessionStart (startup and resume)mossbear update; mossbear refresh || true90sInstalls the newest CLI release, then refreshes the files of any bundles this repository subscribes to.

The SessionStart entry is the one to read twice. By default it installs the newest release of mossbear at the start of every session, on every machine where the hook runs, with no prompt. That is how a breaking change reaches a repository whose hooks still name old commands. init says so when it writes the entry. Your choices:

  • mossbear init --no-auto-update rewrites the entry as mossbear refresh || true, with no install. Running mossbear init without the flag puts the install back.
  • mossbear init --no-session-start-hook leaves the entry out of a repository that doesn’t have one yet. It doesn’t remove an entry that is already there (packages/cli/src/commands/init.ts). To drop an existing one, delete the SessionStart entry from .claude/settings.json.
  • mossbear doctor tells you which form the repository has.

mossbear update works out which package manager owns your global install (npm, bun, pnpm or yarn) and runs that manager’s install command. If it can’t tell, it prints the command and installs nothing (packages/cli/src/update/install-manager.ts). It stays on the channel you installed from: an alpha install updates to the next alpha (packageSpecForChannel in the same file). The ; means the refresh runs even when the update fails, and the trailing || true means a failure here never blocks your session.

No lifecycle command overwrites a file you changed when the server’s copy also changed. It leaves both alone and reports the conflict. mossbear refresh --docs-conflict remote takes the server’s copy, and mossbear complete --docs-conflict local sends yours. refresh never sends your copy, even when you ask (packages/cli/src/lifecycle/operations.ts).

For Codex, init writes the same commands, each wrapped as bash -lc '… || true' (packages/cli/src/harness-writers.ts). For every agent it wires, init prints the file it wrote. init also copies four skills into .claude/skills/. Skills are instructions the agent reads, not code the CLI runs.

The CLI starts other programs in four places: git, to identify the repository and commit; your package manager, during mossbear update; your browser, during mossbear login; and opencode, to export an OpenCode session transcript.

Your API token lives in ~/.mossbear/config.json, written with mode 0600 inside a 0700 directory (packages/cli/src/config.ts). It can also be passed in the MOSSBEAR_TOKEN environment variable.

2. What leaves your machine

Run mossbear what-syncs. It lists, field by field, what the hooks upload about your sessions, what never leaves your machine, and the rules for the redacted transcript excerpts that do. The type checker and a unit test hold that list to the upload request’s schema (packages/protocol/src/whatSyncs.ts), so a new uploaded field can’t ship unlisted. It is not repeated here.

What it does not list is content you move on purpose: guides and files you push or import, and the Mossbear-managed files mossbear complete sends back. Those leave as the files themselves. One case needs no command. If a repository’s published branch (main unless published.branch names another) carries a published block in .mossbear/config.json, every hook run (report, complete and refresh) uploads the files that block names, as that branch holds them, whenever the clone’s copy of the branch has moved since it last published (packages/cli/src/lifecycle/publish-from-branch.ts). It reads only what the clone already has and never fetches. With no such block, nothing is published this way.

The CLI sends to and reads from your Mossbear dashboard. That is https://app.mossbear.io unless you set --dashboard-url or MOSSBEAR_DASHBOARD_URL (packages/cli/src/config.ts). It also asks the npm registry for the newest version (packages/cli/src/update/registry.ts):

  • every time mossbear update runs, including from the SessionStart hook;
  • when you run mossbear status, mossbear doctor, mossbear report, mossbear complete or mossbear refresh yourself in a terminal. A recent answer is reused from a cache. doctor then offers to install the update and asks before it does.

Setting MOSSBEAR_NO_UPDATE_CHECK, NO_UPDATE_NOTIFIER or CI, or "updateCheck": false in ~/.mossbear/config.json, turns off the second kind (packages/cli/src/update/notice.ts). None of them stops mossbear update. --no-auto-update is what takes it out of the hook. The package declares no analytics or error-reporting dependency; npm view mossbear dependencies lists what it does depend on.

3. How a release is built

  1. A maintainer starts every release by hand, either by pushing a cli-v<version> git tag or by running the release workflow in GitHub Actions, which creates that tag. Nothing publishes on a schedule or on merge.
  2. GitHub Actions builds the four packages with tsc and runs their tests.
  3. Inside the monorepo, the packages depend on each other as workspace:*. The workflow rewrites those to the release version: an exact version for a prerelease, ^<version> for a stable one.
  4. It packs all four tarballs, installs them together in an empty project, and runs mossbear --version and mossbear --help before anything is published.
  5. It runs npm publish for each package. A prerelease such as 0.1.0-alpha.70 goes out under its channel’s dist-tag (alpha). A stable version goes out under latest.
  6. Until a stable version exists, the workflow moves latest to the newest prerelease, so npm install -g mossbear gives you the newest alpha rather than the first one.
  7. A second job installs the published version from npm, checks that mossbear --version matches the tag, and runs mossbear init and mossbear doctor in an empty directory.

Two weak points:

  • No provenance. The packages are published without an npm provenance attestation, because npm only accepts Sigstore provenance from a public source repository and this one is private. Nothing cryptographically links a published tarball to the commit it was built from.
  • A stored token publishes. The publish step authenticates with an npm access token kept as a GitHub Actions secret, not with OIDC trusted publishing.

Publishing from a public repository would make provenance and trusted publishing possible. That has not happened yet. Until it does, read the checks in section 4 as “this is the file npm served everyone”, not “this was built from that commit”.

4. How to verify a release yourself

Terminal window
# Which versions the channels point at
npm view mossbear dist-tags
# The checksum and registry signatures for one version
npm view mossbear@<version> dist
# Download the exact tarball npm serves, and read it
npm pack mossbear@<version>
tar xzf mossbear-<version>.tgz
less package/dist/hook-bin.js # the PostToolUse hook starts here
less package/dist/hook-logger.js # and does its work here
# Confirm the package declares no install scripts
npm view mossbear@<version> scripts
# In a project where mossbear is installed: check registry signatures
npm audit signatures

What these prove:

  • npm audit signatures checks that each installed tarball carries a valid registry signature, so it is the file the registry holds. It does not prove where the file was built. It reports “verified attestations” only for packages that have provenance, and the four mossbear packages are not among them. npm view mossbear@<version> dist.attestations prints nothing for them.
  • The unpacked dist/ is readable JavaScript, not minified, and its source maps name the source file each one came from. Reading it shows you what runs.
  • mossbear --version tells you which version is on your PATH.

Comparing a tarball with the git commit it was tagged from is not possible yet, because the source repository is private.

5. What is not covered yet

  • Build provenance. Section 3 explains why there is none. It needs publishing from a public repository.
  • Automatic installs. The default SessionStart hook installs whatever npm currently serves as the newest version on your channel. A bad release, or a stolen publishing token, would reach every repository with that hook at its next session start. --no-auto-update (section
    1. takes you out of that path.
  • Application-level encryption for stored content. Mossbear does not yet encrypt the guides and context you store in your account itself before writing them to its database.
  • Third-party model providers. Grading and suggested guide edits run on the dashboard, and send redacted session content to third-party model providers. The privacy policy names them: https://mossbear.io/docs/privacy/
  • A leaked API token. Anyone holding your token can act as you against the dashboard, uploading to and reading from your account. If it leaks, revoke it on the dashboard’s CLI token settings page and run mossbear login again.

6. Reporting a vulnerability

Please don’t open a public issue for a security problem.

Once the CLI’s public repository exists, report through GitHub’s private vulnerability reporting on that repository. The “Report a vulnerability” button is under its Security tab. Until then there is no published security address. Mossbear is in a private beta, so reach the maintainer through the channel you were invited through, and say that it’s a security report.

There is no published response-time commitment or bug bounty.