Skip to content

Security model

Quartermaster is local-first, preview-first, and safe-by-default. It is built to inspect untrusted artifacts pulled from anywhere on a machine, so anything that touches files, the network, or agent/MCP configuration must be explicit, bounded, auditable, and reversible.

Network updates are a runtime setting (dashboard Settings → “network updates”, persisted in config.json beside the DB), defaulting to off (seeded from ALLOW_NETWORK_UPDATES). When enabled, the hub makes narrow, read-only fetches to the hosts items actually link to:

  • git ls-remote for latest commits (any git host; prompts disabled so private repos fail fast),
  • one GitHub tree-listing call per repo for tree verification (no file contents are downloaded),
  • registry metadata for npm/PyPI/Docker — never arbitrary egress.

An optional GitHub token (for rate limits) is stored in config.json on the data volume, never indexed and never returned by the API. Plugin-marketplace bundles verify offline against the local marketplace clone, and scan-time origin auto-linking is local-only (it reads git metadata from clones already on disk).

No resolver touches the network unless both ALLOW_NETWORK_UPDATES=1 (env) and a per-call allowNetwork: true are set. Otherwise calls return { network: "disabled" } and make no request. All requests are time-bounded (UPSTREAM_FETCH_TIMEOUT_MS). See the public catalog guide.

Writes are approval-gated and root-confined

Section titled “Writes are approval-gated and root-confined”
  • Discovery, drift, update, and pack previews never write artifact files — they return dryRun: true, writes: [].
  • The only write paths are apply_hydration (installs), apply_skill_improvement (skill improvement), and materialize_pack (packs). All require approval: "approve", validate every target path against an allow-list of roots (HYDRATION_ROOTS / PACK_OUTPUT_ROOTS — the latter empty by default, so pack materialization is refused until configured), back up every overwritten file, and return a rollback handle.
  • Path containment rejects absolute paths outside the roots and .. traversal before any mkdir/write reaches disk.
  • pin_version / unpin_version change DB metadata only — they never rewrite MCP/agent config files (they return a suggested config preview instead).

Surface snapshots are supplied-payload only: Quartermaster stores the surface you hand it and never connects to a live MCP server to collect one. Snapshots are metadata onlytools/list, prompts/list, resources/list shapes, never tool/prompt/resource execution. compare_mcp_surface flags added/removed tools and raises a high-severity mcp_destructive_tool_added finding when a newly added tool has annotations.destructiveHint: true.

Secrets are redacted before persistence or display

Section titled “Secrets are redacted before persistence or display”
  • MCP/settings/hook configs keep key names (in requires) and drop values to ***; credential stores (oauth_creds.json, auth.json, *.db, *.log) are hard-skipped by the scanner.
  • Semantic drift evidence redacts local paths and secret-like tokens (API keys, bearer tokens, gh*_/sk-/xox* patterns) before storing or rendering them.
  • Imported pack artifacts default to trustState: "untrusted" until reviewed (set_trust_state). import_pack previews only; it never hydrates.
  • Policy gates block apply-capable plans on high risk, unknown origin, blockers, or block-effect policy findings. Memories block hydration by default.
  • assertSafeBind, a DNS-rebinding guard, and bearer-token auth protect the local HTTP service and are never weakened by these features.
  • The thin collector redacts records at parse time and sanitizes MCP configs client-side before serialization — raw secret values never leave the source machine. Snapshots carry file hashes, never file content.
  • The hub validates the full payload shape at the trust boundary (bounded sizes, ≤500 items, 8 MB body cap), registers the sending device, and namespaces pushed paths as //<device-id>/<path> so machines can never collide or overwrite each other. Pushed items are indexed, never executed.
  • Stale-item reconciliation is the only remote deletion path, and its authority is narrow: it requires a stable device id, deletes only under //<device-id>/, and only items under the roots the collector reports having crawled — one device can never delete another device’s (or the hub’s local) items. The collector calls it only after a fully successful crawl+push; --no-reconcile disables it entirely.
  • A deliberate non-loopback bind (HOST=0.0.0.0) refuses to start without a bearer token, so remote pushes are always authenticated.

Cloud hub (Cloudflare Worker) & the cloud collector

Section titled “Cloud hub (Cloudflare Worker) & the cloud collector”

The Worker is internet-facing by design, so auth is mandatory, not optional:

  • Fail-closed. Without the AUTH_TOKEN secret the Worker answers 500 on /mcp and every admin route rather than serving openly. MCP clients and admin calls present that token as a bearer; comparison hashes both sides to avoid timing leaks.
  • Pairing is one-shot; device tokens are hashed at rest. Pairing requires the admin token and returns a fresh 256-bit device token once; the database stores only its SHA-256, so the server cannot leak raw tokens. Revoking a device server-side kills its access immediately.
  • On the device, the token is private. connect never persists the admin token (used once); the device token lands in ~/.quartermaster/ with 0600 permissions (per-user profile on Windows).
  • Redaction happens before upload, same as everywhere. The cloud collector reuses the scanner’s parse/redact pipeline verbatim, additionally drops the parsed MCP-config object from the wire payload, and caps uploaded body sizes — raw secret values never leave the machine.
  • The cloud plans; only the device writes. apply_hydration on the Worker produces an approved job in a queue; the collector executes it locally and re-checks the same policy gate on-device before anything touches disk — a compromised or buggy hub still can’t push a plan past the local gates. Rollbacks flow through the same jobs path.
  • reindex in the cloud is “ask, don’t scan”: it queues a rescan request the device picks up on its own schedule.

The Windows, macOS, and Linux installers change distribution, not the trust model — they stage the same collector and schedule the same redaction-first pipeline:

  • The admin token is entered once in the install flow, passed to pairing, and never written to disk by any installer; only the revocable device token is stored.
  • Windows: CI signs the setup exe when signing credentials are configured; the bundled node.exe is already signed by the OpenJS Foundation. Locally-built installers are always unsigned — treat CI as the source of release binaries.
  • macOS and Linux: the .run files ship with .sha256 sidecars — verify with shasum -a 256 -c (macOS) / sha256sum -c (Linux) before running. The installers refuse on architecture mismatch (and musl, on Linux) rather than degrading; the bundled node binaries are signed upstream.
  • Uninstalling (any OS) removes the schedule and program files but leaves ~/.quartermaster, so re-installing doesn’t mint a new device; revoke the device server-side to fully disconnect.