Pre-launch — word of mouth only, please. No top-level forum posts.

Dormouse

Security

A modern terminal must have a conservative security stance. Dormouse won't even check whether an update is available unless you give it permission to check. If you opt in to Dormouse's push notifications and remote access, you can restrict their terminal traffic to networks you name, such as your VPN.

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 has three roles (Roles):

  • A Burrow runs your terminal sessions and decides who may reach them: Dormouse Standalone or the VS Code extension.

  • A Client reaches a Burrow's terminals from a phone: Dormouse Pocket or the one-time connection page.

  • A Relay introduces Clients to Burrows. Dormouse Hosted runs one at relay.dormouse.sh; you can self-host your own instead.

A Client reaches a terminal only once you approve it at the Burrow, typing there the two digits the Client shows. Each Burrow keeps its own access control and makes every access decision itself, trusting neither the Client nor the Relay; Hosted account sign-in alone grants no terminal access. All ceremony and terminal content between Burrow and Client is end-to-end encrypted with the Noise protocol, under keys no Relay holds. When the network allows it, terminal traffic runs directly between the two devices over WebRTC, so no server in between sees its timing or volume.

Features

Every feature is opt-in: one you do not use adds none of its risk. With none on, where a new install starts at Settings → Network → Nowhere, Dormouse opens nothing on its own, and you trust only the build you installed, its dependencies, and its release pipeline. One Hosted build can offer both a One-time link and a Hosted phone. The guarantees below are grouped by the features they come with, and each undefended edge and known gap names the features it concerns; one naming none holds for every install. No Relay or rendezvous can read terminal content or grant access; what each still sees is under What is not defended, and what each Settings → Network level connects to under Policy.

FeatureWhat Dormouse opens on its ownWhat you trust beyond this computer
One-time link. A Hosted build, the one Dormouse distributes, at Local networks only or Anywhere. You open a link in Settings for one phone; no account.While a link is open, a socket to relay.dormouse.sh for its handshake (Hosted rendezvous). Terminal traffic goes only directly between the two devices.The phone, and the page Hosted serves it, so Hosted's deployment pipeline and Cloudflare account (One-time connection).
Hosted phone. A Hosted build at Local networks only or Anywhere, enrolled with your Hosted account (Burrow enrollment), with Pocket paired.A socket to Hosted's Relay at relay.dormouse.sh, for handshakes, pairing, and any push you turn on. Terminal traffic goes directly when it can, otherwise through Hosted under Anywhere; under Local networks only, only directly.The phone, and the Pocket app Hosted serves it, so Hosted's deployment pipeline and Cloudflare account (Trust Model).
Self-hosted Relay. A self-host build, compiled from source and pointed at your own Relay (Relay origin), at Anywhere, through your Relay. The default installer keeps that Relay on your tailnet.A socket to your Relay, for handshakes, pairing, and any push you turn on. Terminal traffic goes directly when it can, with no STUN server, otherwise through your Relay.The phone, and the Pocket app your Relay serves it.

Guarantees

Every section the nightly audit checks opens with the guarantees its FAIL IF lines hold, or sits under one that does, and the published page gathers them here under each feature they come with: each opens with a link to that section, and Pinned by names what catches a break before the audit does.

Every install

GuaranteePinned by
Terminal output Terminal output cannot write your clipboard or steal focus. File access requires a user action or the gated OSC 367 open of a Tool run Dormouse launched. OSC 52 only offers a copy format; links require confirmation, naming any app scheme, or an allowed local preview; deceptive links and dangerous Windows handlers have no open action.lib/src/lib/terminal-protocol.test.ts, lib/src/lib/mouse-selection.test.ts, lib/src/lib/copy-editor.test.ts, lib/src/lib/external-links.test.ts, lib/src/lib/terminal-link-activation.test.ts, lib/src/components/ExternalLinkModalHost.test.tsx, lib/src/lib/tool-launch-latch.test.ts, lib/src/components/wall/preview-slot.test.tsx
Browser panes 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, the standalone adapters have no inbox for it to post to, and every other listener it can reach checks the sender.lib/src/lib/platform/vscode-adapter.test.ts, standalone/src/sidecar-adapters.test.ts, lib/src/lib/webview-message-listeners.test.ts
The dor control socket 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.standalone/sidecar/dor-control-server.test.js
Loopback Listeners A loopback listener grants a stranger nothing it could not get from the upstream directly.scripts/loopback-lint.mjs, lib/src/host/loopback-guard.test.ts, lib/src/host/iframe-proxy.test.ts, lib/src/host/browser-viewer.test.ts, dor-tools-builtin/test/file-viewer.test.mjs
Network policy Under Settings → Network → Nowhere, 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, browser panes, and agents reach, restored ones included, are yours.scripts/outbound-lint.mjs, lib/src/host/remote/outbound.test.ts, vscode-ext/test/outbound.test.ts, lib/src/host/remote/service.test.ts, lib/src/host/managed-voice-host.test.ts, standalone/src/updater.test.ts
Persisted state Current persistence writers never save terminal scrollback. Standalone snapshots are owner-only; VS Code controls access to its own storage. Older snapshots may hold transcripts until retired.lib/src/lib/session-save.test.ts, lib/src/lib/session-restore.test.ts; cargo test in standalone/src-tauri (the owner-only half)
Terminal context directory actions A directory a terminal reports reaches a process only as one validated path.lib/src/host/local-directory.test.ts
Spawned programs A folder you open cannot get Dormouse to run a program it planted. On Windows no program is spawned by bare name, and git in a repository you have not trusted runs no program its config names.standalone/sidecar/pty-core.test.js, lib/src/host/git-cli.test.ts, vscode-ext/test/pty-manager.test.ts
Dor Tool configuration A repository's Tool runs nothing until you trust it in Dormouse's own window. No command grants that trust, and a control character in what the repository configures is escaped before it reaches your terminal and refused before it reaches a launch.lib/src/host/tool-host.test.ts, dor/test/cli-output.test.mjs, dor/test/open-picker.test.mjs
GitHub Actions Policies Merging to main and creating a tag are admin-only, except the hosted/ tag a dedicated App records after a reviewed Hosted deploy, and every workflow this repository authors pins its actions by commit.scripts/github-state-check.mjs, scripts/workflow-lint.mjs
Automated Maintainer (tend) The bot maintainer cannot merge, tag, or read a release secret, and its token never enters its own environment..github/workflows/workflow-audit.yaml, nightly
VS Code Extension Releases Publishing the extension takes a second account's approval.scripts/github-state-check.mjs
Desktop Releases Desktop binaries are signed locally, and an installed app takes an update only under the one key it shipped trusting. CI never holds production signing or updater keys, the signing script verifies CI's attestations and hashes first, and the updater asks only dormouse.sh over HTTPS.scripts/sign-and-deploy.test.mjs, standalone/scripts/updater-trust-root.test.mjs
GuaranteePinned by
Trust boundary The Burrow bounds remote session state and handshake admission independently of the Relay. Deadlines use its own clock.lib/src/remote/burrow/burrow-bounds.test.ts, relay/test/malicious-relay.test.mjs
Relay origin A Burrow talks only to the one relay origin its build was pointed at, and a self-host build contacts Dormouse's servers only when you click a link. A stock build reaches only Hosted.lib/src/host/relay-origin.test.ts, lib/src/host/remote/service.test.ts
Direct path Under Local networks only, terminal traffic crosses only the networks you allow. Handshakes, pairing, alerts, and managed voice still pass through Hosted from any network.lib/src/host/remote/local-networks.test.ts, lib/src/remote/burrow/burrow-direct-only.test.ts, lib/src/host/remote/outbound.test.ts, scripts/outbound-lint.mjs
Direct path Under Anywhere, a paired phone may relay through Hosted, end-to-end encrypted. Cloudflare STUN sees this computer's public address as a phone connects.lib/src/remote/burrow/burrow-direct-only.test.ts, lib/src/host/remote/service.test.ts, lib/src/host/remote/outbound.test.ts, scripts/outbound-lint.mjs
One-time connection A one-time connection is one session, confirmed at the laptop. Only typing, on the laptop, the two digits that phone shows authorizes it; nothing is saved at either end, and its terminal traffic runs only directly between the two devices, over a network Settings → Network allows; Hosted carries the handshake alone.lib/src/remote/client/one-time-e2e.test.ts, lib/src/remote/burrow/one-time-runtime.test.ts, scripts/e2e-lint.mjs
Auxiliary helpers A phone sees and drives only your ordinary terminals. A helper terminal stays hidden from it until you promote it.lib/src/remote/burrow/peer-surfaces.test.ts
Origin boundary Each Hosted Worker answers only its own origin and runs only its own code. No third-party script, analytics, or framing; sign-in cookies stay on the account origin, and its session never reaches browser storage.hosted/server/tests/boundary.test.ts, hosted/server/tests/workers.test.ts
Rendezvous boundary The one-time rendezvous relays a handshake it cannot read, to one phone, until the link expires. It decodes and keeps no frame, never admits a second phone, and reads no cookie or account.hosted/server/tests/one-time.test.ts, scripts/e2e-lint.mjs
Deployment boundary Hosted runs only pgstencil releases that passed pgstencil's own audit. A production deploy carries no test controls and verifies each pgstencil package's provenance; a preview reaches no real provider.hosted/scripts/production.test.mjs, hosted/server/tests/artifacts.test.ts
Hosted Deployments Hosted runs only code from main, and a production deploy needs a maintainer's approval.scripts/github-state-check.mjs

Hosted phone

GuaranteePinned by
Trust boundary 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.lib/src/remote/burrow/burrow-runtime.test.ts, lib/src/host/remote/service.test.ts, scripts/e2e-lint.mjs, remote-lib-common/test/security-guarantees.test.mjs
Trust boundary 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.relay/test/malicious-relay.test.mjs, scripts/e2e-lint.mjs
Trust boundary A stolen or synced passkey buys sign-in, not a terminal. Every connection also needs the phone's own paired key and user presence the laptop verified itself: a fresh proof bound to that connection, or one it verified earlier for that phone, which lapses when idle and after a maximum age.lib/src/remote/burrow/burrow-runtime.test.ts, remote-lib-common/test/security-guarantees.test.mjs
Trust boundary The Burrow bounds remote session state and handshake admission independently of the Relay. Deadlines use its own clock.lib/src/remote/burrow/burrow-bounds.test.ts, relay/test/malicious-relay.test.mjs
Relay origin A Burrow talks only to the one relay origin its build was pointed at, and a self-host build contacts Dormouse's servers only when you click a link. A stock build reaches only Hosted.lib/src/host/relay-origin.test.ts, lib/src/host/remote/service.test.ts
Credentials at rest 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.scripts/deploy-lint.mjs
What crosses the boundary Push notifications are opt-in, and a push is sealed to the one phone that receives it.lib/src/remote/pocket-app/App.push.test.tsx, relay/test/push.test.mjs, remote-lib-common/test/push-seal.test.mjs
Direct path Under Local networks only, terminal traffic crosses only the networks you allow. Handshakes, pairing, alerts, and managed voice still pass through Hosted from any network.lib/src/host/remote/local-networks.test.ts, lib/src/remote/burrow/burrow-direct-only.test.ts, lib/src/host/remote/outbound.test.ts, scripts/outbound-lint.mjs
Direct path Under Anywhere, a paired phone may relay through Hosted, end-to-end encrypted. Cloudflare STUN sees this computer's public address as a phone connects.lib/src/remote/burrow/burrow-direct-only.test.ts, lib/src/host/remote/service.test.ts, lib/src/host/remote/outbound.test.ts, scripts/outbound-lint.mjs
Auxiliary helpers A phone sees and drives only your ordinary terminals. A helper terminal stays hidden from it until you promote it.lib/src/remote/burrow/peer-surfaces.test.ts
Origin boundary Each Hosted Worker answers only its own origin and runs only its own code. No third-party script, analytics, or framing; sign-in cookies stay on the account origin, and its session never reaches browser storage.hosted/server/tests/boundary.test.ts, hosted/server/tests/workers.test.ts
Account boundary A Hosted account signs you in and grants nothing more. Login never mints terminal access or stands in for pairing and presence; only an entitled account reaches managed voice, which never logs what you say.hosted/server/tests/policy.test.ts, hosted/server/tests/workers.test.ts
Relay boundary Hosted's Relay keeps each account's computers, passkeys, and push registrations to that account. It stores tokens only hashed, rechecks the owner's entitlement on every request, closes a removed computer's socket, and neither decodes a frame nor reads a push.hosted/server/tests/relay.test.ts, hosted/server/tests/relay-room.test.ts, hosted/server/tests/relay-push.test.ts
Deployment boundary Hosted runs only pgstencil releases that passed pgstencil's own audit. A production deploy carries no test controls and verifies each pgstencil package's provenance; a preview reaches no real provider.hosted/scripts/production.test.mjs, hosted/server/tests/artifacts.test.ts
Hosted Deployments Hosted runs only code from main, and a production deploy needs a maintainer's approval.scripts/github-state-check.mjs

Self-hosted Relay

GuaranteePinned by
Trust boundary 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.lib/src/remote/burrow/burrow-runtime.test.ts, lib/src/host/remote/service.test.ts, scripts/e2e-lint.mjs, remote-lib-common/test/security-guarantees.test.mjs
Trust boundary 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.relay/test/malicious-relay.test.mjs, scripts/e2e-lint.mjs
Trust boundary A stolen or synced passkey buys sign-in, not a terminal. Every connection also needs the phone's own paired key and user presence the laptop verified itself: a fresh proof bound to that connection, or one it verified earlier for that phone, which lapses when idle and after a maximum age.lib/src/remote/burrow/burrow-runtime.test.ts, remote-lib-common/test/security-guarantees.test.mjs
Trust boundary The Burrow bounds remote session state and handshake admission independently of the Relay. Deadlines use its own clock.lib/src/remote/burrow/burrow-bounds.test.ts, relay/test/malicious-relay.test.mjs
Relay origin A Burrow talks only to the one relay origin its build was pointed at, and a self-host build contacts Dormouse's servers only when you click a link. A stock build reaches only Hosted.lib/src/host/relay-origin.test.ts, lib/src/host/remote/service.test.ts
Credentials at rest 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.scripts/deploy-lint.mjs
The setup password The Relay generates its 256-bit setup credential, with no operator-supplied value, and Burrow enrollment is globally admission-limited.relay/test/setup-password-store.test.mjs, relay/test/token-bucket.test.mjs
Cross-origin access A cross-origin browser receives no grant from the Relay.relay/test/cors.test.mjs
Network posture (self-hosted) The self-host HTTPS origin may be public; its plaintext backend may not. Terminal access still needs local Burrow approval.relay/test/config.test.mjs, scripts/deploy-lint.mjs
What crosses the boundary Push notifications are opt-in, and a push is sealed to the one phone that receives it.lib/src/remote/pocket-app/App.push.test.tsx, relay/test/push.test.mjs, remote-lib-common/test/push-seal.test.mjs
What crosses the boundary Push, when enabled, cannot be aimed back into the tailnet.relay/test/push-endpoint.test.mjs
Auxiliary helpers A phone sees and drives only your ordinary terminals. A helper terminal stays hidden from it until you promote it.lib/src/remote/burrow/peer-surfaces.test.ts

How the guarantees are checked

On every pull request, tests and lints turn the mechanical half of these specs into build failures: the end-to-end channel, the installers, every loopback listener and network primitive, the workflows, and the dependency pins. The Guarantees' last column names the ones behind each row. Each lint carries a self-test that re-introduces the thing it forbids and requires the lint to go red (the lints).

Every night, and before every VS Code release, an agent per domain audits the repository against these specs (Domains): it runs every FAIL IF with evidence, then reads its domain adversarially for what no check names. A failure holds the release. Every run's result is public in the pinned Security audit status issue, and a finding's details stay private until it is fixed or 7 days old, then are published under security-audit-finding (Embargo). pgstencil audits the packages Hosted consumes in its own repository.

Every week, a canary audits the audit. It seeds already-fixed or synthetic vulnerabilities into a throwaway checkout, some beside comments that justify them, removes the tests and lints that would catch them, runs the code domains over it, and publishes only how many they caught (Canary recall).

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.

This page is docs/specs/security.md, the spec the audit runs against, published as is, with each guarantee gathered from beside the checks that hold it; the two figures above are generated from the audit's own records. It shows the guarantees for the local application, remote control, and the release pipeline; what reaches your machine is in the supply-chain disclosure.

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, and the Tool trust prompt is no boundary against it (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).

  • A local HTML or SVG document you open can send its contents off the machine. The file viewer's content policy confines what the page loads, not where its scripts navigate (Local-file viewer).

  • A mermaid sanitizer bypass in a Markdown document you open. Its diagrams reach the editor page through mermaid's strict sanitizer, not the editor's allowlist, so a bypass would run script holding the editor's save and image routes (Local-file viewer).

  • A Tool trust grant on an upstream URL trusts the URL a checkout claims. Any directory whose own .git/config claims an already-granted upstream shares that grant; a folder-only grant is not shared (Dor Tool configuration).

  • Output already flowing into a Tool's terminal at launch can, between Dormouse typing the command and the shell reporting it, make that run's OSC 367 open requests (Terminal output).

  • A terminal can choose which of your own agent sessions resumes. Output a pane prints as Dormouse quits can name any session of a supported agent, which the next restore resumes in that pane's directory without asking; it can type nothing else (Terminal output).

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

  • One-time link, Hosted phone, Self-hosted Relay. A compromised browser or operating system, on either end. Active XSS in the Pocket origin can use the phone's key and, with encrypted fallback storage, extract its private bytes (Client statics). Exactly two endpoints are trusted: the distributed Burrow binaries and the exact Pocket artifact the origin serves (Trust Model); a one-time session also trusts the page Hosted serves (One-time connection).

  • Hosted phone, Self-hosted Relay. Presence inside an open window. While this computer holds an open presence window for a phone, it connects without a new prompt, so script running in the Pocket origin, or anyone holding the unlocked phone, reaches a terminal unprompted (Presence window).

  • One-time link, Hosted phone, Self-hosted Relay. 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). An authorized session may move onto a direct connection between the two devices, after which the Relay sees that the session exists and nothing about its traffic (Direct path). Hosted's one-time rendezvous sees a handshake's timing, addresses, and frame sizes, and Cloudflare's STUN server sees every Hosted-served phone's public address, and under Anywhere this computer's (Direct path).

  • Hosted phone, Self-hosted Relay. Push replay, when push is enabled. A push proves confidentiality, not freshness: a Relay that kept an envelope can re-deliver it (Push sealing).

  • Hosted phone, Self-hosted Relay. Per-Burrow unlinkability, when push is enabled. One push endpoint per browser lets the Relay see every Burrow one phone registered (Residual metadata).

  • Hosted phone, Self-hosted Relay. Phone-key durability. Clearing site data means pairing again. Nothing is compromised; a lost key authorized nothing on its own (Client static loss).

  • One-time link, Hosted phone, Self-hosted Relay. Availability. Remote terminal access needs an online Burrow and, for new relay-backed sessions, an available Relay (Goals; keeping it up).

  • Hosted phone. Whichever account first approves a live Hosted enrollment code owns the computer it enrolls; yours is then refused as already approved. That account still pairs no phone without the two digits typed here (Trust boundary).

  • 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 repo-level snapshot-testing tokens are reachable by any workflow the bot can author. Accepted with rotation; each service's dashboard shows abuse (Automated Maintainer).

  • The workflow audit trusts what it classifies as routine. A Renovate pin bump trusts the ref Renovate picked inside that action's repository; a bot commit under a forged human author passes when an admin push that replaces nothing carries it in, such as a cherry-pick or a rebase onto a new branch, or when it was first pushed more than a quarter ago (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 are only as private as the temp directory they land in: 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).

  • Hosted phone, Self-hosted Relay. Revocation has no mechanism: nothing in the product revokes a paired phone (Revocation propagation).

  • One-time link, Hosted phone, Self-hosted Relay. There is no structured audit trail covering connects, attaches, denials, or writes, so a self-hoster cannot answer "did anyone connect to my laptop last night"; each pairing records its approval, and owner-local logs report some rejections (Burrow Authorization, Trust boundary).

  • A fork PR the bot checks out mid-run reaches it with that fork's instruction files. The harness pins project-instruction paths to the base branch only for the checkouts it makes before the agent starts (Automated Maintainer).

  • The workflow audit's window can be evaded. A pusher-set committer date hides a commit from every later window, and a branch pushed, run with repo-level secrets in scope, and deleted before the nightly fetch is in none (Automated Maintainer).

  • The audit agent can reach more than its domains need. They share AUDIT_PAT, and it shares its job with the steps that encrypt and privately file its findings. Its gh runs on that job's GITHUB_TOKEN, so it can open, edit, and comment on issues in this public repository, but cannot push or hold the Claude App's token; EMBARGO_TOKEN is out of its environment, not its reach (Domains, Embargo).

  • The audit's verdict is only as honest as its result lines. The reporting step computes each verdict from the lines a domain wrote and catches a skipped rule, but not a PASS line for a check the domain never ran (Outcomes and reporting).

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

  • Hosted phone, Self-hosted Relay. Pocket Home Screen camera verification requires real iOS hardware (Device verification).

  • Any local process can drive agent-browser's browser. Its Chrome's debugging port and its daemon's stream server take unauthenticated loopback connections, so another local account can run script there, read file:// URLs, or watch it; a web page cannot, since both refuse a foreign Origin (Loopback Listeners).

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 S268 private vulnerability reporting is disabled on the repository (gh api repos/diffplug/dormouse/private-vulnerability-reporting must report enabled: true) (rationale). Pinned by scripts/github-state-check.mjs.