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
2 changes: 1 addition & 1 deletion .github/audit/hosted.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Read `docs/specs/hosted.md`, `docs/specs/one-time.md` (its "Wire contract", "Hos

Check the installed `pgstencil` and `@pgstencil/auth` packages against "Deployment boundary" by reading each `dist/provenance.json`, `pnpm-lock.yaml` and `pnpm-workspace.yaml` (no `.npmrc` is tracked, so the registry is npm's default), and Hosted's runtime imports. Audit no package code, except that the `FAIL IF`s which name the packed adapter (CSRF and CORS, cookie attributes and JSON scrubbing, the revoked-login callback) may read `@pgstencil/auth`'s `dist/` to verify how Hosted configures it.

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). A package counts only from `verified`, never from `invalid` or `missing`. Decode the verified SLSA DSSE payload and the Fulcio certificate in that bundle and judge every clause of S119 against them, the payload's `externalParameters.workflow` and `resolvedDependencies` included. 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:
Verify npm's signed SLSA provenance for each installed package/version. Run `npm audit signatures --json --include-attestations` in `hosted/`, as the production `verify` job does. A package counts only from `verified`, never from `invalid` or `missing`. Decode the verified SLSA DSSE payload and the Fulcio certificate in that bundle and judge S119's certificate and payload clauses against them, and its job clause against that step, the payload's `externalParameters.workflow` and `resolvedDependencies` included. 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
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/hosted-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ jobs:
- run: pnpm build:hosted
- name: Require accepted package provenance
run: node --input-type=module -e 'import { verifyPackages } from "./hosted/scripts/production.mjs"; await verifyPackages();'
# npm exits nonzero for a failure anywhere in Hosted's tree; only pgstencil's packages gate here.
- name: Require npm's registry signature and provenance attestation on each pgstencil package
working-directory: hosted
run: |
npm audit signatures --json --include-attestations > "$RUNNER_TEMP/signatures.json" || true
jq -e '[.invalid[], .missing[] | select(.name == "pgstencil" or .name == "@pgstencil/auth")] == []
and (["pgstencil", "@pgstencil/auth"] - [.verified[] | select(.attestations.provenance) | .name] == [])' \
"$RUNNER_TEMP/signatures.json"
# Every Worker's static files, each under its own `dist/<worker>/`.
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
Expand Down
6 changes: 3 additions & 3 deletions docs/specs/security-hosted.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ What ElevenLabs keeps of it is `docs/specs/security.md` -> "What is not defended

| Guarantee | Feature | Pinned by |
| --- | --- | --- |
| **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 phone | `hosted/server/tests/relay.test.ts`, `hosted/server/tests/relay-room.test.ts`, `hosted/server/tests/relay-push.test.ts` |
| **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 each time it admits a request or socket, closes a socket already open for a removed or de-entitled computer no later than the room's sweep, and neither decodes a frame nor reads a push. | Hosted phone | `hosted/server/tests/relay.test.ts`, `hosted/server/tests/relay-room.test.ts`, `hosted/server/tests/relay-push.test.ts` |

**The Hosted Relay** on the relay Worker, and its account routes on the account Worker: `docs/specs/hosted.md` -> "Relay", "Relay sockets", and "Burrow enrollment" own them; these are the checks on them. Inspect `relayApiRoutes` in `hosted/server/relay-api.ts`, `hosted/server/relay-auth.ts`, `relaySocketRoutes` in `hosted/server/relay-sockets.ts`, `RelayRoom` in `hosted/server/relay-room.ts`, the frame layer in `remote-lib-common/src/remote/relay-routing.ts`, `relayAccountRoutes` in `hosted/server/relay-account.ts`, `cookieEntitled` in `hosted/server/account-gate.ts`, `hosted/server/relay-push.ts`, and `hosted/server/dormouse-migrations/002_relay.sql`, `003_relay_push.sql`, `004_relay_enrollment_redeemed.sql`, `006_relay_setup_token_claim.sql`, and `007_independent_voice.sql`.

Expand Down Expand Up @@ -89,11 +89,11 @@ Pinned by `hosted/server/tests/relay.test.ts`, `hosted/server/tests/relay-push.t

| Guarantee | Feature | Pinned by |
| --- | --- | --- |
| **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. | One-time link, Hosted phone | `hosted/scripts/production.test.mjs`, `hosted/server/tests/artifacts.test.ts` |
| **Hosted runs only pgstencil releases that passed pgstencil's own audit.** A production deploy carries no test controls and first verifies each pgstencil package's provenance and npm attestation; a preview reaches no real provider. | One-time link, Hosted phone | `hosted/scripts/production.test.mjs`, `hosted/server/tests/artifacts.test.ts` |

- **FAIL IF** `S117` a production Worker exposes the captured-email inbox or deterministic clock controls, or imports the testing injection module; inspect `hosted/server/worker.ts`, `hosted/server/relay-worker.ts`, `hosted/server/voice-worker.ts`, the build configuration, and `hosted/server/tests/worker-entry.ts`.
- **FAIL IF** `S118` either installed pgstencil package lacks `dist/provenance.json`, records `dirty`, names a commit that is not 40 lowercase hex or differs from the other's; `pnpm-lock.yaml` resolves either package from outside npm or without an integrity hash; 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** `S119` 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** `S119` the production `verify` job, which `deploy` needs, can pass while `npm audit signatures --include-attestations` finds either installed pgstencil package's registry signature or SLSA provenance attestation missing or invalid; or, beyond what npm checks (rationale), either package's verified attestation has a Fulcio certificate SAN not naming `diffplug/pgstencil` `.github/workflows/release.yml` on `refs/heads/main`, a source-repository digest (OID `1.3.6.1.4.1.57264.1.13`) other than `dist/provenance.json`'s commit, or a signed subject/payload that disagrees with the installed package, certificate, or commit.
- **FAIL IF** `S120` 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** `S121` 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`.

Expand Down
7 changes: 7 additions & 0 deletions docs/specs/security-hosted.rationale.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ Cookie reads (2026-10-11):
- Writes stay unlooked-up so the adapter's Origin and CSRF refusals still come before any database read. The one write that starts something with a login is `link-social`, whose callback is open: an unlisted login can attach another identity its holder controls to its own unlisted account (pinned in `workers.test.ts`), which grants nothing, since every read still deletes its logins. Before 2026-10-11, the callback's lookup ended that login first.
- The deletion stays on the read rather than moving to sign-in: the unlisted logins it removes are created inside a provider callback the packed adapter completes, where only its signed session cookie names them. Every use of a login starts with a read, so the gated read is its first use.
- 403, not a signed-out answer, so a refusal never reads as a logout.

## Deployment boundary

pgstencil provenance at deploy time (2026-10-20):

- Renovate opens a pgstencil bump as soon as a release is approved (`.github/renovate.json`), and a production deploy can follow its merge within hours, between two nightly audits. The `verify` job therefore asks npm for each package's registry signature and provenance attestation before every deploy.
- npm verifies the signature, the certificate chain, and the subject against the registry's tarball, not who signed: the payload's workflow claim is the signer's own statement. Reading the certificate's SAN and source-repository digest, and the commit's place on `main` and its audit, means decoding the bundle and calling the GitHub API, so that stays in the nightly audit rather than in a deploy script.
Loading