Skip to content

Latest commit

 

History

History
183 lines (161 loc) · 15.4 KB

File metadata and controls

183 lines (161 loc) · 15.4 KB

Security

See docs/specs/glossary.md for Session, Pane, Surface, and remote-role vocabulary. Owns the guarantees Dormouse makes, what it does not defend, the gaps it knows about, and how all of it is checked. Defers every mechanism to the spec that owns it, and every audited check to the five specs under How the guarantees are checked. Published at https://dormouse.sh/docs/security, whole but for the three blocks split by audience; docs/specs/website-docs.md owns the page.

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.

Only the self-hosted deployment ships. The relay runs on hardware the user owns and is private to their tailnet by default, but its application boundary assumes the HTTPS origin is public (SELF_HOST.md). Nothing about remote control applies to a Burrow (a Standalone or VS Code Dormouse) that never enrolls with a Relay: enrollment is where the relay, the phone, and push begin. Cloud-hosted operation is staged, and its boundary is re-analyzed before that code ships (security-remote.md).

Guarantees

Each guarantee names the spec that states the rule and what pins it on every pnpm test. The nightly audit (below) checks all of them; audit in the last column means nothing cheaper does.

Guarantee Rule Pinned by
A program printing to your terminal cannot write your clipboard, read a file, or steal focus. It can raise an alert, set a title, or mark a prompt; an OSC 8 link requires confirmation, and a deceptive link has no open action. Terminal output lib/src/lib/terminal-protocol.test.ts, lib/src/lib/external-links.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 panes lib/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 socket standalone/sidecar/dor-control-server.test.js
A loopback listener grants a stranger nothing it could not get from the upstream directly. Loopback Listeners scripts/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. Explicitly captured notepad excerpts, titles, and working directories are archived on Surface closure. Persisted state audit
Nothing but a human at the laptop can authorize a phone. The only path into a Burrow's ACL is typing, on that Burrow, the two digits the phone shows, and the Burrow makes every access decision. Pairing, Burrow Authorization remote-lib-common/test/security-guarantees.test.mjs
The Relay cannot read ceremony or terminal content or grant terminal access. One end-to-end channel per ceremony carries content under keys the Relay never holds; account data and routing metadata remain visible. Trust Model, Residual metadata scripts/e2e-lint.mjs
Push notifications are opt-in, and a push is sealed to the one phone that receives it. Push sealing remote-lib-common/test/push-seal.test.mjs
A stolen or synced passkey buys sign-in, not a terminal. Every connection also needs the phone's own paired key and a fresh presence proof bound to that connection. Passkeys, Presence proofs remote-lib-common/test/security-guarantees.test.mjs
The Burrow bounds remote session state and handshake admission independently of the Relay. Deadlines use its own clock. Burrow bounds lib/src/remote/burrow/burrow-bounds.test.ts, relay/test/malicious-relay.test.mjs
A Burrow talks only to the relay its build was pointed at, and refuses before any credential leaves the machine. Where a Burrow may reach a Relay lib/src/host/remote/connect-src.test.ts
The self-host installer restricts Relay credentials to the installing account, on macOS, Windows, and Linux; Burrow enrollment uses protected app storage or VS Code's secret storage. Installer owner-check gaps are listed below. Credentials at rest scripts/deploy-lint.mjs
The self-host HTTPS origin may be public; its plaintext backend may not. The Relay generates its 256-bit setup credential with no operator-supplied value, Burrow enrollment is globally admission-limited, cross-origin browsers receive no grant, and terminal access still needs local Burrow approval. The setup password, Cross-origin access, Network posture relay/test/setup-password-store.test.mjs, relay/test/config.test.mjs, relay/test/token-bucket.test.mjs, relay/test/cors.test.mjs, scripts/deploy-lint.mjs
Push, when enabled, cannot be aimed back into the tailnet. What crosses the boundary relay/test/push-endpoint.test.mjs
Every dependency that reaches a machine is disclosed at dormouse.sh/supply-chain, and a change without the disclosure fails CI. Disclosure .github/workflows/ci.yml
The bundled runtime is the version disclosed. The build verifies the binary against the pin. Bundled runtime standalone/src-tauri/build.rs
No newly published dependency is adopted for 24 hours, security fixes included. Cooldown and alerts audit
Merging to main and creating a tag are admin-only, and every workflow this repository authors pins its actions by commit. GitHub Actions Policies audit
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 Releases audit
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 Releases audit

What is not defended

Stated so the audit does not rediscover them and a reader deciding whether to run this knows what they are taking on.

  • 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).
  • A compromised browser or operating system, on either end. Active XSS in the Pocket origin can use the phone's key without extracting it. Exactly two endpoints are trusted: the distributed Burrow binaries and the exact Pocket artifact the origin serves (Trust Model).
  • Traffic analysis. The Relay sees who talks to whom, when, how often, and how large each ciphertext is, and keystroke timing, never keystroke values (Residual metadata).
  • Push replay, when push is enabled. A push proves confidentiality, not freshness: a Relay that kept an envelope can re-deliver it (Push sealing).
  • Per-Burrow unlinkability, when push is enabled. One push endpoint per browser lets the Relay see every Burrow one phone registered (Residual metadata).
  • Phone-key durability. Clearing site data means pairing again. Nothing is compromised; a lost key authorized nothing on its own (Client static loss).
  • Availability. The relay is down whenever the laptop is (Goals; keeping it up).
  • 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 token is reachable by any workflow the bot can author. Accepted with rotation; abuse is visible in Chromatic's dashboard (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).
  • VS Code's peer-link token has no Windows ACL applied by Dormouse, and recovery.json is written at the umask (Persisted state).
  • The standalone log file is written at the umask, readable by another local account wherever the temp directory is shared, and records the dor socket path; no terminal output reaches it (Persisted state).
  • Revocation has no mechanism. Revoking a lost phone is editing the Burrow's ACL file and restarting the Burrow (Revocation and the audit trail).
  • There is no audit trail. Nothing records connects, attaches, denials, or writes (same).
  • The workflow audit's window has two evasions: a backdated committer date, and a branch pushed, run, and deleted before the nightly fetch (Automated Maintainer).
  • The audit's three subagents 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).
  • Two Pocket properties are verified on real hardware only (Device verification).

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. Three 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 that reaches 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 are the record of what tripped and what changed. scripts/security-audit-local.sh runs the same prompts locally. security-audit.md is the contract.

Domain Specs Covers
application-security security-local.md, security-remote.md the local application's boundaries, remote control, and every path no other domain claims
supply-chain security-supply-chain.md the dependency graph, the lockfile, the disclosure and its generator
ci-and-secrets security-ci.md, security-audit.md, this spec GitHub Actions, the bot, releases, secrets, and the audit itself

Every pull request that adds, removes, or upgrades a production dependency fails CI until the regenerated disclosure is committed (Disclosure).

Every desktop release ships attestations and hash manifests from CI, verified locally before anything is signed (Desktop Releases).

Reporting a vulnerability

Report privately through GitHub's Report a vulnerability form, which opens an advisory visible only to you and the maintainers. It is the right channel for anything here, and for remote control most of all: a public issue describing a live path into a Burrow's ACL is a disclosure, not a report.

Never open a public issue, and never email the maintainer. Include the version or commit, the deployment (self-hosted Relay, standalone app, VS Code extension), and the shortest reproduction. Every advisory is acknowledged with what we intend to do about it. There is no bounty, and a fix that needs a coordinated release says so in the advisory rather than promising a date; this is a one-maintainer project and nothing here promises a response time it cannot keep.

  • 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.