Dormouse

Security

What Dormouse promises, what it does not, and the audit that holds it to the difference.

This page is the spec the audit runs against, published from the repository — not a summary of one. It shows the guarantees for the local application and the release pipeline; remote control and self-hosting are on the self-host runbook, and what reaches your machine is on the supply-chain disclosure. The audited checklists behind all three live beside the spec, in the specs directory on GitHub.

Dormouse holds shells, source trees, credentials, and local files. Its dependencies and release pipeline determine what code reaches a machine; remote control admits an authorized phone as a person at the keyboard; loopback listeners receive requests from pages in the user's browser.

Remote control supports relay-backed sessions and direct one-time connections. The Relay can be self-hosted; Hosted implements admin-entitled enrollment, account-scoped relay routing, and Pocket, with live production controls and paid-service acceptance requiring verification (Hosted). Hosted account sign-in alone grants no terminal access. Pairing and presence remain the Burrow's decision. Hosted also serves the one-time phone page and forwards only that connection's handshake ciphertext, authorizing nothing (Hosted rendezvous); its deployment pipeline and Cloudflare account are part of a one-time session's trust base.

A self-hosted Relay runs on hardware the user owns; the default installer keeps it private to the tailnet, while the application boundary permits a public HTTPS origin (SELF_HOST.md). Remote access starts only when a Burrow enrolls with a Relay or opens a one-time link; a link admits one phone for one session. Paid cloud operation still needs the review under Cloud-hosted mode.

Guarantees

Each guarantee names its owning rule and automated checks. The nightly audit checks all of them; audit in the last column identifies a property without a cheaper automated check. Native Cargo tests run in separate CI jobs, outside root pnpm test.

GuaranteeRulePinned by
Terminal output cannot write your clipboard or steal focus. File access requires a user action or a running designated Tool's gated OSC 367 open. OSC 52 only offers a copy format; links require confirmation or an allowed local preview, and deceptive links have no open action.Terminal outputlib/src/lib/terminal-protocol.test.ts, lib/src/lib/external-links.test.ts, lib/src/lib/terminal-link-activation.test.ts, lib/src/components/ExternalLinkModalHost.test.tsx
A page in a browser pane cannot forge a host message. In VS Code every host message carries a per-boot token it cannot read, and the standalone adapters have no inbox for it to post to.Browser paneslib/src/lib/platform/vscode-adapter.test.ts
Only your own account can drive your terminals through dor. The socket sits in a directory only you can open, and its token never crosses the wire.The dor control socketstandalone/sidecar/dor-control-server.test.js
A loopback listener grants a stranger nothing it could not get from the upstream directly.Loopback Listenersscripts/loopback-lint.mjs
Current persistence writers never save terminal scrollback. Standalone snapshots are owner-only; VS Code controls access to its own storage. Older snapshots may contain transcripts.Persisted statecargo test in standalone/src-tauri (the owner-only half); audit
Under Settings → Network → Nothing, a new install's level, Dormouse opens no connection on its own: no relay socket, one-time link, push, managed voice, or update check. What you click, and what your terminals and browser panes reach, are yours.Network policylib/src/host/remote/service.test.ts, lib/src/host/managed-voice-host.test.ts, standalone/src/updater.test.ts
Merging to main and creating a tag are admin-only, and every workflow this repository authors pins its actions by commit.GitHub Actions Policiesaudit
The bot maintainer cannot merge, tag, or read a release secret, and its token never enters its own environment.Automated Maintainer (tend).github/workflows/workflow-audit.yaml, nightly
Publishing the extension takes a second human's approval.VS Code Extension Releasesaudit
Desktop binaries are signed locally. CI never holds production signing or updater keys, and the signing script verifies CI's attestations and hashes first.Desktop Releasesscripts/sign-and-deploy.test.mjs

What is not defended

  • A process running as you. dor, its socket, and every file mode bound other local accounts, never a program already running under your own account; an agent holding dor has exactly the power of the person at the keyboard (The dor control socket).

  • The Windows dor pipe carries no ACL of ours. A named pipe has no directory to harden, so an unguessable name and the token handshake are the whole of it (The dor control socket).

  • What VS Code does with the pane state it stores. Structure persists in VS Code's own storage under its modes, never a transcript (Persisted state).

  • The bot's upstream is pinned by tag, not commit, so a hostile upstream could change what the bot runs without a diff here. Accepted: the trust equals what the harness already holds (Automated Maintainer).

  • The Chromatic and Argos tokens are reachable by any workflow the bot can author. Accepted with rotation; each dashboard shows abuse (Automated Maintainer).

Known gaps

Gaps rather than accepted risks: we intend to close them.

  • Browser-pane scripts share loopback cookies across grant ports. HTTP and WebSocket cookie headers are stripped, but document.cookie remains shared; cookie-authenticated iframe pages are unsupported (Loopback Listeners).

  • Windows screenshots and pasted clipboard images inherit %TEMP%'s ACL: private by default, exposed if it is shared or loosened (Browser panes).

  • Neither VS Code's peer-link token, its Tool trust receipts, nor the recovery.json beside them carries a Windows ACL applied by Dormouse. They are written owner-only by unix mode, which Windows makes a no-op; standalone locks its state directory instead (Persisted state).

  • The standalone log file is written at the umask and records the dor socket path (Persisted state).

  • The workflow audit's window has two evasions, both in how the window is computed (Automated Maintainer).

  • Audit domains share one credential. Their contexts are separate; AUDIT_PAT is not (Domains).

  • The notarization password sits on a command line for up to half an hour per architecture; the remedy is known and not yet done (Desktop Releases).

How the guarantees are checked

On every pnpm test, four lints turn the cheap half of these specs into build failures: scripts/spec-lint.mjs (the specs' own conventions and word budgets), scripts/e2e-lint.mjs (one Noise suite, no negotiation, no plaintext path), scripts/deploy-lint.mjs (every installer control, on all three platforms), and scripts/loopback-lint.mjs (a new loopback bind references a guard). Each carries a self-test that re-introduces the thing it forbids and requires the lint to go red; a rule without one is a claim, not a check. scripts/installer-verify-test.mjs executes the installer helpers the lints can only read.

Every night at 04:21 UTC, and before every VS Code release, .github/workflows/security-audit.yaml audits the repository against these specs. Four subagents, each owning the specs below, run every FAIL IF as a mechanical check with evidence, then read their domain adversarially for what no check names. A failure, or a run reaching no verdict, files a public issue labeled security-audit-failure and holds the release; a later pass closes it. Open issues are live; closed ones record what tripped and changed. scripts/security-audit-local.sh runs the same prompts locally. security-audit.md is the contract. pgstencil audits the packages Hosted consumes in its own repository.

DomainSpecsCovers
application-securitysecurity-local.md, security-remote.mdlocal boundaries, remote control, and everything no other domain claims
hostedsecurity-hosted.mdHosted accounts, the one-time rendezvous, and the pgstencil provenance link
supply-chainsecurity-supply-chain.mdthe dependency graph, the lockfile, the disclosure and its generator
ci-and-secretssecurity-ci.md, security-audit.md, this specGitHub Actions, the bot, releases, secrets, and the audit itself

Production dependency changes require committed regenerated disclosure. Desktop release artifacts carry CI attestations and hash manifests, verified locally before signing.

Source of truth: root scripts in package.json; native jobs in .github/workflows/ci.yml; .github/workflows/security-audit.yaml and its release gate in .github/workflows/release.yml.

Reporting a vulnerability

Must report vulnerabilities privately through GitHub's Report a vulnerability form, visible only to the reporter and maintainers. Never open a public issue or email the maintainer. Include the version or commit, deployment (self-hosted Relay, Hosted, standalone app, or VS Code extension), and shortest reproduction. Every advisory is acknowledged with intended next steps; there is no bounty or promised response time. A coordinated-release requirement is communicated in the advisory.

  • FAIL IF private vulnerability reporting is disabled on the repository (gh api repos/diffplug/dormouse/private-vulnerability-reporting must report enabled: true): the advisory form is the only channel this spec offers, and a disabled one sends a reporter to a public issue.