Repository navigation
Releases
GhostBus v0.5.11 — MCP Registry listing (mcpName) + tarball roadmap
Metadata and discoverability release. No runtime code changes beyond the version constant.
This release exists so GhostBus can be listed in the official MCP Registry: the registry verifies ownership of the npm package by reading an mcpName field from the published package.json, and 0.5.10 does not carry one.
- package.json now declares "mcpName": "io.github.littlestjames82-sys/ghostbus", matching the server.json manifest in this repo (a glama.json for Glama was added alongside it)
- ROADMAP.md now ships in the npm tarball (27 files); a bugs URL was added to package.json
- Repo hygiene: the stale filestore-race-fix/ development workspace was removed (its patch is the FileStore fix that shipped in 0.4.1), and examples/wake-hook/wake.sh regained its executable bit
- Tests: main suite 71 checks, serverless 12, pack gate green
GhostBus v0.5.10 — release-pipeline fix (npm 12 pack shape)
Release-pipeline fix — and an honest gap in the record, closed.
The v0.5.8 and v0.5.9 GitHub releases were created, but their publish runs failed and never reached npm (the registry stayed at 0.5.7). This release supersedes both on npm; their release notes and the CHANGELOG carry the explanation.
Root cause: round 7's new pack-manifest gate parsed npm pack --dry-run --json assuming npm ≤ 11's array-shaped output. The publish workflow installs npm@latest before running the suite (required for Trusted Publishing) — and npm 12 changed the output to an object keyed by package name. The parser produced undefined, the check threw, the suite went red, and the publish was blocked. Local development ran npm 10/11, where the old shape still holds, so the break only appeared on the runner. The gate blocking the publish was the design working; the parser's single-shape assumption was the bug.
The fix: the parser now accepts all three shapes (array, bare object, name-keyed object) and refuses with "manifest NOT verified" if none yields a real file list. Verified against live npm 10, 11, and 12 output — same 22-file manifest each time. Assertions unchanged.
No runtime change: src/ differs from 0.5.9 only in the version constant. The round-7 supply-chain work and the round-8 final sweep + posture doc ship to npm for the first time in this version.
Verified before release: 71 checks, serverless 12/12, both demos, pack gate green under three npm majors. This release's own publish run is the end-to-end proof under the runner's npm 12.
GhostBus v0.5.9 — hardening round 8: final sweep & posture doc
Hardening round 8 — the final sweep, and the posture document. This closes the eight-round hardening loop (v0.5.2 → v0.5.9).
The sweep found nothing new to fix — and shows its work. A fresh-eyes re-read of every route on every surface, then a purpose-built 71-probe hostile battery re-run against the current code on all five surfaces (single relay, hosted, MCP stdio, serverless, core): auth bypass attempts, the planted-registry escape, traversal IDs, prototype-pollution payloads through every state entry point, bus-bricking snapshots, MCP crash frames, numeric edges, body caps, and rotation immediacy on both hosted surfaces. Every probe landed as designed; Object.prototype canaries stayed clean throughout. The battery's single red was the probe's own wrong expectation (a paramless tools/call answers a clean JSON-RPC error — the codebase's convention), hand-verified on both transports. One examined non-fix, documented: deleting a workspace doesn't sever an already-open stream to it, but no route can reach the orphaned bus afterward, so the stream can only idle.
New: docs/security.md — the consolidated security posture, shipped in the tarball. Trust boundaries, every control family with its evidence, the load figures with their honest ceiling, and a "Known limitations — stated, not hidden" section. It closes with the mapping to the five steps an external review named toward 9/10: harden the security model — done; prove it under load — done; transactional persistence — deferred by the owner's explicit decision (the store stays an optional adapter, later); intelligent orchestration and coding-lifecycle integration — not hardening items, not claimed.
Consistency fixes: README's install line still said v0.5.0 (now v0.5.9); a docs example showed workspace keys as 64 hex chars (they've been 48 / 192-bit all along). Every number in the docs was audited against the code.
Loop end state: main suite 71 checks, serverless 12, both demos green, pack gate green (22 files). No suite padding in this round: a clean sweep adds evidence, not checks.
Correction (2026-10-11): this release's npm publish was blocked by the new pack-manifest gate — its parser assumed npm ≤ 11's output shape and npm 12 changed it, so the publish run failed the suite step and this version never reached npm. The gate did its job; the parser was the bug. Fixed and superseded on npm by v0.5.10 (see its CHANGELOG entry). The work described below ships in 0.5.10.
GhostBus v0.5.8 — hardening round 7: supply chain
Hardening round 7 — supply chain & release integrity.
Actions pinned to immutable SHAs. The publish and CI workflows used mutable @v4 tags; both actions are now pinned to the exact commits those tags resolve to (actions/checkout and actions/setup-node at v4.4.0), verified live against the API — if a tag is ever re-pointed, this pipeline doesn't move. One deliberate non-pin, commented in the workflow: npm install -g npm@latest stays, because Trusted Publishing requires npm ≥ 11.5.1. The workflow itself was already least-privilege (contents: read, id-token: write; dry-run by default on manual dispatch), and this release is its first run with a new gate (below).
The tarball is now gated. A new scripts/check-pack.mjs enforces an allowlist over npm pack: exactly the runtime (src/, docs/, examples/, README, PLAN, CHANGELOG, LICENSE — and SECURITY.md, now shipped so the policy travels with the code). Tests, deploy/, scripts/, .github/, runtime state, and stray tarballs are forbidden by name, proven both ways (green on the real tree; fails naming every smuggled path on a doctored one). It runs in the suite and as a publish-workflow step — a future stray file can't quietly ship to npm.
Provenance: present and verifiable. Registry metadata for recent versions carries SLSA provenance attestations (sigstore) plus registry signatures — the chain from this repo's release workflow to the npm artifact is signed end to end. SECURITY.md now documents how to verify (npm audit signatures).
Honest corrections: engines claimed Node ≥18 but nothing ever verified 18 — CI evidence covers 20/22/24, so the floor is now ≥20 (README fixed to match). .gitignore didn't cover the hosted server's default data dir — a state file could have been committed by accident; fixed. Zero dependencies continues to mean npm audit: 0 vulnerabilities, by construction.
Verified: 71 checks (was 70), serverless 12/12, both demos; CI matrix now Node 20/22/24.
Correction (2026-10-11): this release's npm publish was blocked by the new pack-manifest gate — its parser assumed npm ≤ 11's output shape and npm 12 changed it, so the publish run failed the suite step and this version never reached npm. The gate did its job; the parser was the bug. Fixed and superseded on npm by v0.5.10 (see its CHANGELOG entry). The work described below ships in 0.5.10.
GhostBus v0.5.7 — hardening round 6: tokens & serverless parity
Hardening round 6 — token lifecycle, and the serverless pack had quietly drifted.
Four real divergences between the Node servers and the Netlify build, all closed:
- Token enforcement defaulted OFF in the serverless pack — it kept the pre-v0.5.2 opt-in semantics. A tokenless act returned 200 where the hosted server returns 400. Default is ON now, with the same
GHOSTBUS_REQUIRE_TOKENS=0opt-out spelling. - No admin-key floor serverless — a 5-character admin key was accepted. Now refuses <16 chars at construction, matching the Node servers.
- Rotate-key, export, and import did not exist serverless — a leaked serverless workspace key could only be answered by deleting the workspace. All three routes now exist with Node semantics.
- Agent-token rotation was MCP-only — no REST route anywhere.
POST /api/agents/rotate-tokennow exists on the relay, hosted, and serverless handlers; rotation requires the current token and the old token fails on the very next request (pinned over real HTTP).
Verified already safe, now pinned: rotation replaces hashes and persists before replying; every credential is crypto-random 192-bit (format-asserted, values never printed); credentials appear only in their one-time issuance replies — never in the event log (readable by all workspace members), errors, audit lines, or board payloads; exports carry SHA-256 hashes only and are not credential-equivalent.
Documented, deliberately not built: tokens don't expire (rotation is the revocation story — full runbook in SECURITY.md), and the authorization model is single-scope by design at pilot stage (workspace key = full access to its one workspace). Both stated plainly in SECURITY.md rather than papered over.
Verified: 70 checks (was 69); serverless suite doubled 6 → 12 to hold the parity; both demos.
GhostBus v0.5.6 — hardening round 5: load & resilience
Hardening round 5 — prove it under load, and close the abuse holes load exposes.
The load bench (new, scripts/load-bench.mjs): boots the real hosted server with per-agent tokens enforced, runs concurrent agents doing mixed register/heartbeat/send/inbox/task/file work, then reconciles EXACTLY — messages sent == stored, tasks created == done, files put == stored. Across repeated runs: 0 errors and exact reconciliation every time (e.g. 2,506 ops in 30s at 25 agents; 650/650 messages, 300/300 tasks, 300/300 files). Figures are this-machine evidence, not an SLA — and the honest ceiling is documented: the JSON store re-saves whole state per op, so throughput decays as a workspace grows (~83→57 ops/s between 0.9 and 1.2 MB of state). RSS stays in a bounded sawtooth; the trims hold.
SSE subscriptions were unbounded — now capped (100 per workspace, 1000 per server, clean 429, idempotent cleanup so churned clients never leak listeners).
Server timeouts, done carefully: Node's defaults were probed on Node 24 — headersTimeout left partial-header sockets alive far too long, and requestTimeout semantics are unsafe for long-lived responses. The shipped guard bounds header receipt (10s) and declared-body receipt (30s) and destroys only genuinely incomplete requests: SSE streams and long-polls are never time-bounded. An early cut of the guard killed live streams in testing; the shipped version arms only on a declared body — the suite pins that.
Recovery drill (in the suite): SIGKILL the server mid-load, restart on the same data dir — state parses (atomic rename), every acknowledged op is durable, stored messages form a contiguous prefix per agent, counts reconcile with the file.
Verified: 69 checks (was 64), serverless 6/6, both demos.
GhostBus v0.5.5 — hardening round 4: MCP surface
Hardening round 4 — the MCP surface and hostile client input.
The headline bug: one bare null line on stdin crashed the MCP server. The stdio handler destructured every parsed frame; a null/string/number/array frame threw an unhandled TypeError and the process exited — taking the bus down for every connected agent. Non-object frames are now ignored and the handler is wrapped so one bad message is never fatal.
Silent state corruption via tool arguments, fixed. Wrong-typed values were coerced deep in the core: object bodies stored as "[object Object]", threadId={} stored as NaN (invisible in memory, null after reload), numeric file paths became numeric state keys. All 29 MCP tools now pass through one schema-driven validator at the call boundary: declared types enforced, null counts as missing, non-finite numbers rejected; every declared property across every tool is fuzz-covered in the suite.
Numeric semantics: limit=0 returned ALL messages and limit=-1 all-but-one; bus_sync with a garbage cursor claimed "caught up" while returning nothing. Limits are clamped honestly now, and /api/wait?timeout=abc no longer reports an instant false wait.
Pinned as already safe (adversarially, end-to-end): the board's data path (hostile names/bodies/titles return inert; the key never appears in a URL; probe/health return exactly their documented counts), encoded path traversal and method mismatches (clean 404s), and the CLI (usage + exit 2, one-line errors, no stacks).
Verified: 64 checks (was 59), serverless 6/6, both demos. The crash and the corruption class were reproduced on v0.5.4 before the fix and are green after.
GhostBus v0.5.4 — hardening round 3: hostile input
Hardening round 3 — hostile input. An audit-first round: two surfaces proved already safe (with tests pinning them), three real bugs closed.
Closed
- Planted-registry path escape. Hosted create/import already validated workspace ids strictly, but the registry loader trusted registry-file keys blindly — a planted
../escapeentry made the server load a state file OUTSIDE the data dir. The registry is now filtered through the id rule at load (invalid entries ignored with a warning) and ids are re-checked before becoming paths. __proto__file-store corruption.putFile('__proto__')invoked the prototype setter on the files map and silently corrupted the store (the file existed nowhere enumerable). Reserved names (__proto__,constructor,prototype) are now refused at write, treated as absent at read/delete, and refused in snapshots. A channel named__proto__also silently lost its message counts — fixed.- Snapshots that brick the bus. Validation was shallow:
agents: [null]passed and then crashed every subsequent operation. The validator now deep-checks entry shapes (objects, string names, string file text, finite non-negative counters, context shape) with sanity caps far above the bus's own trims. Legitimate exports from v0.5.0–0.5.3 still import and round-trip — compatibility was a hard constraint (unknown extra top-level keys remain allowed, deliberately, for forward compatibility).
Proven already safe (now pinned by tests): hosted id traversal at create/import was already refused; bus file names are pure state keys (hostile names incl. NUL bytes store/return inertly); the admin audit log can't be CR/LF-injected; global prototype pollution was not reachable through any state entry point.
Verified: 59 checks (was 54 — the 5 new checks all fail on v0.5.3 for exactly the intended reasons), serverless 6/6, both demos.
GhostBus v0.5.3 — hardening round 2
Hardening round 2 — the layer under v0.5.2's defaults.
What changed
- Timing-safe secret compares everywhere. The hosted workspace-key check, the core agent-token check, and the serverless pack each compared hashes with plain
===; all now use hash-then-timingSafeEqual. No plain secret compares remain. - Hosted failed-auth throttle. After 30 failed key/admin attempts per minute from one IP, the hosted server returns 429 on admin and workspace routes (valid traffic and /health unaffected once it clears). Guessing keys at a paid host is now expensive.
- 1 MB request-body cap on every JSON route (hosted admin, in-workspace, single relay): oversized bodies get a clean 413. Note: workspace exports over 1 MB can no longer be imported over HTTP — use the CLI or place the state file (documented).
- Board + landing headers:
X-Frame-Options: DENYand a content security policy (frame-ancestors 'none', object-src 'none'; inline script/style allowed because the board is one self-contained page loading zero external resources). - Data files are 0600. Workspace state and the registry hold key/token hashes; they're now written owner-only via the atomic save path.
- Generic 500s. Internal errors log server-side (with stack) and return
{error:'internal error'}— no internals leaked to clients. Deliberate 400 validation/auth errors are unchanged. - Admin audit log. Workspace create/delete/rotate-key/export/import each emit one structured line (timestamp, action, workspace, IP) — never keys or hashes.
- SECURITY.md added: supported version, reporting address, one-business-day acknowledgement.
Verified: 54 checks in the main suite (was 51; the 3 new checks fail on v0.5.2 for exactly the intended reasons), serverless 6/6, both demos, live hosted smoke.
Scope boundary, honestly: the auth throttle is per-IP and in-memory — behind a shared NAT, one guesser can briefly 429 that IP's unauthenticated attempts. Timing safety is verified by audit + functional checks; a timing channel isn't observable from a functional test.
GhostBus v0.5.2 — security hardening
Security hardening — closing the gaps a cold review (and common sense) flags before hosted workspaces serve paying strangers.
What changed
- Hosted token enforcement is now ON by default. Every workspace on the hosted server enforces per-agent tokens unless the operator explicitly opts out with
GHOSTBUS_REQUIRE_TOKENS=0— and opting out prints a loud startup warning. (Before: enforcement was opt-in.) - Admin-key floor: the hosted server refuses to start with an admin key under 16 characters (a missing key was already refused). The admin key is root for the deployment; docs now specify root-only file storage (chmod 600) and rotation.
- Isolation tests: the suite now proves cross-workspace keys get 401, admin routes reject non-admins (incl. list), and agent actions are rejected without/with a wrong token and accepted with the issued token — over real HTTP against the hosted server. 47 → 51 checks, all passing; serverless 6/6; both demos green.
- Single-relay warnings: the plain HTTP relay now warns loudly at startup when it has no key (it listens on all interfaces) or runs without token enforcement.
- Docs: new "Security & deployment hardening" section in docs/hosted.md — HTTPS is required for public deployments (reverse proxy or a named Cloudflare tunnel; a quick tunnel is not for paid use), key handling, scheduled export backups.
What did NOT change — local/stdio trust model and semantics, core storage (memory/JSON file), and the roadmap: a networked multi-writer store remains future work, deliberately. The serverless pack keeps its env opt-in posture, now documented as such.
The open surfaces, stated plainly: /health and a counts-only /probe (queued / needsApproval / agent counts — no message, task, or file content) stay unauthenticated for monitoring and wake hooks, as documented. All data routes require the workspace key.