MCP server supply chain attestation security
MCP server supply chain attestation — SLSA provenance, Sigstore, in-toto, and SBOM
A supply chain attack on an MCP server doesn't require compromising the server's application code — it can compromise the build pipeline, the base container image, an upstream npm package, or the CI runner that produces the artifact. Supply chain attestation answers the question "how do I know this artifact is what the author says it is?" with cryptographic proof: a provenance record that traces the artifact from source commit to deployed container, signed by a key that the build system controls, and verifiable by anyone with the public key. Four tools cover the full chain: SLSA for provenance framework, Sigstore Cosign for container image signing, in-toto for build step metadata, and CycloneDX SBOM for dependency inventory.
Pattern 1: SLSA provenance via GitHub Actions
SLSA (Supply-chain Levels for Software Artifacts) is a framework for evaluating the trustworthiness of a build pipeline. SLSA Level 3 — the level required by Anthropic's official MCP skills directory for listed servers — requires that provenance is generated by a hosted build platform (not the developer's machine), the build definition is version-controlled, and the provenance is signed by a key the build platform controls. GitHub Actions achieves all three with a single workflow addition.
# .github/workflows/release.yml
name: Build and attest
on:
push:
tags: ['v*']
permissions:
contents: read
id-token: write # required for OIDC token used by slsa-github-generator
jobs:
build:
runs-on: ubuntu-latest
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '22' }
- run: npm ci && npm run build
- name: Create release archive
run: tar czf mcp-server.tar.gz dist/ package.json package-lock.json
- name: Compute SHA-256 hashes
id: hash
run: |
echo "hashes=$(sha256sum mcp-server.tar.gz | base64 -w0)" >> $GITHUB_OUTPUT
provenance:
needs: build
permissions:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with:
base64-subjects: ${{ needs.build.outputs.hashes }}
upload-assets: true # attaches .intoto.jsonl provenance to the release
Pattern 2: Container image signing with Sigstore Cosign
Cosign signs container images using keyless signing backed by Sigstore's Fulcio CA and the Rekor transparency log. The signature is stored in the same OCI registry alongside the image. Consumers can verify the signature and the signing identity (a GitHub Actions workflow, identified by its OIDC token) without pre-distributing any public key.
# In your CI workflow, after docker push:
- name: Install Cosign
uses: sigstore/cosign-installer@v3
- name: Sign container image (keyless)
run: |
cosign sign --yes \
--rekor-url https://rekor.sigstore.dev \
ghcr.io/myorg/mcp-server@${{ steps.build.outputs.digest }}
# Cosign uses the GitHub Actions OIDC token as the signing identity
# The signature is stored at ghcr.io/myorg/mcp-server:sha256-.sig
# In your deployment pipeline, verify before pulling:
- name: Verify container image signature
run: |
cosign verify \
--certificate-identity-regexp '^https://github.com/myorg/mcp-server/.github/workflows/release.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/myorg/mcp-server@${{ env.IMAGE_DIGEST }}
# Fails if the image was not signed by the expected workflow — prevents
# pulling a tampered image even if the tag was moved
Pattern 3: CycloneDX SBOM generation
A Software Bill of Materials (SBOM) lists every dependency in your MCP server — direct and transitive — with their exact versions, license identifiers, and known vulnerability associations. Team buyers use SBOMs to run their own CVE scans, check license compliance, and audit what third-party code they're installing in their infrastructure when they adopt your MCP server.
# Generate a CycloneDX SBOM during CI build
- name: Generate SBOM
run: |
npx --yes @cyclonedx/cdxgen \
--output-format json \
--output-file sbom.cdx.json \
--project-type node \
.
# Validate the SBOM has expected structure
- name: Validate SBOM
run: |
npx --yes @cyclonedx/cyclonedx-npm validate --input-file sbom.cdx.json
# Attach to release artifacts
- name: Upload SBOM
uses: actions/upload-release-asset@v1
with:
asset_path: sbom.cdx.json
asset_name: sbom.cdx.json
asset_content_type: application/json
Pattern 4: Dependency pinning and lockfile enforcement
Unpinned npm dependencies (^1.2.3, ~1.2.3, or *) allow the package manager to install newer patch or minor versions on each fresh install. In a dependency confusion or typosquatting attack, a malicious package with a higher version number can be installed transparently. Pinning to exact versions and using npm ci in CI (which enforces the lockfile exactly) prevents this class of attack.
// WRONG: version ranges in package.json
{
"dependencies": {
"express": "^4.18.2", // any 4.x.x is acceptable
"jsonwebtoken": "^9.0.0", // attacker publishes 9.99.0 with malicious code
"zod": "~3.22.0" // only patch versions, still risky
}
}
// RIGHT: exact version pins + integrity hashes enforced via npm ci
// 1. Pin all deps to exact versions:
{
"dependencies": {
"express": "4.18.2",
"jsonwebtoken": "9.0.0",
"zod": "3.22.4"
}
}
// 2. Commit package-lock.json to version control
// 3. Use npm ci (not npm install) in all CI and Docker build steps:
// npm ci --ignore-scripts (--ignore-scripts prevents install-time code execution)
// 4. Verify integrity:
// npm audit --audit-level=high
SkillAudit's Team plan includes SBOM generation and export as a built-in feature of every audit report, and checks for SLSA provenance files, Cosign signatures, and dependency pinning as part of the supply chain axis of the security grade. For a full supply chain audit of your MCP server, run a SkillAudit scan. Related: supply chain security, dependency pinning, CI pipeline security.