Security Checks in the scan-plugins GitHub Action
The scan-plugins GitHub Action enforces a layered security review of external plugin entries through static pin-checks, URL validation, host allow-listing, command injection prevention, and AI-driven policy scanning using the Claude CLI.
The scan-plugins action is part of the anthropics/claude-plugins-community repository and validates marketplace entries before publication. It ensures that Claude plugins meet deterministic security standards by combining static analysis with dynamic AI policy enforcement.
Static Pin-Check Analysis
The action performs deterministic, auth-free validation of MCP server launchers to prevent the execution of unpinned code from remote registries.
Package Manager Launcher Classification
The static analysis logic resides in [lib/pin-check.sh](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/scan-plugins/lib/pin-check.sh). It classifies each launcher into one of six categories:
pinned: Uses an immutable reference such as a specific version or SHAfloating: Uses a package manager runner (npx,bunx,uvx,pipx) with a mutable spec like@latest, version ranges, or bare package namesbare: Direct command without a package manager wrappervendored: Code included directly in the repositorylocal: References a local filesystem pathvcsref: Uses a version control reference
The check specifically flags floating specs that lack an explicit waiver. These launchers resolve code from a registry at session start, bypassing the entry's pinned SHA and introducing supply-chain risks.
To run this check manually for debugging:
source .github/actions/scan-plugins/lib/pin-check.sh
pin_check_tree "$PLUGIN_ROOT"
Input Validation and Allow-List Enforcement
Before cloning any repository, the action validates entry metadata in [scripts/scan.sh](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/scan-plugins/scripts/scan.sh).
URL and SHA Verification
Lines 71-90 verify that every target entry supplies:
- A non-empty
urlfield - A 40-character SHA hash
- URLs containing only safe characters
- URLs matching the
https://scheme exclusively
Host Allow-List
Lines 82-86 restrict network fetches to a predefined allow-list:
github.comgitlab.combitbucket.org
Any host outside this list triggers a warning and causes the scan to skip the entry entirely.
Unsafe Character and Path Detection
Lines 78-95 reject URLs or sub-directory paths containing characters that could lead to command injection or path traversal attacks. Lines 92-95 specifically ensure that source.path does not contain .. or other unsafe patterns before the action enters the cloned repository.
Repository Integrity Verification
Git Clone and Checkout Safety
Lines 97-104 of scripts/scan.sh perform a shallow clone of the declared URL, fetch the exact SHA, and check out the commit. If cloning, fetching, or checkout fails, the entry is skipped with a warning. This ensures the action only scans the exact code referenced by the pinned SHA, preventing time-of-check-to-time-of-use (TOCTOU) attacks.
AI-Driven Policy Scanning
Claude CLI Policy Execution
The action executes the Anthropic claude CLI in headless mode (lines 11-22 and 34-38 of scripts/scan.sh). It uses the minimal policy prompt defined in [policy/prompt.md](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/scan-plugins/policy/prompt.md).
The CLI receives read-only file-tool permissions limited to Read, Glob, and Grep operations scoped strictly to the cloned directory. It returns a JSON verdict structured according to [policy/schema.json](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/scan-plugins/policy/schema.json), containing:
passes: Boolean compliance statussummary: Human-readable description of findingsviolations: List of specific policy breaches- Flags indicating unauthorized network calls or software installation attempts
Reporting and Failure Configuration
Verdict Parsing and Annotations
Lines 126-165 and 170-187 parse the JSON output and merge it with static pin-check results. The action emits GitHub workflow commands (::warning or ::error) and populates step-summary tables. It also sets action outputs including scanned, failed, and result for downstream consumption.
Configurable Fail-On-Findings
By default, findings generate non-blocking warnings. Setting fail-on-findings: true (documented in [README.md](https://github.com/anthropics/claude-plugins-community/blob/main/.github/actions/scan-plugins/README.md) at line 58) upgrades policy failures to ::error commands and aborts the job. For isolation, the action can run in a separate job with minimal permissions as noted in lines 44-51 of the README.
Implementation Example
Configure the action in your workflow to scan plugin directories on pull requests:
# .github/workflows/scan-plugins.yml
name: Scan Plugins
on:
pull_request:
paths: ['.claude-plugin/**']
jobs:
scan:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: anthropics/claude-plugins-community/.github/actions/scan-plugins@<PINNED-SHA>
with:
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
fail-on-findings: "true"
Summary
- Static pin-checks in
lib/pin-check.shclassify MCP launchers and block floating package manager specs that bypass SHA pinning - Input validation enforces HTTPS URLs, 40-character SHAs, and safe character sets while restricting hosts to GitHub, GitLab, and Bitbucket
- Path safety prevents directory traversal by validating
source.pathbefore repository entry - Git integrity ensures exact commit checkout via shallow clones and precise SHA fetching
- AI policy scanning uses the Claude CLI with read-only permissions to detect policy violations, network calls, and installation attempts
- Configurable reporting allows choosing between warning annotations or hard failures via the
fail-on-findingsinput
Frequently Asked Questions
What triggers a floating launcher warning in the pin-check?
A floating launcher warning occurs when the action detects MCP servers using package manager runners like npx, bunx, uvx, or pipx with mutable version specs such as @latest, semantic version ranges, or bare package names. These configurations resolve code from registries at runtime, bypassing the entry's pinned SHA and requiring an explicit waiver to pass validation according to the logic in lib/pin-check.sh.
How does the action prevent command injection attacks?
The action prevents command injection through multiple layers: it validates that URLs contain only safe characters (lines 78-95 of scripts/scan.sh), ensures sub-directory paths don't contain .. sequences (lines 92-95), restricts network requests to a predefined host allow-list (lines 82-86), and executes the Claude CLI with read-only file permissions scoped strictly to the cloned directory.
Can I run the security checks without failing the CI job?
Yes. By default, the fail-on-findings input is set to false, causing violations to emit ::warning annotations rather than ::error commands. Set fail-on-findings: "true" only when you want policy violations to block the workflow and prevent plugin publication, as documented in the action's README.
Which code hosting platforms are supported by the host allow-list?
The action restricts fetches to github.com, gitlab.com, and bitbucket.org. Any plugin entry referencing URLs outside these hosts triggers a warning and is skipped during the scan, as implemented in lines 82-86 of scripts/scan.sh.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →