Skip to content

fix(cli): install skill content from a pinned release, not a branch - #93

Draft
MajorLift wants to merge 2 commits into
mainfrom
jongsun/docs/skills-threat-model
Draft

fix(cli): install skill content from a pinned release, not a branch#93
MajorLift wants to merge 2 commits into
mainfrom
jongsun/docs/skills-threat-model

Conversation

@MajorLift

@MajorLift MajorLift commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Warning

Draft — not intended for merge until the security review completes.
This changes the delivery channel for every consumer. The threat-model work is a separate stream in progress with the security team; this PR is the proposed fix, parked here for that review to land against. Do not merge on CI green alone.

The gap

A lockfile entry pins the @metamask/skills CLI. It never pinned the skill content that reaches disk:

  • ensurePublicSkillsCache cloned --branch main and reset --hard origin/main, so a lockfile-pinned install still picked up anything merged since.
  • tools/bootstrap — the documented curl … | bash for cloud agents — defaulted SKILLS_REF to main, with no pin and no checksum.

Skill bodies are auto-loaded instructions and the bundle carries executable payloads, so "which revision landed" is a question the install should be able to answer.

The fix

CLI — content ref derives from the package's own version, so one lockfile entry pins both halves:

@metamask/skills@0.2.0  →  content from tag v0.2.0

Bootstrap — resolves the newest release tag via git ls-remote --tags instead of defaulting to main.

Both fail closed. A missing tag means this package version has no published content; widening to a branch there would reinstate the channel being removed. The CLI warns and declines; bootstrap exits non-zero naming the override.

SKILLS_REF still overrides — for development against main, and for holding a consumer on a specific release. It is now a deliberate opt-in to a mutable ref rather than the default, and taking it warns.

Scope

Engineer-owned checkouts reached through METAMASK_SKILLS_DIR are untouched — that is a working copy, not a delivery channel. tools/sync continues to git pull --ff-only there.

Test plan

  • yarn test — 14 pass / 0 fail
  • CLI resolves v0.2.0 from package version 0.2.0; tag exists
  • git ls-remote --tags resolves v0.2.0 as newest against the live remote
  • bash -n tools/bootstrap
  • Tests assert the branch defaults are gone in both paths and that the fail-closed message is present, so a revert to main fails CI
  • Security review — the gate on this PR

Open, for that review

  • Whether release cadence suits content delivery: a skill merged today ships on the next tagged release, not immediately. That is the intended trade, but it changes how quickly a fix reaches engineers.
  • Whether scripts/ and adapters/ should ship at all, and under what review — untouched here.
  • Whether branch protection and CODEOWNERS on main match the channel's reach. Not asserted either way in this PR: the protection endpoint returns 404 to a non-admin token, which does not distinguish "no rule" from "no permission to read it".

Skills are auto-loaded instructions distributed to engineers' agents across the
org, alongside executable payloads written into agent-trusted paths. The repo's
security posture does not describe that channel anywhere.

Describes three channels — instruction, execution, delivery — with the state of
each verified against the tree: `copy_bundle_dirs` ships `scripts` and
`adapters` (7 payloads today), `tools/sync` pulls from a tracked branch rather
than a reviewed ref, and `tools/bootstrap` is documented as `curl | bash` from
`main` with no checksum. The package is lockfile-pinned; the content it installs
is not, and that distinction is the crux.

States the existing controls with equal care, since the risk is easy to
overstate: auto-update is opt-in behind `SKILLS_AUTO_UPDATE`, this package
declares no `postinstall` of its own, `--ff-only` breaks rather than silently
applying a rewritten history, and CLI distribution is already review-gated.

Descriptive, not a proposal — the five open decisions are listed for the
security team to rule on rather than settled here. Also flags that SECURITY.md
describes the repo as prompt templates with no runtime code, which does not
account for the published CLI or the shipped executables.

Raised during ADR 0057 review (MetaMask/decisions#162).
A lockfile entry pins the `@metamask/skills` CLI. It never pinned the skill
revision that reaches disk: the cache cloned and reset to `main`, so an install
pinned in a lockfile still picked up whatever had merged since, and the
documented `curl … | bash` bootstrap did the same for cloud agents.

The CLI now derives the content ref from its own package version, so one
lockfile entry pins both halves. `tools/bootstrap` resolves the newest release
tag over `git ls-remote` instead of defaulting to `main`.

Both fail closed. A missing tag means this package version has no published
content, and widening to a branch at that point would reinstate the channel
being removed — so the CLI warns and declines, and bootstrap exits non-zero
naming the override.

`SKILLS_REF` still overrides, for development against `main` and for holding a
consumer on a specific release. It is now a deliberate opt-in to a mutable ref
rather than the default, and taking it warns.

Engineer-owned checkouts reached through `METAMASK_SKILLS_DIR` are untouched;
that is their working copy, not a delivery channel.
@MajorLift
MajorLift marked this pull request as draft July 30, 2026 19:21
@MajorLift MajorLift changed the title docs(security): document the skill-delivery threat model fix(cli): install skill content from a pinned release, not a branch Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant