curl writes "000" to -w '%{http_code}' on a connection failure AND exits
nonzero. The previous fallback put the echo inside the command
substitution — both wrote, the captured value was "000000", and the
case statement's 000) arm didn't match, so dead hosts fell through to
PASS. Move the fallback assignment outside the substitution so the
captured value is exactly "000" and connection failures fail.
Also skip entries with an empty url field — those are placeholders
awaiting user config, not dead endpoints, and would false-fail.
* Cache scan verdicts and drop policy-failing entries from bump PRs
Three changes that together let the nightly bump clear any backlog in a
single run without blocking on a single bad upstream or re-burning Claude
time on already-scanned SHAs:
- bump-plugin-shas.yml: raise max-bumps default 20 -> 130 (above the
external entry count, so a single run can clear a full backlog) and add
an explicit 60-min job timeout. The cap was the only thing bounding the
blast radius of a single policy failure; the changes below take over
that role so the cap can be lifted.
- scan-plugins.yml: add a verdict cache keyed on (plugin, sha, policy
hash). The bump action force-resets bump/plugin-shas every night, which
makes the same SHAs reappear in the diff on consecutive nights — without
the cache the scan would re-burn ~90s of Claude time per entry per
night. Cached verdicts (pass and fail) are served from disk; only
uncached SHAs are scanned. The job still fails on cached failures so
the required check stays honest.
- revert-failed-bumps.yml (new): after a Scan Plugins workflow_run on
bump/plugin-shas concludes with a failure, drop just the failing
entries' source.sha back to main's pin via a follow-up signed commit
and re-dispatch the scan. The re-dispatch finds only cached-pass
entries and goes green in seconds. Bounded at 3 passes/night, restricted
to SHA-only diffs, and aborts if the bump branch was tampered with.
* Harden bump cache and revert workflows after review
- revert-failed-bumps: replace the time-based revert budget (anchored on
the PR head, which a revert commit immediately replaces — never
accumulating past 1) with a commit count: every nightly bump force-
resets to one commit and every revert pass adds exactly one, so
commits > MAX+1 is the budget without date math, pagination, or
exposure to comment spoofing.
- revert-failed-bumps: filter the bump PR by head owner so a fork PR
with a branch named bump/plugin-shas can't be selected.
- revert-failed-bumps: continue-on-error on the artifact download so a
scan that died before uploading (infra error) doesn't fail the revert
job — the missing-file guard downstream handles it.
- scan-plugins: add a per-ref concurrency group so concurrent scans
don't lose one another's cache writes; key the cache on run_attempt
so a re-run can save its own verdicts.
- scan-plugins: store the full source object in the cache and require
source equality on lookup, so a repo/path change at the same SHA
misses the cache instead of getting a stale verdict.
- scan-plugins / revert-failed-bumps: strip markdown control chars,
wrap model-generated text in code spans (neutralizes auto-linked
URLs), and redact key-shaped tokens before they reach the step
summary, artifact, cache, or PR comment.
Upstream plugins move daily; a weekly sweep with a 20-bump cap can fall
behind. Each run force-resets the bump branch, so stale unmerged PRs are
replaced rather than piling up.
Walks marketplace.json for vendored plugins, extracts http/sse MCP
server URLs from .mcp.json / mcp.json / plugin.json, and probes each
with HEAD then a JSON-RPC POST fallback. Fails on 404/410 and
connection errors; passes on auth/method errors (expected without
credentials). Runs on PR, daily schedule, and manual dispatch.
External (SHA-pinned) plugins are out of scope — their .mcp.json
isn't checked out here.
Scan Plugins is meant to gate every change to marketplace.json, but two
gaps made that unenforceable:
1. The bump workflow opens PRs with GITHUB_TOKEN, which GitHub exempts
from on:pull_request triggers. Weekly bump PRs (e.g. #1809) get no
scan check at all.
2. The workflow had a paths filter, so a required-check ruleset for
`scan` would block every PR that doesn't touch marketplace.json
(no check run = pending forever).
Fixes:
scan-plugins.yml
- Drop the paths filter; replace with a step-level `git diff --quiet`
early-exit on the same paths. The check now reports on every PR,
which makes it safe to require.
- Fail closed when ANTHROPIC_API_KEY is unset and a scan is needed.
The shared action no-ops gracefully in that case (right default for
community repos), but a required check that silently does nothing is
a rubber stamp.
bump-plugin-shas.yml
- After the action opens the bump PR, `gh workflow run scan-plugins.yml
--ref bump/plugin-shas`. workflow_dispatch is exempt from the
GITHUB_TOKEN recursion guard, and the resulting check run lands on
the branch HEAD (= PR head), so it satisfies the required check.
- Add `actions: write` so the dispatch is allowed.
Follow-up: add a repo ruleset on main requiring the `scan` check
(integration: github-actions) once this merges.
The pinned version of anthropics/claude-plugins-community's
bump-plugin-shas action creates the bump commit with a local git commit,
which is unsigned and unmergeable under the required_signatures ruleset
on main. The new SHA creates the commit via the GraphQL
createCommitOnBranch mutation, which GitHub signs server-side, so weekly
bump PRs (e.g. #1809) become mergeable.
* Tighten policy scan: hook scope, telemetry, disclosure; make blocking
policy/prompt.md — adds Part 2 (hook scope and disclosure):
- Enumerate every registered hook and read its source.
- Flag has_broad_scope_hooks when UserPromptSubmit/PreToolUse/
PostToolUse runs without a project-relevance gate, or any hook
reads user data beyond the plugin's stated scope — regardless of
whether it makes network calls.
- Flag has_undisclosed_telemetry when any hook or shipped code calls
a non-MCP host without explicit disclosure + opt-out.
- Flag description_matches_behavior=false when the install
description would not lead a reasonable user to expect the
hooks/telemetry/data-access found.
- passes=false when any of the above trip. Violations must cite the
specific hook/file and what the user wasn't told.
The bar is now "handles user data responsibly," not merely "isn't
malicious." A non-malicious plugin that observes more than its stated
purpose justifies will fail.
policy/schema.json — adds required hooks[], has_broad_scope_hooks,
has_undisclosed_telemetry, description_matches_behavior.
scan-plugins.yml:
- fail-on-findings: true (blocking — loosen later if FP rate too high)
- workflow_dispatch with scan_all input for full re-review of all
external entries
- timeout-minutes: 360 (full scan of 117 entries at ~96s each ≈ 3h)
- trigger on .github/policy/** so prompt edits get scanned
* Bump vercel SHA to test the tightened scan against it
* Adopt validate-plugins action suite; pin all external SHAs
Replaces the hand-rolled marketplace validator and bot-based bump
workflow with the shared composite actions (pinned at f846a0b).
marketplace.json:
- 62 external entries that were missing a `sha` are now pinned to
their current upstream HEAD (resolved via git ls-remote).
Workflows:
- validate-plugins.yml: invariants I1-I11 + claude plugin validate +
diff-gated clone-at-SHA validation of changed external entries.
SHA-pin (I5) is a hard error. I8/I11 stay warnings until the 15
known data issues (vendored dirs without manifests; one dotted
name) are cleaned up.
- bump-plugin-shas.yml: bot-free weekly refresh. Validates each new
SHA with claude plugin validate before opening one PR; works with
the default GITHUB_TOKEN (contents:write + pull-requests:write).
- scan-plugins.yml: Claude policy scan of changed external entries.
Non-blocking; graceful no-op if ANTHROPIC_API_KEY isn't set.
Removed:
- validate-marketplace.yml + the two TS helper scripts (superseded
by step 11/20 of validate-plugins).
validate-frontmatter.yml is kept — it's complementary (targeted
checks on agent/skill/command files for in-repo plugins).
* Remove 5 external entries that fail validation at HEAD
Step 30 (clone at pinned SHA + claude plugin validate) fails for
these at their current HEAD:
aiven Unrecognized key "logo" in plugin.json
atlassian-forge-skills skill YAML frontmatter parse error
sagemaker-ai skill YAML frontmatter parse error
speakai no plugin manifest at repo root
stagehand no plugin manifest at repo root
These can be re-added once the upstream repos are fixed.
* Wire scan-plugins to the detailed policy prompt
Adds .github/policy/prompt.md and schema.json (the full security
review rubric — malicious code, privacy, deception, safety
circumvention, exfiltration; plus network-call and software-install
flags) and points scan-plugins at it via the policy-prompt input.
With ANTHROPIC_API_KEY now configured on the repo, scan-plugins runs
the actual policy review on changed external entries instead of
no-op'ing.
* Bump scan-plugins action pin to include L11/L12 fixes
The 'Validate frontmatter' step interpolated step output directly into a
double-quoted shell string, allowing a fork PR that adds a file named
e.g. agents/$(curl ...).md to execute arbitrary commands on the runner.
- Pass the file list via env: and reference as "$FILES" so the shell
never re-evaluates the contents
- Pass PR number via env: for consistency (no ${{ }} inside run:)
- Gate the job on same-repo PRs only, since fork PRs are auto-closed by
close-external-prs.yml anyway
Impact was bounded (fork PRs get a read-only token with no secrets), but
this closes the RCE-on-runner vector entirely.
* Add auto-SHA-bump workflow for marketplace plugins
Weekly CI action that discovers stale SHA pins in marketplace.json
and opens a batched PR with updated SHAs. Adapted from the
claude-plugins-community-internal bump-plugin-shas workflow for
the single-file marketplace.json format.
- discover_bumps.py: checks 56 SHA-pinned plugins against upstream
repos, oldest-stale-first rotation, capped at 20 bumps/run
- bump-plugin-shas.yml: weekly Monday schedule + manual dispatch
with dry_run and per-plugin targeting options
Entries without SHA pins (intentionally tracking HEAD) are never
touched. Existing validate-marketplace CI runs on the resulting PR.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix input interpolation and add BASE_BRANCH overlay
- Pass workflow_dispatch inputs through env vars instead of direct
${{ inputs.* }} interpolation in run blocks (avoids shell injection)
- Add marketplace.json overlay from main so the workflow can be tested
via dispatch from a feature branch against main's real plugin data
Both patterns match claude-plugins-community-internal's implementation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use GitHub App token for PR creation
The anthropics org disables "Allow GitHub Actions to create and approve
pull requests", so GITHUB_TOKEN cannot call gh pr create. Split the
workflow: GITHUB_TOKEN pushes the branch, then the same GitHub App
used by -internal's bump workflow (app-id 2812036) creates the PR.
Prerequisite: app must be installed on this repo and the PEM secret
(CLAUDE_DIRECTORY_BOT_PRIVATE_KEY) must exist in repo settings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use --force-with-lease for bump branch push
Prevents push failure if the branch exists from a previous same-day
run whose PR was merged but whose branch wasn't auto-deleted.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The workflow was passing deleted files to the validation script, which
failed when trying to read them. Add --diff-filter=AMRC to only process
Added, Modified, Renamed, and Copied files.
Adds a sort check as a second step in the existing validate-marketplace
workflow. The script supports --fix to sort in place.
Sorts the existing 86 entries — pure reorder, no content change.
Previously grouped loosely by kind (LSPs first, then internal, then
external); now strictly alphabetical so insertion point is unambiguous.
* Add CI workflow to validate marketplace.json on PRs
Add a GitHub Actions workflow that validates marketplace.json is
well-formed JSON with a plugins array whenever PRs modify it. Includes:
- validate-marketplace.ts: Bun script that parses and validates the JSON
- validate-marketplace.yml: GH Actions workflow triggered on PR changes
- test-marketplace-check.js: Unit tests for the validation logic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Strengthen marketplace validator and remove orphaned test file
- validate-marketplace.ts: check duplicate names and required fields
(name, description, source) per entry, not just valid JSON
- remove .github/workflows/test-marketplace-check.js: tested a
checkMarketplaceViolations function that doesn't exist in the PR,
and was in workflows/ instead of scripts/
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tobin South <tobin.south@gmail.com>
Adds a GitHub Actions workflow that validates frontmatter in agent,
skill, and command .md files changed by a PR. Checks:
- Agents: name and description are present and parseable
- Skills: description is present (required for Skill tool discovery)
- Commands: description is present and parseable
The workflow only runs when PRs touch files in agents/, skills/, or
commands/ directories, and only validates the changed files.
* Add write permissions for external PR workflow
* Use pulls.createReview instead of issues.createComment
* Revert to issues.createComment with proper permissions