Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions .github/audit/hosted.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,39 @@ Read `docs/specs/hosted.md`, `hosted/server/`, `hosted/src/`, `hosted/scripts/`,
Deployment boundary quantifies over the preview and production paths, which
live in those scripts and workflows rather than in the Worker.

Verify the vendored packages by their provenance rather than by reading them:
hash each archive in `vendor/` against `vendor/build.json`; read each archive's
own claim with `tar -xOf vendor/<archive>.tgz package/dist/provenance.json` and
check that it names `build.json`'s commit and does not record `dirty`; then
check that commit against pgstencil `main` and its audit:
Verify the installed `pgstencil` and `@pgstencil/auth` packages by reading each
`dist/provenance.json`, without auditing package code. Require a 40-hex commit,
no `dirty: true`, and the same commit in both packages. Confirm `pnpm-lock.yaml`
resolves both through the npm registry with integrity hashes. Inspect Hosted's
runtime imports for references to a sibling pgstencil checkout.

Verify npm's signed SLSA provenance for each installed package/version. Use a
temporary npm consumer of the exact locked versions and `npm audit signatures
--json --include-attestations` (npm does not audit a pnpm-only install). Each
package **must appear in `verified`** with a SLSA provenance bundle; reject
`invalid` and `missing` entries too. Decode the verified SLSA DSSE payload and
the Fulcio certificate in that bundle. The certificate SAN must be
`https://github.com/diffplug/pgstencil/.github/workflows/release.yml@refs/heads/main`;
its source-repository digest extension `1.3.6.1.4.1.57264.1.13` must equal
the installed `dist/provenance.json` commit. The signed subject must identify
the installed package/version and digest; the payload's
`externalParameters.workflow` and `resolvedDependencies` must agree with the
certificate and commit. npm verifies the signature and subject digest, but
the payload's workflow claim alone is not the signer identity. Then check the
commit against pgstencil `main` and its audit:

```sh
gh api repos/diffplug/pgstencil/compare/<commit>...main --jq .status
gh api repos/diffplug/pgstencil/commits/<commit>/check-runs \
--jq '.check_runs[] | select(.name=="security-audit") | .conclusion'
```

The first must be `ahead` or `identical`, the second `success`. The packed code
The first must be `ahead` or `identical`. A commit can carry several
`security-audit` runs, and a `cancelled` one, from a manual dispatch that was
stopped, is not a verdict: ignore `cancelled`, then require at least one
`success` and no other conclusion. The released code
itself is audited in `diffplug/pgstencil` by that repository's own
`security-audit` workflow against its `SECURITY.md`; do not audit the tarballs'
`security-audit` workflow against its `SECURITY.md`; do not audit the installed packages'
contents here — audit how `hosted/` configures the adapter. Distinguish tested
code from pending production configuration; do not treat local provider
simulations as live OAuth acceptance, and treat a checked-in placeholder as no
Expand All @@ -42,7 +60,7 @@ report its state as INFO under `### Qualitative findings`.

## Qualitative pass

You own `hosted/` and `vendor/`. You **read** `.github/workflows/hosted-preview.yml`
You own `hosted/` and the installed pgstencil boundary. You **read** `.github/workflows/hosted-preview.yml`
and `.github/workflows/hosted-production.yml` for the Deployment boundary above,
but you do not own them: `ci-and-secrets` owns those workflows' credentials,
environments, reviewers, and token placement
Expand Down
10 changes: 10 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@
"matchPackageNames": ["node"],
"enabled": false
},
{
"description": "pgstencil releases are staged by a workflow and approved with 2FA; group the three packages and open the PR as soon as one is approved",
"matchManagers": ["npm"],
"matchPackageNames": ["pgstencil", "@pgstencil/**"],
"groupName": "pgstencil",
"groupSlug": "pgstencil",
"separateMajorMinor": false,
"minimumReleaseAge": null,
"schedule": ["at any time"]
},
{
"description": "canopy pins the pristine @xterm/addon-webgl AND @xterm/xterm to the exact commit the SDF fork's sdf branch is based on (the addon's beta counter is offset from core's; canopy/README.md records the current correspondence). They are the UpstreamVsFork regression baseline and move in lockstep with the hand-cut @diffplug/xterm-addon-webgl-sdf tarball, which Renovate cannot see, so bumping either one is a manual edit made when the fork rebases — never a Renovate bump (see canopy/README.md and docs/specs/webgl-text.md). File-scoped: lib and standalone still track upstream betas via the xterm group above. Must stay last; later rules win",
"matchManagers": ["npm"],
Expand Down
8 changes: 4 additions & 4 deletions docs/specs/hosted.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@

**Must run committed Better Auth migrations before deploying code that needs them, never during a Worker request.** Postgres is reached through an uncached Hyperdrive binding. The runtime creates and closes its database pool within each request.

**Must pin locally packed core/auth packages through root pnpm overrides and commit archives, provenance, and lockfile together.** `vendor/build.json` records the source commit, archive hashes, and `dirty: false`, which production preflight requires; each archive's `package/dist/provenance.json` names its source commit, and a pack pgstencil marked dirty cannot be vendored. No runtime import depends on a sibling checkout. The auth migrations remain owned by the package.
**Must install released core/auth packages from npm and commit their lockfile integrity hashes.** The installed packages' `dist/provenance.json` must name the same clean pgstencil commit; no runtime import depends on a sibling checkout. The auth migrations remain owned by the package.

**Must declare every peer dependency of the pinned archives in `hosted/package.json`**, so they share Hosted's copy and Renovate updates them.
**Must declare every peer dependency of the installed packages in `hosted/package.json`**, so they share Hosted's copy and Renovate updates them.

Source of truth: `auth` in `hosted/server/worker.ts`; `workerApp` in `hosted/server/worker-app.ts`; `migrations` in `hosted/server/migrations.ts`; `scripts/sync-pgstencil.mjs`. Pinned by `hosted/server/tests/artifacts.test.ts`.
Source of truth: `auth` in `hosted/server/worker.ts`; `workerApp` in `hosted/server/worker-app.ts`; `migrations` in `hosted/server/migrations.ts`; `verifyPackages` in `hosted/scripts/production.mjs`. Pinned by `hosted/server/tests/artifacts.test.ts`.

## Identity and login

Expand Down Expand Up @@ -65,7 +65,7 @@ Source of truth: `touchesHosted` in `hosted/scripts/changed.mjs`; `.github/workf

## Production releases

**Must deploy only manually selected main revisions after Hosted tests/build and accepted clean package provenance.** `verifyPackages` checks the archive hashes and each archive's own packed provenance; preflight checks uncached Hyperdrive, matching migration/runtime database identity with distinct roles, and required Worker secret names. Back up, encrypt, decrypt, and restore-test before applying migrations; upload only the encrypted archive. Production has no public candidate URL.
**Must deploy only manually selected main revisions after Hosted tests/build and accepted clean package provenance.** `verifyPackages` checks both installed packages' clean, matching provenance; preflight checks uncached Hyperdrive, matching migration/runtime database identity with distinct roles, and required Worker secret names. Back up, encrypt, decrypt, and restore-test before applying migrations; upload only the encrypted archive. Production has no public candidate URL.

**Must record an immutable annotated hosted/YYYY-MM-DD tag only after live verification.** Tags identify the deployed commit and verification run/attempt; retries are idempotent and redeployments get new tags. Dating and repeat-deployment suffixes: `recordDeployment`. Code rollback never reverses migrations.

Expand Down
2 changes: 1 addition & 1 deletion docs/specs/security-audit.rationale.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The three release-gate pieces are named separately because they break independen

One context holding every subject matter degrades application security — the domain with the most code behind it, and the easiest to crowd out with API responses.

Hosted accounts were split out of `application-security` on 2026-09-21. That domain already carried remote control (where the depth goes), the local boundaries, Hosted, and the catch-all sweep, and it had overrun the 32-minute deadline more than once, so a remote-control pass that ran out of time took the Hosted results down with it. Hosted is a disjoint tree — `hosted/`, `vendor/`, and the two `hosted-*.yml` workflows it reads for the Deployment boundary — with its own spec, so it splits cleanly and now writes its own fragment. It also gives the pgstencil provenance checks a prompt that is about them rather than a paragraph inside one about pairing code. It runs on Opus for the same reason `application-security` does: the Worker's origin gate and the deployment path are read, not enumerated.
Hosted accounts were split out of `application-security` on 2026-09-21. That domain already carried remote control (where the depth goes), the local boundaries, Hosted, and the catch-all sweep, and it had overrun the 32-minute deadline more than once, so a remote-control pass that ran out of time took the Hosted results down with it. Hosted is a disjoint tree — `hosted/`, its installed pgstencil dependencies, and the two `hosted-*.yml` workflows it reads for the Deployment boundary — with its own spec, so it splits cleanly and now writes its own fragment. It also gives the pgstencil provenance checks a prompt that is about them rather than a paragraph inside one about pairing code. It runs on Opus for the same reason `application-security` does: the Worker's origin gate and the deployment path are read, not enumerated.

Folding the application-security scope back into a shared context is how that spec stops being audited without anyone deciding to stop auditing it.

Expand Down
7 changes: 4 additions & 3 deletions docs/specs/security-hosted.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ Pinned by `hosted/server/tests/workers.test.ts` and `hosted/server/tests/policy.

## Deployment boundary

**Must vendor pgstencil from a commit on its `main`.** A Dormouse branch may vendor a pgstencil branch while a cross-repo change is in flight; `main` must not merge it until pgstencil has.
**Must depend on a released pgstencil** whose installed `dist/provenance.json` names a commit on pgstencil `main` with a passing `security-audit`. The core and auth packages must name the same clean commit.

- **FAIL IF** a production Worker exposes the captured-email inbox or deterministic clock controls, or imports the testing injection module; inspect `hosted/server/worker.ts`, the build configuration, and `hosted/server/tests/worker-entry.ts`.
- **FAIL IF** an archive's SHA-256 differs from `vendor/build.json`, `build.json` records `dirty`, either archive's `package/dist/provenance.json` is missing, records `dirty`, or names a commit other than `build.json`'s, the core/auth pnpm overrides cease resolving to those archives, or a runtime import depends on a sibling source checkout.
- **FAIL IF** `vendor/build.json`'s commit is not on pgstencil `main` (`gh api repos/diffplug/pgstencil/compare/<commit>...main`, status `ahead` or `identical`), or that commit's `security-audit` check run (`gh api repos/diffplug/pgstencil/commits/<commit>/check-runs`) is missing or not `success`. pgstencil's own audit is the evidence for the packed code; Dormouse audits only how Hosted configures it.
- **FAIL IF** either installed pgstencil package lacks `dist/provenance.json`, records `dirty`, or names a different commit; `pnpm-lock.yaml` resolves either package from outside npm; or a runtime import depends on a sibling pgstencil checkout. Inspect `verifyPackages` in `hosted/scripts/production.mjs`, `hosted/server/tests/artifacts.test.ts`, and Hosted runtime imports.
- **FAIL IF** either installed package lacks a verified npm SLSA provenance attestation whose Fulcio certificate SAN names `diffplug/pgstencil` `.github/workflows/release.yml` on `refs/heads/main`, whose source-repository digest (OID `1.3.6.1.4.1.57264.1.13`) equals `dist/provenance.json`'s commit, or whose signed subject/payload disagrees with the installed package, certificate, or commit.
- **FAIL IF** that commit is not on pgstencil `main` (`gh api repos/diffplug/pgstencil/compare/<commit>...main`, status `ahead` or `identical`), or its `security-audit` check runs (`gh api repos/diffplug/pgstencil/commits/<commit>/check-runs`) include no `success`, or any conclusion other than `success` and `cancelled`. pgstencil audits the released code; Dormouse audits only how Hosted configures it.
- **FAIL IF** the local email inbox accepts a foreign Host or Origin or cross-site Fetch Metadata; inspect `allowedDevRequest` in `hosted/server/dev-host-guard.ts`, including the upgrade guard in `hosted/server/dev.ts`.

- **FAIL IF** the production deploy can proceed without `preflight` establishing an uncached Hyperdrive, a matching migration/runtime database, and distinct runtime and migration roles; inspect `preflight` in `hosted/scripts/production.mjs` and its ordering ahead of the deploy step in `.github/workflows/hosted-production.yml`.
Expand Down
3 changes: 2 additions & 1 deletion docs/specs/security-supply-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ Source of truth: `bundle_node_runtime` / `verify_node_version` in `standalone/sr

## Cooldown and alerts

**Maturity gating runs in both the pnpm configuration and the Renovate configuration.**
**Maturity gating runs in both the pnpm configuration and the Renovate configuration, except for pgstencil releases audited on their main commit, staged, and approved with 2FA.** (rationale)

- **FAIL IF** `pnpm-workspace.yaml` is missing `minimumReleaseAge: 1440`.
- **FAIL IF** `minimumReleaseAgeExclude` in `pnpm-workspace.yaml` contains anything except `pgstencil` and `@pgstencil/*`, or a Renovate package rule sets `minimumReleaseAge: null` for any package outside `pgstencil` and `@pgstencil/**`.
- **FAIL IF** `.github/renovate.json` is missing `npm` or `cargo` from `enabledManagers` (npm covers `/`; cargo covers `/standalone/src-tauri`), or is missing `minimumReleaseAge` package rules for those managers (rationale).
- **FAIL IF** `.github/renovate.json` has no `vulnerabilityAlerts` block, or that block does not set `minimumReleaseAge` **explicitly**. Renovate's built-in default for that block is `minimumReleaseAge: null`, force-applied before lookup, so *omitting* the key drops the cooldown rather than inheriting it from `packageRules`. Keeping it is deliberate (rationale).
- **FAIL IF** secret scanning or its push protection is disabled on the repository (`gh api repos/diffplug/dormouse --jq .security_and_analysis`), or Dependabot alerts are off (`GET /repos/diffplug/dormouse/vulnerability-alerts` must answer 204, not 404). Push protection is the one control that acts *before* a credential lands, blocking a push whose diff carries a recognized provider token; it applies to `dormouse-bot` too (rationale).
2 changes: 2 additions & 0 deletions docs/specs/security-supply-chain.rationale.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Why alternate version declarations are excluded: in the pinned [setup-node imple

What the `minimumReleaseAge` package rules are: the Renovate equivalent of the pnpm dependency cooldown window, applied per manager.

The pgstencil exception has a different gate: its release workflow stages a version only after a passing `security-audit` on the packaged commit, and a human approves the staged package with npm 2FA before it becomes public. The package carries that commit in `dist/provenance.json`; Hosted verifies matching clean commits for core and auth, and its audit checks npm-signed workflow attestations bind those bytes to that commit. The exclusion list is deliberately narrow so other dependencies retain the withdrawal window.

Why the `vulnerabilityAlerts` cooldown is kept rather than dropped for speed: it guards the opposite threat from the alert itself — a compromised release that gets yanked within a day, which a reviewer reading a dependency diff cannot detect the way the ecosystem's own yank process can. Nothing here auto-merges, and the Dependabot alert already makes the vulnerability visible the moment it is published, so what the cooldown costs is a day before the remediation PR appears, not a day before anyone knows.

Why push protection covering `dormouse-bot` is the point rather than an incidental: an injected agent pasting a token into a file is exactly the shape it stops.
2 changes: 1 addition & 1 deletion docs/specs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ last column means nothing cheaper does.
| **Push, when enabled, cannot be aimed back into the tailnet.** | [What crosses the boundary](./security-remote.md#what-crosses-the-boundary) | `relay/test/push-endpoint.test.mjs` |
| **Every dependency that reaches a machine is disclosed** at [dormouse.sh/supply-chain](https://dormouse.sh/supply-chain), and a change without the disclosure fails CI. | [Disclosure](./security-supply-chain.md#disclosure) | `.github/workflows/ci.yml` |
| **The bundled runtime is the version disclosed.** The build verifies the binary against the pin. | [Bundled runtime](./security-supply-chain.md#bundled-runtime) | `standalone/src-tauri/build.rs` |
| **No newly published dependency is adopted for 24 hours**, security fixes included. | [Cooldown and alerts](./security-supply-chain.md#cooldown-and-alerts) | audit |
| **Dependencies wait 24 hours**, except audited pgstencil releases approved with 2FA. | [Cooldown and alerts](./security-supply-chain.md#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](./security-ci.md#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)](./security-ci.md#automated-maintainer-tend) | `.github/workflows/workflow-audit.yaml`, nightly |
| **Publishing the extension takes a second human's approval.** | [VS Code Extension Releases](./security-ci.md#vs-code-extension-releases) | audit |
Expand Down
Loading
Loading