Skip to content

Supply Chain page: Cargo + bundled-runtime snapshots, provable Node pin#98

Merged
nedtwigg merged 21 commits into
mainfrom
bad-draft-supply-chain
May 26, 2026
Merged

Supply Chain page: Cargo + bundled-runtime snapshots, provable Node pin#98
nedtwigg merged 21 commits into
mainfrom
bad-draft-supply-chain

Conversation

@nedtwigg
Copy link
Copy Markdown
Member

@nedtwigg nedtwigg commented May 26, 2026

Summary

Reworks the website's /dependencies page into a fuller /supply-chain page and tightens the supply-chain story end to end.

  • New Supply Chain page (/supply-chain, replacing /dependencies) — separate sections for the bundled Node.js runtime, npm packages, direct Cargo crates, and transitive Cargo crates, with an up-front summary of our security posture (maturity gating, gated VS Code publish, offline standalone signing).
  • Cargo dependency snapshotsgenerate-deps.js now emits dependencies-npm.json, dependencies-cargo.json (direct vs transitive), and dependencies-runtime.json from cargo metadata --locked. License strings are normalized (legacy /OR, MIT moved first).
  • Provable bundled Node.js version — the runtime is pinned exactly in standalone/.node-version (22.22.3), and the build is the authority: build.rs runs --version on the binary it is about to bundle and fails the build unless it matches the pin. The supply-chain page reads the same pin, so the disclosed version provably equals what ships. The build-standalone job installs that pin via setup-node's node-version-file; other workflow jobs (which never bundle a runtime) are free to track latest 22.
  • pty-manager — always run the pty host under the editor's own Electron Node (process.execPath) instead of hunting for a system Node, which was unreliable and caused multi-second fork stalls on Windows.
  • SECURITY.md — summarizes the tend secret blast radius up front and codifies the new pin/build-verification as FAIL IF rules.

Review fixes (own commits)

  • b1e58cb — removed dead totalDependencyCount (computed, never rendered; trips noUnusedLocals).
  • e53657b — softened the Cargo copy: the transitive list is the full locked build graph (no platform/dep-kind filter), so it includes build-time, proc-macro, and platform-specific crates that don't all link into the shipped binary. Copy now says so rather than implying every crate ships.

Verification

  • node website/scripts/generate-deps.js reproduces the committed JSON with zero diff (the SECURITY.md FAIL IF holds).
  • build.rs verifies the bundled Node binary against standalone/.node-version at build time.

🤖 Generated with Claude Code

nedtwigg and others added 18 commits May 24, 2026 21:11
The /dependencies route was renamed to /supply-chain, leaving the
README pointing at a now-404 URL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches the /supply-chain route and page title.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Supply Chain page showed Requirement, Scope, and Features columns
only for direct Cargo crates, which were noisy and out of place next to
the simpler npm and transitive tables. Stop collecting those fields in
generate-deps.js, trim the DirectCargoDependency type and table to
Crate / Resolved / License, and regenerate the cargo snapshot to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a plain-language summary of what each tend-reachable secret
(TEND_BOT_TOKEN, CLAUDE_CODE_OAUTH_TOKEN, CHROMATIC_PROJECT_TOKEN)
exposes, framing TEND_BOT_TOKEN as the worst case and noting that none
escalate directly to main or deployment secrets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the hand-listed licenseAliases map with a normalizeLicense
function that converts legacy slash-separated dual licenses (e.g.
"Apache-2.0/MIT") to SPDX "OR" form, then moves MIT to the front of its
OR choice for a consistent listing — including inside parenthesized
groups like "(MIT OR Apache-2.0) AND BSD-3-Clause". Conjunctive AND
expressions and the distinct MIT-0 license are left untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The direct Cargo table was missing the Author column (the data already
carries it) and, with only three columns, its spacing diverged from the
four-column npm and transitive tables. Add the Author column with the
same cell classes as PackageTable and rename the "Resolved" header to
"Version" so all three tables share an identical column structure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a security-practices overview (with a properly styled list) to the
Supply Chain page intro, and fix link brightness: body-copy links were
dimmed by their paragraph's `opacity-70`, since opacity composites the
whole subtree. Dim body text with a text-color alpha
(text-[var(--color-text)]/70) instead so links keep full caramel
brightness, matching the dependency-table links.

Centralize the caramel link style in a single tailwind-variants `link`
variant applied to every link on the page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix the garbled dependency-cooldown bullet and explain the actual
protection (delay adoption so malicious releases get caught first),
parallel the two secret bullets ("<type> secrets for <product> are
<protection>"), and use consistent parallel product names — the VS Code
extension and the Standalone app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The standalone app ships a Node.js binary as a Tauri sidecar
(build.rs), but it was disclosed nowhere. Pin it exactly in a new
.nvmrc as the single source of truth shared by CI's setup-node
(node-version-file) and the bundling step, generate a
dependencies-runtime.json snapshot, render it as a "Bundled Runtime"
section on /supply-chain, and document it plus new FAIL IFs in
SECURITY.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The VS Code extension forked the pty host under a user-installed
system Node on macOS/Linux (and Electron's Node only on Windows),
hunting through nvm/Homebrew/PATH — unreliable, and on Windows the
bogus Unix-path fallback caused multi-second fork stalls.

Always use process.execPath instead. node-pty ships N-API prebuilds
that load across runtimes, and the host re-execs as Node via the
inherited ELECTRON_RUN_AS_NODE, so no external Node is needed. Update
the /supply-chain "Bundled Runtime" copy to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dead code: the aggregate was computed but never rendered, and trips
noUnusedLocals under tsc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The supply-chain page disclosed an exact Node version, but the bundle
just copied whatever node was on the build machine's PATH — disclosed
and shipped were coupled only by convention.

Make build.rs the authority: it runs --version on the binary it's
about to bundle and fails the build unless it matches the pin, now at
standalone/.node-version (the distributed runtime, scoped away from CI
tooling). generate-deps.js reads the same pin, so the page provably
equals what ships. Cross-compile skips the exec check (can't run a
foreign-arch binary).

Only the build-standalone release job needs the exact pin (via
setup-node node-version-file); every other workflow goes back to
node-version: 22 since its interpreter is never bundled. SECURITY.md
reframed around provable disclosure with tighter FAIL IFs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The transitive list is the full locked build graph (cargo metadata with
no platform/dep-kind filter), so it includes build-time, proc-macro, and
platform-specific crates that don't all link into the shipped binary.
Soften the copy to match rather than implying every crate ships.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 26, 2026

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 380a434
Status: ✅  Deploy successful!
Preview URL: https://ac851a58.mouseterm.pages.dev
Branch Preview URL: https://bad-draft-supply-chain.mouseterm.pages.dev

View logs

Copy link
Copy Markdown
Collaborator

@dormouse-bot dormouse-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One copy nit in the new TEND_BOT_TOKEN bullet — looks like a mid-edit artifact left a dangling fragment. The rest reads well; the build-time Node pin verification and the scoped node-version-file change all line up with the new SECURITY.md FAIL IF rules.

Comment thread SECURITY.md

An attacker who lands a prompt injection in tend's harness can reach three secrets. None of them escalates directly into malicious content on the `main` branch or into any deployment-related secret — those paths stay admin-gated. The boundaries we accept are codified below.

- `TEND_BOT_TOKEN` (worst case): full `repo` + `workflow` write access *as a trusted collaborator*. Direct uses are issue/PR spam, force-pushing or deleting feature branches, and persistent compromise by authoring new workflows (persistent compromise mitigated by [`workflow-audit.yaml`](.github/workflows/workflow-audit.yaml)). Authoring a workflow is also the mechanism by which `CHROMATIC_PROJECT_TOKEN` is reached. and the bot's trusted identity. **It cannot itself merge to `main`, push tags, or reach env-scoped secrets, but the bot's trusted identity can be used to social-engineer an admin toward a `main` merge.**
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The . and the bot's trusted identity. fragment reads as a leftover from an edit — it has no verb, and the bot's trusted identity is already covered by the bold sentence that follows. Suggest dropping it:

Suggested change
- `TEND_BOT_TOKEN` (worst case): full `repo` + `workflow` write access *as a trusted collaborator*. Direct uses are issue/PR spam, force-pushing or deleting feature branches, and persistent compromise by authoring new workflows (persistent compromise mitigated by [`workflow-audit.yaml`](.github/workflows/workflow-audit.yaml)). Authoring a workflow is also the mechanism by which `CHROMATIC_PROJECT_TOKEN` is reached. and the bot's trusted identity. **It cannot itself merge to `main`, push tags, or reach env-scoped secrets, but the bot's trusted identity can be used to social-engineer an admin toward a `main` merge.**
- `TEND_BOT_TOKEN` (worst case): full `repo` + `workflow` write access *as a trusted collaborator*. Direct uses are issue/PR spam, force-pushing or deleting feature branches, and persistent compromise by authoring new workflows (persistent compromise mitigated by [`workflow-audit.yaml`](.github/workflows/workflow-audit.yaml)). Authoring a workflow is also the mechanism by which `CHROMATIC_PROJECT_TOKEN` is reached. **It cannot itself merge to `main`, push tags, or reach env-scoped secrets, but the bot's trusted identity can be used to social-engineer an admin toward a `main` merge.**

Copy link
Copy Markdown
Collaborator

@dormouse-bot dormouse-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI: Standalone Smoketest is failing because of this PR's new build-time gate. The standalone-smoketest job in .github/workflows/ci.yml runs cargo check, which now triggers build.rs's version verification — but its setup-node step still pins node-version: 22, which resolved to 22.22.3 on the runner and trips the pin:

failed to prepare bundled Node.js runtime: "bundled Node.js 22.22.3 does not match the standalone/.node-version pin 22.17.1."

The gate now affects every build of the standalone crate, not just the release build-standalone job — so this CI job needs the pinned runtime too. The one-line fix matches the PR's single-source-of-truth intent:

      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version-file: standalone/.node-version

(.github/workflows/ci.yml, the standalone-smoketest job — line is outside this PR's diff, so I can't attach an inline suggestion.) The build-and-test job doesn't compile the crate, so it can stay on node-version: 22. Happy to push this fix to the branch if you'd like.

Cloudflare Pages also shows failed, but it completed in 0s with no accessible Actions log (it's the external Pages integration, not a workflow run). The website itself built and tested clean in the Build & Test job (pnpm --filter dormouse-website build passed), so this looks like a deploy-side issue rather than a code problem — though I can't confirm the cause from CI here.

nedtwigg and others added 2 commits May 26, 2026 14:39
Update the standalone/.node-version pin and regenerate the supply-chain
runtime disclosure to match. build.rs verifies the bundled binary against
this pin at build time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
generate-deps.js shells out to `cargo metadata`, but the website deploy
environment (Cloudflare Pages) has no Rust toolchain, so prebuild failed
with spawnSync cargo ENOENT. The snapshots are committed artifacts,
regenerated as a manual release step (docs/specs/deploy.md) and verified
by the security-audit FAIL IF, so the build should just consume them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Show the dependency count inline beside each section title and let
  descriptions contain explicit newlines (whitespace-pre-line).
- Tighten the intro copy and bundled-runtime description.
- Add cargo author/homepage overrides for libappindicator and
  libappindicator-sys, whose published Cargo.toml omits both, so they
  link to tauri-apps/libappindicator-rs and credit Tauri Apps
  Contributors instead of rendering "Unknown".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@nedtwigg nedtwigg merged commit e27a348 into main May 26, 2026
4 checks passed
@nedtwigg nedtwigg deleted the bad-draft-supply-chain branch May 26, 2026 22:36
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.

2 participants