modelcontextprotocol / modelcontextprotocol/registry
Docs: the recommended GitHub Actions workflow installs `mcp-publisher` unpinned and unverified, in a job that holds the publishing credential
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 7.3k
- Forks
- 994
- Avg merge
- 4d 12h
- Merged PRs (30d)
- 19
Description
Automate Publishing with GitHub Actions is
the workflow most servers copy, and all three authentication variants share this step:
- name: Install mcp-publisher
run: |
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
Three properties, and the third is the one that makes the first two matter.
1. releases/latest is mutable. Resolving the exact URL the doc uses:
HTTP 302 -> https://github.com/modelcontextprotocol/registry/releases/download/v1.8.0/mcp-publisher_linux_amd64.tar.gz
Two runs of an unchanged workflow, either side of a release here, execute different bytes.
2. This repository already signs every asset, and the doc does not use it. v1.8.0 ships, for
the exact tarball the doc downloads:
mcp-publisher_linux_amd64.tar.gz 7,337,300 B
mcp-publisher_linux_amd64.tar.gz.sbom.json 85,883 B
mcp-publisher_linux_amd64.tar.gz.sigstore.json 10,599 B
The verification material exists and is published for a reason. The documented workflow fetches the
first file and ignores the other two. Piping curl straight into tar xz also means there is no
point at which verification could happen — the bytes are extracted as they arrive.
3. The binary runs in a job that is holding a credential. Per variant, as documented:
| variant | what the job holds when ./mcp-publisher runs |
|---|---|
| OIDC (recommended) | permissions: id-token: write at job level, so any step in the job can mint an OIDC token |
| PAT | ./mcp-publisher login github --token ${{ secrets.MCP_GITHUB_TOKEN }} — passed on the command line |
| DNS | ./mcp-publisher login dns --domain … --private-key ${{ secrets.MCP_PRIVATE_KEY }} — an Ed25519 private key, on the command line |
So whoever controls the contents of releases/latest — maintainers here, or anyone who compromises
this project's release pipeline — controls a binary that every follower of this doc runs next to
their publishing credential.
I am not reporting a compromise, and I am not implying one. This is about the recommended
configuration: the download is unpinned and unverified, the signature that would fix it is already
published, and the doc places that download in the highest-privilege job in the workflow.
A reference fix already exists
ChromeDevTools/chrome-devtools-mcp hit exactly this and fixed it today in
PR #2474. Their result is a good
template for the doc:
- name: Install Cosign
uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
- name: Install MCP Publisher
run: |
export OS=$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
export VERSION=v1.8.0
curl -L -o mcp-publisher.tar.gz \
"https://github.com/modelcontextprotocol/registry/releases/download/${VERSION}/mcp-publisher_${OS}.tar.gz"
curl -L -o mcp-publisher.tar.gz.sigstore.json \
"https://github.com/modelcontextprotocol/registry/releases/download/${VERSION}/mcp-publisher_${OS}.tar.gz.sigstore.json"
cosign verify-blob mcp-publisher.tar.gz \
--bundle mcp-publisher.tar.gz.sigstore.json \
--certificate-identity-regexp="https://github.com/modelcontextprotocol/registry/\.github/workflows/.*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com"
tar xzf mcp-publisher.tar.gz mcp-publisher
rm mcp-publisher.tar.gz
Pinned version, download to disk rather than piping, verify, then extract.
Suggested changes to the page
- Pin the version and verify the signature, as above. This is the substantive one — it turns
"trust whateverlatestresolves to" into "trust a signature this project produced". - Move the publishing step into its own job, so the step that runs a downloaded binary is not
the step holdingid-token: writeor a private key.chrome-devtools-mcpsplit it into a
separate workflow entirely, which is stronger still. - Consider pinning the Actions by digest — the examples use
actions/checkout@v5and
actions/setup-node@v5, and GitHub's own hardening guidance recommends a full commit SHA. - If a
mcp-publisherGitHub Action were published, most of this would collapse into one pinned
uses:line and servers would get it by default.
Happy to open a PR against the docs with the OIDC variant rewritten if that is useful.
How I got here
I reported the copied version of this in chrome-devtools-mcp; a maintainer there noted the pattern
came from this page and suggested raising it upstream, which is what this is. Credit to
@OrKoN for pointing at the root rather than fixing only their own copy.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the Automate Publishing with GitHub Actions page and review its OIDC, PAT, and DNS workflow variants. Compare the download and verification sequence with the linked ChromeDevTools PR #2474. Done means the documented publisher version is pinned, its signature is verified before extraction, and downloaded code is isolated from publishing credentials; consider the documented action pinning suggestion too.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- github-actions, shell
- Domain
- ci-cd, documentation, security
- Issue type
- Documentation
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100