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 disabled by default
Section titled “Network disabled by default”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-remotefor 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), andmaterialize_pack(packs). All requireapproval: "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 anymkdir/write reaches disk. pin_version/unpin_versionchange DB metadata only — they never rewrite MCP/agent config files (they return a suggested config preview instead).
MCP surface collection restrictions
Section titled “MCP surface collection restrictions”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 only — tools/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.
Untrusted imports & policy findings
Section titled “Untrusted imports & policy findings”- Imported pack artifacts default to
trustState: "untrusted"until reviewed (set_trust_state).import_packpreviews 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.
Collector push (POST /api/ingest)
Section titled “Collector push (POST /api/ingest)”- 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-reconciledisables 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_TOKENsecret the Worker answers 500 on/mcpand 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.
connectnever 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_hydrationon 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. reindexin the cloud is “ask, don’t scan”: it queues a rescan request the device picks up on its own schedule.
Installers
Section titled “Installers”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.exeis already signed by the OpenJS Foundation. Locally-built installers are always unsigned — treat CI as the source of release binaries. - macOS and Linux: the
.runfiles ship with.sha256sidecars — verify withshasum -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.