A PR touching only .github/policy/** matched none of the validate
pull_request paths, so the required 'validate' check never ran via
pull_request and sat Expected forever (a workflow_dispatch check run
isn't associated with the PR, so it can't satisfy the gate). Mirrors
the existing .github/workflows/** carve-out.
Refines the cross-service rule after the full -official re-validation showed the
prior wording let a plugin pass by *claiming* an ANTHROPIC_*-named token was
"its gateway key." Now: which service a credential belongs to is judged by its
NAME / storage location (ANTHROPIC_AUTH_TOKEN => Anthropic; ~/.railway/config.json
=> Railway; ~/.aws/credentials => AWS), NOT by how the plugin repurposes it. So
reading an ANTHROPIC_*-named token and routing it to a non-Anthropic endpoint is
cross-service (flag) even if the code treats it as a gateway key; same-service
use (Railway token -> Railway) still passes. Catches the wrong-credential-class
trust-boundary breach while preserving the same-service FP fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A full faithful scan of all 159 -official url-source plugins surfaced false
positives: the credential clause flagged plugins that use the user's OWN
service token to call that SAME service (e.g. a Railway plugin reading the
Railway CLI token to call Railway; a gcloud token used against Google) — normal
integration behavior. The "flag even if the destination is the vendor's own
service" wording inverted the right rule.
Corrected: flag only CROSS-service routing — a credential for service A sent to
a DIFFERENT service or third party (the vercel-style misuse: Anthropic's
ANTHROPIC_AUTH_TOKEN routed to a non-Anthropic endpoint). Same-service use
(token for X used to call X) is explicitly NOT a violation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The review rubric anchored "read every relevant file" to the loaded plugin
surface (skills/*/SKILL.md, hook-referenced source) and checked credential
reads (~/.ssh, ~/.aws/credentials) only within hooks. Code that reads the user's
live secrets from a non-loaded location — e.g. a dotdir like .claude/ that still
ships to the user's disk on a git-source install — could fall through both.
Two fixes:
- Scope: direct the reviewer to read the WHOLE shipped payload incl. dotdirs
like .claude/ (clones to disk, agent-reachable though not auto-loaded).
- Detector: add an explicit credential/secret-extraction check across ALL
shipped code (not just hooks), naming OS credential-store CLIs + token
harvest, with the set-your-own-key vs harvest trust-boundary distinction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ci): allow live external contributors to open scoped PRs
Add an opt-in allowlist so a vetted external developer who already has a
plugin live in this marketplace — but cannot use the submission form
(e.g. an enterprise partner without a Claude account) — can open a
reviewable PR instead of having it auto-closed.
- .github/external-contributors.json: username -> allowed_sources map
(doubles as the allowlist and the per-author source scope).
- close-external-prs.yml: skip the auto-close for allowlisted authors
(reads the list from the trusted base checkout). Grants ONLY the right
to open a PR; CI + maintainer approval are unchanged.
- external-pr-scope-guard.yml: required check for allowlisted external
authors. Fails unless the PR touches ONLY marketplace.json and the
delta is additions-only, with every added entry's source.url under
that author's allowed_sources. Anthropic members are unrestricted.
Reads head marketplace.json as data via the API (no untrusted checkout).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(ci): neutral wording in external-contributors allowlist note
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ci): key external-PR allowlist on source org, not individuals
Replace the per-username allowlist with a source-org allowlist so no
individual is named in the repo. A non-member PR stays open only if it
adds marketplace.json entries whose source.url is under an allowlisted
prefix and changes nothing else; merge still requires CI + maintainer
approval.
- external-pr-allowed-sources.json: flat allowed_sources prefixes (no usernames)
- scripts/external-pr-scope.js: shared additions-only / allowed-source logic
- close-external-prs.yml + external-pr-scope-guard.yml: both use the shared module
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ci): derive external-PR scope from live repos, no maintained list
Replace the curated source-org allowlist with auto-derivation from the
live marketplace. A non-member PR stays open only if it ADDS entries
whose source.url repo ALREADY backs a live plugin here, additions-only,
nothing else touched. No list to maintain, no individuals named.
Trust is anchored in the source repo + pinned SHA (org-controlled), not
the submitter's identity; merge still requires CI + maintainer approval.
- remove external-pr-allowed-sources.json
- scripts/external-pr-scope.js: derive allowed repos from base marketplace.json
- both workflows drop the allowlist-file arg
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ci): diff external-PR scope against the merge-base, not base tip
Comparing base-branch-tip -> head made a fork that is behind main report
all of main's later additions as phantom removals/modifications, which
would wrongly fail the scope guard for a legitimate additions-only PR.
Diff merge-base -> head (the PR's actual changes) instead; keep the
"already live" check against the current base branch.
Found by an end-to-end run of evaluate() against real PR data (#3298
stayed clean; #3044's phantom drift collapsed to its real one-line change).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the SAP UI5 Modernization plugin (SAP SE / github.com/UI5) to the
official marketplace. Sibling of the already-listed ui5 and
ui5-typescript-conversion plugins from the same repo.
Onboarded on behalf of the developer (Florian Vogt, SAP): the original
contribution PR #3298 was auto-closed by the external-contributor gate,
and SAP cannot currently use the submission form.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add sap-hana-cli plugin
150+ SAP HANA database tools for AI assistants. Pinned at 160ae47e.
Held: this PR is on hold pending an upstream plugin.json fix in
SAP-samples/hana-cli-claude-plugin#2 (the upstream repo uses the
npm-style {type, url} form for `repository`; Claude's plugin schema
requires the string form).
Once that lands, bump the SHA pin here to the merge commit and re-run CI.
Other SAP namespace work that was previously bundled in this PR has been
split into:
- #1777 add sap-fiori-mcp-server
- #1778 add sap-cds-mcp + author block on cds-mcp
- #1779 align SAP author blocks on sap-mdk-server, ui5, ui5-typescript-conversion
* chore(sap-hana-cli): bump source SHA to abadd0a (upstream fixed repository field to string)
Upstream SAP-samples/hana-cli-claude-plugin@abadd0a changes plugin.json
`repository` from an object to a string, resolving the validate failure
(repository: expected string, received object) that blocked #1616.