Skip to content

scripts: verify-site — check served bytes and headers against the published tree - #902

Merged
nyblnet merged 1 commit into
mainfrom
ops-site-verify
Nov 7, 2026
Merged

nyblnet merged 1 commit into
mainfrom
ops-site-verify

Conversation

@nyblnet

@nyblnet nyblnet commented Nov 7, 2026

Copy link
Copy Markdown
Owner

Adds scripts/verify-site.mjs, a read-only check of what a host actually serves.

  • node scripts/verify-site.mjs --tree ../bento-site fetches every published file from bento.page and fails if any body differs from the tree. This is the "verify what the CDN serves" check that release verification has been doing by hand.
  • --candidate <url> also fetches everything from a second host and fails on any difference in status, body, or the headers a client acts on: Access-Control-Allow-Origin, Cache-Control, Content-Type, Location, Content-Disposition, X-Robots-Tag, and the security headers, so a host can't add one without anyone noticing. It also checks the URLs a static host answers on its own: /dir, /dir/, extensionless .html, directories without an index, and the 404 page.
  • On every host it runs the update path the way a file:// deck does: requests carry Origin: null, every hop including redirects must carry a CORS grant, the manifest, history and pack index must verify against PUBLIC_KEY_JWK (read from kernel/src/update.ts so it can't drift), and the shell and every pack must match their signed sha256.
  • --preview reports two differences as expected instead of failing: zone-only Cache-Control rewrites, and preview noindex. --allow <regex> accepts one named difference.

Measured: running it against bento.page and the current tree checks 100 URLs with no differences, and the slides manifest, history and 22 packs all verify.

Rig: scripts/test-verify-site.mjs (27 checks, offline) is registered at the end of validate-shared. It runs two local servers over a tree signed with a throwaway key. The identical candidate has to pass, and the rig asserts that the update path actually ran on both hosts. Each mutant has to fail: a .html → extensionless redirect, a redirect hop with no CORS grant, a manifest with no grant, an added nosniff, a one-byte shell change, a cacheable 404, and a payload edited after signing.

No changelog entry (tooling).

…d tree

Fetches every file of the bento-site tree from bento.page (and optionally a
second host) as a file:// deck would, with Origin: null. Fails on a body that
differs from the tree, on any difference in status or client-relevant headers
between the two hosts (CORS grant, Cache-Control, Content-Type, Location, and
any security header one adds), and runs the update path: signed manifest,
history and pack index verify against the kernel's release key, and the shell
and packs hash to their pins, with every redirect hop needing a CORS grant.

Offline rig with a throwaway key and mutants, in the shared CI slice.
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown

Build size

main (baab8ff) → ops-site-verify (62ca88b)

app base PR change
bento/slides 705.4 KiB 705.4 KiB 0.0 KiB (0.00%)
bento/spaces 408.2 KiB 408.2 KiB 0.0 KiB (0.00%)
bento/dash 519.9 KiB 519.9 KiB 0.0 KiB (0.00%)

Updated: 2026-11-07T13:59:47Z

@nyblnet
nyblnet merged commit 0066df0 into main Nov 7, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant