Skip to content

Ship a Claude Desktop bundle with every local-MCP release, and explain the install scripts - #202

Merged
aterga merged 11 commits into
mainfrom
claude/secondary-mcp-local-deploy-jpof9i
Sep 24, 2026
Merged

aterga merged 11 commits into
mainfrom
claude/secondary-mcp-local-deploy-jpof9i

Conversation

@aterga

@aterga aterga commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Two pieces of the local-MCP install story, both riding dist's custom-job seam in the release pipeline:

  1. Every imcp2-local release now ships imcp2-local.mcpb — download, double-click, and Claude Desktop installs and manages the server. No PATH, no setup. setup --print had been promising this "once released".
  2. The release notes explain the install scripts before the command, and tell you what to do after it; the crate README documents a verified install beside the one-liner.

The bundle

One bundle covers every Claude Desktop platform. MCPB's platform_overrides key on the OS alone, not the CPU, so a single bundle cannot choose between the Apple Silicon and Intel builds. It carries a universal macOS binary instead — the two release builds joined with lipo — beside the Windows binary. dist has no universal-binary support, so this is the one genuinely new build step.

  • Each slice is byte-identical to its attested archive's binary: the build script extracts each slice with lipo -thin, compares it with cmp, and stops on any difference. This is what keeps the arm64 slice's ad-hoc signature valid, and Apple Silicon won't execute arm64 code without one.

  • Every input archive must carry the release workflow's attestation for that very tag before it is used. The job goes on to attest the bundle, so a swapped input would otherwise come out wearing a fresh, legitimate attestation.

  • Building and publishing are separate jobs, split by privilege. Building runs code this repository didn't write (the MCPB CLI and its npm dependencies), so that job holds only a read-only token and no OIDC. A second job, which runs nothing but pinned actions and gh, attests the bundle and attaches it to the release.

  • Claude Desktop appends .exe itself for binary servers, so the manifest names the binary once, with no Windows override.

  • The manifest's tool list is read off the shipped binary's own tools/list, using the server's display titles, so the install dialog can't advertise a surface the server lacks:

    Sign in with Internet Identity · Check sign-in status · Open an app (resolve origin + discover canisters) · Query a canister (Candid method or OQL) · Make a canister update call · Get Candid interface · Get a canister's API documentation · Get the OQL schema · Get the OQL query guide · Get the textual Candid guide · Get your principal at an app · List your accounts at an app

  • Fixed manifest fields live in crates/imcp2-local/mcpb/, with the repository's ICP logo (512×512, the size Claude Desktop recommends) and the published privacy policy, which Anthropic's directory requires of any listing. Display name and keywords follow the test bundle you built by hand earlier; adjust freely.

It is not code-signed yet, and the README says what that means: an unverified-developer warning; possibly a Gatekeeper refusal on the server's first macOS launch, since a browser download is quarantined and the binary isn't notarized; and an outright block in organizations that limit Claude Desktop to directory-listed extensions, which is what your own install attempt hit. Signing stays blocked on the organization's Developer ID credentials.

The release-note summary

The summary is prepended to every future release's notes, from .github/release-notes/imcp2-local-install-note.md. What it says is what I found by reading the shipped installers:

  • Both install into ~/.cargo/bin with an auto-updater and put that directory on PATH. The shell script appends to every shell profile it finds; the PowerShell one edits the Path registry key.
  • Where sha256sum is missing (older macOS releases, for one), the shell script prints a one-line notice and installs without checking its checksum. The PowerShell installer checks none.
  • What establishes provenance is the archive's attestation, checked against the release's own tag as the README's verified install does.

It then points at imcp2-local setup / --print / --remove, and at the bundle for Claude Desktop users.

Related issues

Follows the 0.5.0 release, the first imcp2-local binary release.

Changes

  • .github/scripts/build-mcpb.sh assembles the bundle from the release's own published archives.

    • Each archive is checked against its .sha256, falling back to shasum where sha256sum is missing so it doesn't repeat the installer's gap.
    • Each must also pass gh attestation verify --signer-workflow …/imcp2-local-release.yml --source-ref refs/tags/<tag> --deny-self-hosted-runners. That check is mandatory in CI; MCPB_ALLOW_UNATTESTED=1 opts out locally only and fails the build under CI.
    • After lipo -create, each slice is cmp-checked against its attested input.
    • After the last fetch, the script drops GH_TOKEN, GITHUB_TOKEN and the OIDC request variables from its environment.
    • It then renders the manifest and runs mcpb validate + pack, using the CLI installed from the lockfile below.
  • .github/scripts/mcpb-cli/package.json + package-lock.json pin the MCPB CLI's whole dependency tree: 55 packages, every one with an integrity hash and resolved from registry.npmjs.org, none with an install script. The script installs them with npm ci --ignore-scripts into its scratch directory and invokes the local CLI, with no npx.

  • .github/workflows/imcp2-local-mcpb.yml defines two jobs:

    • build (macos-15, contents: read + attestations: read, no OIDC) runs the script. macOS provides the native lipo, and the job executes the binary it just built to read the tool list.
    • publish (ubuntu-24.04, write + id-token + attestations) receives the bundle as an artifact, attests it as the new artifact it is, and attaches it to the release.
  • .github/workflows/imcp2-local-install-note.yml + .github/release-notes/imcp2-local-install-note.md — the notes job (ubuntu-24.04), made idempotent by a marker line that is matched in the shell rather than through a pipe, so the check stays reliable under pipefail.

  • dist-workspace.toml — both jobs under post-announce-jobs, plus github-custom-job-permissions granting the bundle workflow id-token/attestations beyond the default contents: write. dist puts that union on the caller job, and each of the bundle's two jobs takes only its share.

  • .github/workflows/imcp2-local-release.yml — regenerated with dist 0.31.0, never hand-edited.

  • crates/imcp2-local/README.md — the Install section, in order:

    • The verified path first. It resolves the newest imcp2-local-v* tag by paginating the releases API. (releases/latest is unusable here: production deploys publish release-* releases in this repo.) The steps are &&-chained with curl -f, so a failed attestation stops the install.
    • The installer one-liner, with its caveats.
    • The Claude Desktop bundle.
    • The source build.

    Every verification command the README documents, for the archives and the bundle alike, is pinned to the release's tag with --source-ref and to GitHub-hosted runners. Archive names repeat across releases, so an older release's attested file would otherwise pass for a newer one's.

  • crates/imcp2-local/src/setup.rs — setup --print points at the shipped bundle.

  • docs/scoping-local-deployment.md — describes the single-bundle design, replacing one artifact per platform, together with its privilege-split assembly and the tag-pinned verification.

Testing

  • Built a real bundle locally from the published 0.5.0 archives with llvm-lipo:
    • mcpb validate passes the schema.
    • The zip entries are exactly icon.png, manifest.json, server/imcp2-local (mode 0755, x86_64 arm64) and server/imcp2-local.exe (PE32+ x86-64).
    • The manifest lists 12 tools.
  • arm64 slice carries LC_CODE_SIGNATURE and is byte-identical to the attested release binary.
  • Slice check: the build passes with a faithful lipo. A lipo wrapper that alters the thinned x86_64 slice stops the build with no bundle written.
  • Input provenance gate, run with a real gh against the published 0.5.0 archives:
    • All five archives pass the tag-bound check.
    • An archive with one byte appended fails, since there is no attestation for its digest.
    • A genuine archive under the wrong tag fails on the ref.
  • Opt-out matrix:
    • The opt-out builds with zero gh calls.
    • It is refused under CI.
    • A missing gh refuses.
    • A normal build verifies all four inputs against the tag.
  • Credential scrub, tested with shims around python3 (which launches the binary), npm and node:
    • None of them saw GH_TOKEN, GITHUB_TOKEN or the two OIDC variables.
    • All four attestation checks ran before the scrub.
  • Locked CLI: a bundle built from the lockfile has extracted contents identical to the npx-built one's.
  • The README's verified-install block, extracted verbatim and run under set -euo pipefail in a sandboxed HOME, resolves imcp2-local-v0.5.0, verifies it and installs it. The installed binary reports 0.5.0.
  • dist generate --check clean; all three workflow files parse as YAML; the generated caller job carries the three permissions.
  • The notes job's script, extracted from the workflow and run against the real 0.5.0 body with a stubbed gh:
    • It prepends the note and leaves dist's body intact.
    • A re-run is a no-op.
    • The marker check still finds the marker on a 2 MB body. The earlier printf | grep -q missed it 20 of 20 times under pipefail.
  • Tag selector run against the live release list: picks imcp2-local-v0.5.0 past v0.5.0 and release-2026-08-18-0.5.0.
  • cargo fmt --all --check, cargo test --workspace --all-targets (297 passed); clippy's remaining map_or warnings are pre-existing in imcp2-core.
  • .github/scripts/scan-internal-identifiers.sh — clean.
  • Not testable here: installing in Claude Desktop itself (no macOS GUI), the jobs' first real run (including the artifact hand-off between the bundle's two jobs), and the bundle's own tag-bound verification command. All of these need the next imcp2-local-v* tag. The bundle workflow is the part most worth watching on that release.

Checklist

  • I have read the Contributing guidelines.
  • Docs (README / comments) updated for any user-visible change.
  • No secrets, credentials, or internal-only information are included.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK

…fied install

The notes led with `curl … | sh` and said nothing about what it does. It
installs into ~/.cargo/bin, adds an auto-updater, and appends a PATH line
to every shell profile it can find; its checksum is baked into the script
and skipped silently on stock macOS, which has no sha256sum, while the
PowerShell installer checks none at all. For a binary that acts as the
user's Internet Identity, that is worth saying before the command.

dist writes the release body itself and has no setting for extra prose, so
this rides its documented custom-job seam: post-announce-jobs invokes
.github/workflows/imcp2-local-install-note.yml after the release is
published, and that job prepends
.github/release-notes/imcp2-local-install-note.md to the notes. Keeping the
prose in a markdown file means it is reviewed as rendered markdown rather
than as a string inside YAML — the first attempt embedded it in a heredoc,
which broke the block scalar and would have made the workflow unparseable.
A marker line makes a re-run a no-op. The release workflow was regenerated
with dist 0.31.0 rather than hand-edited, so it stays reproducible from
config.

The crate README gains the verified path it should be recommending —
download, `gh attestation verify --signer-workflow`, extract — beside the
one-liner carrying the same caveats, with version-agnostic
releases/latest/download URLs. It also no longer claims that no release has
been cut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
@aterga
aterga requested a balanced review from Copilot September 24, 2026 08:36
@aterga
aterga marked this pull request as ready for review September 24, 2026 08:36
@aterga
aterga requested a review from a team September 24, 2026 08:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The documented asset URL and installation destination/PATH handling can cause installation failures.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 2 Medium severity · 2 Low severity

Open (4)
What changed in this PR

Documents installer behavior and adds an attestation-verified installation path for imcp2-local.

Changes:

  • Adds an idempotent post-release workflow for installer guidance.
  • Configures cargo-dist to run the custom job.
  • Expands README installation and verification instructions.
File Description
dist-workspace.toml Registers the post-announcement job.
crates/​imcp2-local/​README.md Documents verified, scripted, and source installation.
.github/​workflows/​imcp2-local-release.yml Adds the generated custom release job.
.github/​workflows/​imcp2-local-install-note.yml Prepends installer guidance to release notes.
.github/​release-notes/​imcp2-local-install-note.md Defines the installer disclosure text.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/imcp2-local/README.md Outdated
Comment thread crates/imcp2-local/README.md Outdated
Comment thread .github/release-notes/imcp2-local-install-note.md Outdated
Comment thread crates/imcp2-local/README.md Outdated
…steps

`releases/latest` is the repository's latest release, and deploy-release.yml
publishes a GitHub Release for every `release-*` production deploy
(deploy-release.yml:249), so the next deploy would have made those URLs 404.
Confirmed against the live release list, which already interleaves `v*` and
`release-*` entries with this crate's. The verified path now resolves the
newest `imcp2-local-v*` tag with `gh release list` — no new dependency,
since that path already needs `gh` for the attestation — and the one-liner
is pinned with a note to substitute the current tag.

The verified path also creates `~/.local/bin` before installing into it and
says it must be on PATH, rather than promising a PATH change it never made.

Both the README and the release note stated the PowerShell installer checks
no checksum and then spoke of "either hash" being carried by a script piped
to a shell, which is not the Windows story at all. The caveat is now
singular to the shell script, with the attestation named as what establishes
provenance on both platforms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Copilot AI review requested due to automatic review settings September 24, 2026 08:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The verified installation path can select no release and can continue after failed attestation verification.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (4)

Comment thread crates/imcp2-local/README.md Outdated
`gh release list` returns 30 releases by default, and this repository
publishes one per production deploy, so the newest `imcp2-local-v*` will
eventually page out and leave TAG empty. The lookup now paginates the
releases API and takes the first matching tag, which is stable however many
deploy releases accumulate.

The steps were also separate lines, so a failed attestation would print its
error and the install would proceed anyway — precisely the outcome the
verified path exists to prevent. They are now `&&`-chained, and the download
uses `curl -f` so an empty or wrong tag fails there rather than saving
GitHub's 404 body as the archive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Copilot AI review requested due to automatic review settings September 24, 2026 08:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The release-note workflow has a moderate idempotency defect that should be fixed before approval.

Review effort: Balanced
Findings: None

Resolved since last review (1)
Previously missed (3)

In code that hasn't changed since last review

Low severity Clarify warning when sha256sum is unavailable

.github/​release-notes/​imcp2-local-install-note.md:2

The shell installer emits a "skipping" message when sha256sum is unavailable, so calling the skipped check silent is inaccurate. State that it warns but continues successfully without verification.

Low severity Document visible warning and continued installation without verification

crates/​imcp2-local/​README.md:58

The shell installer does not skip this silently: its no-sha256sum path emits a "skipping" message and then returns success. Since this section is intended to explain the installer precisely, describe the visible warning and the fact that installation continues without verification.

Low severity Align PR description with pinned package-specific release URLs

crates/​imcp2-local/​README.md:66

The PR description says the README uses releases/latest/download URLs that do not rot, but this changed command is pinned to imcp2-local-v0.5.0 and tells readers to substitute it. Please update the description to match the implemented package-specific tag/pinned-release strategy; otherwise it describes behavior this change does not provide.

Installing leaves you with a binary on PATH and, from the release page
alone, no stated next step: dist's body is the install one-liners, the
download table and the attestation section, and nothing there says the
server still has to be registered with a client. The note now names
`setup`, `--print` and `--remove`, says to restart the client, and links
the README's per-client table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Copilot AI review requested due to automatic review settings September 24, 2026 10:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Replace broad secret inheritance with an explicit token and correct the noted documentation inaccuracies.

Review effort: Balanced
Findings: None

Every imcp2-local release now carries `imcp2-local.mcpb`: download it,
double-click it, and Claude Desktop installs and manages the server — no
PATH, no `setup`. `setup --print` had been promising this "once released".

One bundle serves every Claude Desktop platform. MCPB's `platform_overrides`
key on the OS alone, not the CPU, so a single bundle cannot pick between the
Apple Silicon and Intel builds; it carries a universal macOS binary instead,
the two release builds joined with `lipo`, beside the Windows binary. Each
slice is byte-identical to its attested archive, so the arm64 slice keeps
its ad-hoc signature, which Apple Silicon requires before it will execute
anything. Claude Desktop appends `.exe` itself for binary servers, so the
manifest names the binary once with no Windows override.

.github/scripts/build-mcpb.sh assembles it from the release's own published
archives, each checked against its .sha256 (with a `shasum` fallback, since
macOS has no `sha256sum`), and fills the manifest's version and tool list —
the tools read off the shipped binary's `tools/list`, using the server's own
display titles, so the install dialog cannot advertise a surface the server
lacks. The manifest's fixed fields live in crates/imcp2-local/mcpb/, with
the repository's ICP logo as the icon and the published privacy policy, which
Anthropic's directory requires of any listing.

It runs as a second dist `post-announce-jobs` entry, on macOS for native
`lipo` and to execute the binary it just built, and gives the bundle its own
provenance attestation: it is a new artifact with its own digest. That needs
`id-token`/`attestations` beyond the workflow's `contents: write`, granted
through `github-custom-job-permissions`; dist emits them on the caller job.
The release workflow was regenerated with dist 0.31.0.

The bundle is not code-signed yet. The README says so, and what that means:
an unverified-developer warning, possibly a Gatekeeper refusal on the
server's first macOS launch, and an outright block in organizations that
limit Claude Desktop to directory-listed extensions. It also documents
verifying the bundle, whose attestation names the bundle workflow as its
signer rather than the release workflow. The design doc now states the
single-bundle design in place of one artifact per platform.

Built locally against the published 0.5.0 archives: manifest schema passes
`mcpb validate`, 12 tools, both macOS slices, 0755 on the macOS binary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Copilot AI review requested due to automatic review settings September 24, 2026 10:40
@aterga aterga changed the title Explain the install scripts in the release notes, and document a verified install Ship a Claude Desktop bundle with every local-MCP release, and explain the install scripts Sep 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The critical provenance gap and unresolved release automation issues must be fixed before approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)

Comment thread .github/scripts/build-mcpb.sh
Comment thread .github/workflows/imcp2-local-install-note.yml Outdated
…PE bugs

The bundle job checked each input archive only against a .sha256 fetched
from the same mutable release, which catches corruption but not
replacement — and then attested the bundle, so a swapped archive would have
come out wearing a fresh, legitimate attestation. Every input now has to
pass `gh attestation verify` against the release workflow with
`--source-ref refs/tags/<tag>`, which also refuses a genuinely attested
archive from an older release, before it is used. Tested against the
published 0.5.0 archives: a genuine archive passes, one with a byte appended
fails (no attestation for its digest), and a genuine one under the wrong tag
fails on the ref. The check is mandatory in CI; a local build may opt out
with MCPB_ALLOW_UNATTESTED=1, which is refused when CI is set.

The notes job tested for its marker with `printf | grep -q` under pipefail.
Once grep matches and exits, printf takes SIGPIPE on a body larger than the
pipe buffer, the test reads as "absent", and a re-run prepends the note
again — reproduced deterministically, 20 of 20 runs, with a 2 MB body. It is
now a shell substring match. The same bug class was also in the bundle
script's `find | head` and the README's `gh api | grep -m1` lookup, both
now `awk`, which reads its whole input; the README command resolves the tag
correctly even under `set -eo pipefail`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Copilot AI review requested due to automatic review settings September 24, 2026 10:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Critical anti-replay gaps and moderate bundle-build validation issues remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 2 High severity · 1 Medium severity

Open (3)
Resolved since last review (2)

Comment thread crates/imcp2-local/README.md Outdated
Comment thread crates/imcp2-local/README.md Outdated
Comment thread .github/scripts/build-mcpb.sh Outdated
…onour the opt-out

The README's verified install checked that the archive came from the
release workflow, but not from the release it had just resolved. Archive
names repeat across releases, so an older release's genuinely attested
archive swapped into the newest release would have passed: the downgrade
the bundle job already refuses for its own inputs. The verified install,
the bundle's check and the "Verifying a download" command now all carry
`--source-ref refs/tags/<tag> --deny-self-hosted-runners`. The release note
no longer calls dist's repository-only command what establishes
provenance; it points at the README's tag-bound check instead, and the
design doc describes the pinned form.

Verified against the published 0.5.0 archives: all five pass the tag-bound
check, and a wrong tag fails on the ref. The README's verified-install
block, extracted verbatim and run under `set -euo pipefail`, resolves the
tag, verifies, installs, and the installed binary reports 0.5.0.

MCPB_ALLOW_UNATTESTED=1 was reachable only when `gh` was missing, so on a
machine with `gh` installed the documented opt-out did nothing. It is now
checked first, and in CI it fails the build outright instead of being
ignored. All four cases were run: the opt-out with a `gh` that fails every
call builds the bundle with zero calls, the opt-out under CI=1 and a
missing `gh` both refuse, and a normal build verifies all four inputs
against the tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Copilot AI review requested due to automatic review settings September 24, 2026 11:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The release pipeline and Claude Desktop installation cannot be validated end-to-end until a future tagged release.

Review effort: Balanced
Findings: None

Resolved since last review (3)

The release note and the README said the shell installer skips its
checksum "silently" where there is no sha256sum. It doesn't: the 0.5.0
installer prints "skipping sha256 checksum verification (it requires the
'sha256sum' command)" (its `say` is on unless the install is run quiet)
and then installs anyway. Both now say that.

The same sentence pinned the gap on "stock macOS". That holds for older
releases; newer macOS releases have been reported to ship a sha256sum, and
the wording no longer depends on which. It names older macOS releases as
one case rather than the case. The bundle script's comment on its shasum
fallback is reworded to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK

aterga commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator Author

This covers two points from Copilot's review summaries that never became inline threads.

The installer's checksum skip is not silent. This came from the 90d789f review, under "previously missed", for both the release note and the README. Copilot is right: when sha256sum is missing, the 0.5.0 installer prints skipping sha256 checksum verification (it requires the 'sha256sum' command) and installs anyway. Its say is on unless the install runs quiet. Fixed in e06c734 (pushing right after this comment): both texts now say it prints a one-line notice and installs anyway.

The same sentence blamed "stock macOS". That holds for older releases, but newer ones have been reported to ship a sha256sum, so the wording now names older macOS releases as one case rather than the only one.

secrets: inherit on the generated caller jobs. This came from the 026028a review summary. I'm leaving it as dist generates it, for three reasons:

  • dist 0.31.0 hard-codes secrets: inherit for custom jobs. It has github-custom-job-permissions but no secrets counterpart.
  • Dropping it by hand would need allow-dirty = ["ci"], which turns off dist's check that the release workflow still matches its config. That check is what keeps the pipeline reproducible from dist-workspace.toml.
  • It grants nothing an inline job wouldn't have. Both callees are local files at the same commit as the caller, and any job in a workflow can already name any repository secret. Neither callee names anything beyond secrets.GITHUB_TOKEN.

Generated by Claude Code

Copilot AI review requested due to automatic review settings September 24, 2026 11:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Critical supply-chain and credential-exposure issues must be resolved before approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 2 High severity

Open (2)

Comment thread .github/scripts/build-mcpb.sh
Comment thread .github/scripts/build-mcpb.sh Outdated
Comment thread .github/workflows/imcp2-local-install-note.yml Outdated
… the MCPB CLI

The bundle job held a write-scoped token and, through id-token: write, the
OIDC request credentials, and in that same environment it ran the
release's binary and `npx`, meaning the mcpb CLI and every npm dependency
npx resolved. A compromised dependency could have edited the release, or
minted a token and signed attestations as this workflow.

Unsetting the variables before those programs run narrows that but does not
close it: a child can still read its parent's original environment
(/proc/<ppid>/environ on Linux, `ps -E` on macOS). So the workflow is now
two jobs split by privilege. `build` gets `contents: read` and
`attestations: read`, no OIDC, and runs the third-party code; `publish`
gets the write and signing rights and runs only pinned actions and `gh`,
receiving the bundle as an artifact. The script also drops credentials from
its environment after its last fetch, which keeps a developer's own token
out of a local build's subprocesses.

`npx -y @anthropic-ai/mcpb@2.1.2` pinned only the top-level package; the
rest of the tree was resolved afresh on every release. The CLI now comes
from .github/scripts/mcpb-cli/package-lock.json (55 packages, every one
with an integrity hash and resolved from registry.npmjs.org, none with an
install script), installed with `npm ci --ignore-scripts` into the scratch
directory and invoked from there. MCPB_VERSION goes: the lockfile is the
version.

Tested with a full verified build against the 0.5.0 release, with shims
around python3, npm and node recording which credentials reached them:
none did, all four attestation checks ran before the scrub, and the
bundle's extracted contents are identical to the npx-built one's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Copilot AI review requested due to automatic review settings September 24, 2026 11:32
The notes job and the bundle's publish job ran on ubuntu-22.04, copied from
the dist-generated workflow's default; this repository's own workflows use
ubuntu-24.04, and so do these now. The bundle's build job moves from
macos-14 to macos-15. Nothing in either job depends on the image beyond
bash, jq, gh, python3, node/npm and lipo, which all of them carry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Release-job ordering and concurrency can publish missing or duplicate bundle guidance, and README installation issues remain.

Review effort: Balanced
Findings: None

Resolved since last review (2)
Files not reviewed (1)
  • .github/scripts/mcpb-cli/package-lock.json: Generated file

Copilot AI review requested due to automatic review settings September 24, 2026 11:39
MRmarioruci
MRmarioruci previously approved these changes Sep 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

The universal binary’s slices are not checked byte-for-byte against their attested inputs.

Review effort: Balanced
Findings: None

Files not reviewed (1)
  • .github/scripts/mcpb-cli/package-lock.json: Generated file
Previously missed (1)

In code that hasn't changed since last review

Medium severity Verify universal-binary slices match their attested source binaries

.github/​scripts/​build-mcpb.sh:131

The PR states that each universal-binary slice is checked byte-for-byte with cmp, but this code only verifies that the arm64 and x86_64 architecture labels exist. That leaves the signature-preservation invariant unenforced: a lipo result whose slice bytes differ from the attested inputs would still be packaged and published. Extract both slices and compare them to their source binaries before continuing.

The bundle's safety argument rests on lipo carrying each slice through
unchanged: that keeps the arm64 slice's ad-hoc signature valid, and it is
what the archives' attestations vouch for. The script only checked that
both architecture names were present, so a lipo that rewrote a slice would
still have been packaged and attested. It now extracts each slice with
`lipo -thin` and compares it byte for byte with the binary from the
attested archive, stopping the build on any difference (a missing slice
already fails `-thin`).

Tested: a full verified build against 0.5.0 passes, and a lipo wrapper
that alters the thinned x86_64 slice stops the build with no bundle
written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK

aterga commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator Author

This covers two points from Copilot's latest review summaries, which have no inline threads.

Universal-binary slices weren't checked against their inputs (1e7ac1d summary, "previously missed"). Correct: the script only confirmed that both architecture names were present, while the description says each slice is byte-identical to its attested binary. I had checked that by hand, but the build didn't enforce it. Fixed in e8aa97e (pushing right after this comment): after lipo -create, each slice is extracted with lipo -thin and compared with cmp against the binary from its attested archive, and any difference stops the build. A missing slice already fails -thin.

Tested against 0.5.0: a full verified build passes, and a lipo wrapper that alters the thinned x86_64 slice stops the build with no bundle written.

Job ordering and duplicate notes (29ff585 summary). There is no code change here, and there are two parts to it:

  • Ordering: dist wires every post-announce job to [plan, announce] and has no way to order custom jobs against each other, short of hand-editing the generated workflow, which dist's own check rejects. So the notes job and the bundle workflow run in parallel. For the few minutes the bundle takes to build, the note mentions an .mcpb that isn't attached yet. If the bundle job fails, the release run shows that, and re-running it attaches the bundle.
  • Duplicates: the notes job is idempotent through its marker, and the upload uses --clobber, so re-runs don't duplicate anything. Two runs can't overlap for the same tag in practice. A run can't be re-run while it's still in progress, and a second run would need the tag pushed again, which dist's host job refuses once the release exists.

Generated by Claude Code

@aterga
aterga merged commit 3a20558 into main Sep 24, 2026
17 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Bundle publication is currently blocked by missing artifact permissions, with additional reproducibility and release-recovery concerns.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Files not reviewed (1)
  • .github/scripts/mcpb-cli/package-lock.json: Generated file

GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Fetch the bundle
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
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.

4 participants