| id | error-handling-security-policy |
|---|---|
| type | semantic |
| created | 2026-07-03 10:00:27 -0400 |
Please do not open a public GitHub issue for security vulnerabilities.
Report security issues using GitHub private vulnerability reporting for this repository (Security → Advisories → "Report a vulnerability"), or by emailing the maintainer directly.
Claude Code does not verify plugin signatures or attestations at install time yet (tracked upstream: anthropics/claude-code#30727). This marketplace cannot rely on the installer to refuse an unverified plugin. Enforcement lives at these points instead:
- CI on every push/PR (
.github/workflows/ci.yml) —claude plugin validate . --stricton bothplugin.jsonandmarketplace.json,actionlinton the workflow files themselves, andpin-check(everyuses:in.github/workflowsmust be a full 40-char commit SHA, never a mutable tag or branch). - Fail-closed release verification (
.github/workflows/release.yml) — the release tarball is attested with SLSA build provenance, a CycloneDX SBOM, six quality-gate verdicts (SAST, SCA, IaC/license, Semgrep, secrets, manifest review), and an OpenVEX disposition; themarketplace.jsoncatalog is cosign-signed. Every attestation and the catalog signature are re-verified in the same run before the tag-gated publish step. A tag publishes nothing that didn't just pass its own verification. - Documented consumer verification — the commands below let anyone re-check a release from a clean workstation before trusting it.
Every gate is risk-reducing, not risk-eliminating. A passing verification proves the tarball was built by this repository's own release workflow from a specific commit, untampered after signing. It does not certify the plugin's contents are free of bugs or the skills it teaches are correct for your use case.
Each release tarball carries GitHub's Sigstore-backed (keyless, OIDC)
attestations — no long-lived signing keys, and anyone can re-verify. Unlike a
multi-repo org, this plugin has no separate central signer: this repository's
own release.yml workflow produces every attestation and the catalog
signature. Pin --signer-workflow to that workflow explicitly rather than
relying on --repo alone — --repo only proves "signed by some workflow in
this repo," not specifically the release workflow.
Prerequisites: GitHub CLI gh ≥ 2.49.0,
authenticated (gh auth login); cosign
for the catalog signature.
TARBALL="cdc-error-plugin-0.4.0.tar.gz" # substitute the downloaded release asset
REPO="zircote/cdc-error-plugin"
SIGNER="zircote/cdc-error-plugin/.github/workflows/release.yml"gh attestation verify "$TARBALL" --repo "$REPO" --signer-workflow "$SIGNER" \
--predicate-type https://slsa.dev/provenance/v1
gh attestation verify "$TARBALL" --repo "$REPO" --signer-workflow "$SIGNER" \
--predicate-type https://cyclonedx.org/bomfor pt in sast sca iac-license semgrep secrets manifest; do
gh attestation verify "$TARBALL" --repo "$REPO" --signer-workflow "$SIGNER" \
--predicate-type "https://zircote.github.io/attestations/${pt}/v1"
donegh attestation verify "$TARBALL" --repo "$REPO" --signer-workflow "$SIGNER" \
--predicate-type https://openvex.dev/ns/v0.2.0A passing verification looks like:
Loaded digest sha256:... for file://cdc-error-plugin-0.4.0.tar.gz
Loaded 1 attestation from GitHub API
✓ Verification succeeded!
A failed verification exits non-zero. Treat any verification failure as a supply-chain integrity issue — do not install or use the artifact.
Also verify the published checksum, included as a release asset alongside the tarball:
sha256sum -c cdc-error-plugin-*-checksums.txt # substitute the downloaded checksums fileThe marketplace.json catalog is a blob (not an OCI image), signed with
cosign keyless (Sigstore Fulcio/Rekor) by this repository's own
release.yml workflow. Verify the downloaded catalog against its detached
bundle (also a release asset):
cosign verify-blob .claude-plugin/marketplace.json \
--bundle marketplace.json.cosign.bundle \
--certificate-identity-regexp '^https://github\.com/zircote/cdc-error-plugin/\.github/workflows/release\.yml@' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comSee docs/how-to/verify-release.md for a narrative walkthrough.
| Attestation | Predicate type | What it proves |
|---|---|---|
| SLSA build provenance | https://slsa.dev/provenance/v1 |
The tarball was built by this repo's release workflow from a specific commit, untampered since |
| CycloneDX SBOM | https://cyclonedx.org/bom |
The tarball is bound to a bill of materials |
| SAST | .../attestations/sast/v1 |
CodeQL ran over this repo's workflows and recorded a verdict |
| SCA | .../attestations/sca/v1 |
OSV-Scanner ran and recorded a verdict (no dependency manifests currently exist, so this asserts an empty, non-vulnerable dependency graph) |
| IaC / license | .../attestations/iac-license/v1 |
Trivy ran (misconfig + license) and recorded a verdict |
| Semgrep | .../attestations/semgrep/v1 |
Semgrep ran and recorded a verdict |
| Secrets | .../attestations/secrets/v1 |
Gitleaks + TruffleHog ran and recorded a verdict |
| Manifest review | .../attestations/manifest/v1 |
marketplace.json/plugin.json passed structural integrity review |
| OpenVEX | https://openvex.dev/ns/v0.2.0 |
Vulnerability disposition recorded for this release |
| Catalog signature | cosign keyless blob signature | The marketplace.json catalog blob is the one this repo published |
Signed ≠ passed. A passing verification proves the gate ran and recorded a verdict bound to the subject digest. Read the predicate body (
gh attestation verify ... --format json) for the verdict itself.
- Every GitHub Action referenced in
.github/workflows/is pinned to a full 40-character commit SHA, never a mutable tag or branch. Thepin-checkCI job enforces this on every push and PR. - The release pipeline is fail-closed: every attestation and the catalog signature must verify before a tag's publish step runs. There is no path from build to publish that bypasses verification.
- This is a single solo-maintained plugin repository, not a multi-repo org.
There is no fleet of GitHub Apps minting release tokens (the default
GITHUB_TOKEN, scoped per job, is used throughout) and no separate central signing seam — this repo's ownrelease.ymlis the signer for every attestation and the catalog signature. Verification still pins--signer-workflowto that workflow explicitly:--repoalone only proves "signed by some workflow in this repo," not specifically the release workflow. - ShellCheck is deliberately not wired as a gate: this repo has no
.shscripts to check (its only executable logic lives in.github/workflows/*.ymlrun:blocks). Wiring a gate with nothing to scan would produce a permanently-empty attestation, which asserts nothing and would be misleading.
For the reasoning behind this pipeline's scope and design (why these gates, why no central signer, what it does and doesn't close), see docs/explanation/attested-releases.md.