How the scan-plugins Action Performs Policy Scanning in the Claude Community
The scan-plugins GitHub Action performs automated policy scanning by orchestrating Claude to analyze changed external plugins against Anthropic's safety policies using a structured prompt and JSON schema validation.
The anthropics/claude-plugins-community repository maintains a marketplace of community plugins, and the scan-plugins action ensures every external submission complies with security standards before merging. This action automates what would otherwise be a manual code review process, checking for credential exfiltration, unauthorized network calls, and other safety violations by leveraging the Claude CLI.
Overview of the Policy Scanning Architecture
The policy scanning workflow operates as a Claude-driven policy reviewer that executes within GitHub Actions. According to the source code in .github/actions/scan-plugins/scripts/scan.sh, the action follows a strict pipeline: authenticating with the Anthropic API, resolving which plugins changed in the current pull request, checking out each plugin at its exact commit hash, and invoking Claude with a detailed safety prompt.
The action relies on two critical policy definitions stored in the repository:
policy/prompt.md: Human-readable instructions telling Claude what safety criteria to evaluatepolicy/schema.json: Structural validation rules ensuring Claude returns parseable JSON with required fields likepasses,summary, andviolations
Authentication and Environment Validation
Before scanning begins, the script validates the runtime environment at lines 7-19 of scan.sh. It first checks for either an ANTHROPIC_API_KEY or a federation rule ID, explicitly unsetting empty variables to prevent the Claude CLI from sending blank authentication headers.
The action also asserts that required inputs are defined:
MARKETPLACE_PATH: Path to the marketplace JSON fileBASE_REF: Git reference for comparison to detect changesALLOWED_HOSTS: Permitted domains for external plugin sourcesSCAN_TIMEOUT_SECS: Maximum duration for individual plugin analysis
# Environment validation from scan.sh
if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
unset ANTHROPIC_API_KEY # Prevent empty header errors
fi
# Input validation ensures critical variables are populated
: "${MARKETPLACE_PATH:?}" "${BASE_REF:?}" "${ALLOWED_HOSTS:?}" "${SCAN_TIMEOUT_SECS:?}"
Resolving Scan Targets
The action uses the shared library lib/targets.sh to determine which plugins require scanning. The resolve_scan_targets function extracts {name, source} objects from the marketplace JSON and writes them to targets.json.
Target resolution follows two modes controlled by the SCAN_ALL_EXTERNAL environment variable:
- Full scan: When
SCAN_ALL_EXTERNAL=true, every external plugin entry is targeted - Incremental scan: Otherwise, only plugins added or changed relative to the
BASE_REFcommit are included (lines 30-38 ofscan.sh)
# Target resolution logic
source "$(dirname "$0")/../lib/targets.sh"
resolve_scan_targets "$MARKETPLACE_PATH" "$BASE_REF" "$SCAN_ALL_EXTERNAL" > targets.json
Plugin Checkout and Pre-Validation
For each entry in targets.json, the script performs rigorous validation and checkout (lines 71-109 of scan.sh):
- URL Safety Check: Validates the plugin source URL against the
ALLOWED_HOSTSlist, skipping unsafe entries with warnings - SHA Verification: Ensures the target commit hash is present and valid
- Subdirectory Handling: Respects optional subdirectory specifications for monorepo-style plugins
- Precise Checkout: Clones with
--depth 1, fetches the exact SHA, and checks out the specific commit to prevent TOCTOU attacks
# Checkout process for each plugin
git clone --depth 1 "$URL" "$TMPDIR"
git -C "$TMPDIR" fetch --depth 1 origin "$SHA"
git -C "$TMPDIR" checkout "$SHA"
Claude Policy Analysis Execution
Inside the checked-out plugin directory, the action invokes the Claude CLI with restricted tool access and structured output requirements (lines 111-122 of scan.sh):
claude -p "$PROMPT" \
--bare \
--allowed-tools "Read,Glob,Grep" \
--output-format json \
--json-schema "$SCHEMA"
Key execution parameters:
- Prompt injection: Loads
policy/prompt.mdwhich instructs Claude to examine plugin manifests,skills/directories, hidden files, and configuration for safety violations - Tool restrictions: Limits Claude to read-only operations (
Read,Glob,Grep) preventing any execution of untrusted code - Schema enforcement: Uses
policy/schema.jsonto enforce output structure containing booleanpasses, stringsummary, and arrayviolations
The prompt specifically directs Claude to identify:
- External network calls outside allowed domains
- Credential exfiltration attempts
- Unauthorized file system access
- Dynamic code execution risks
Result Parsing and Violation Handling
After Claude returns the analysis, the script parses the JSON using jq and extracts specific fields (lines 26-50 of scan.sh):
passes: Boolean indicating overall safetysummary: Human-readable assessmentviolations: Array of specific policy breachesmay_make_external_network_calls: Boolean flag for network activitymay_download_additional_software: Boolean flag for dynamic installation
If parsing fails, the action logs a warning but continues processing other plugins. Valid results are merged with static pin-check data (unpinned_autoexec_* flags) and appended to the scanned results array.
The fail/warning logic (lines 55-63) determines CI behavior:
- When a plugin has
passes: false, its name is added to thefailedlist - If
FAIL_ON_FINDINGS=true, the action emits a GitHub error annotation and fails the workflow - If
FAIL_ON_FINDINGS=false(default), it emits a warning annotation allowing the workflow to continue
Workflow Integration and Outputs
The action exports three workflow outputs for downstream consumption:
scanned: JSON array of all analyzed plugins with their verdictsfailed: Array of plugin names that failed policy checksresult: Overall pass/fail status
It also generates a GitHub step summary in markdown format, displaying:
- Pass/fail icons for each plugin
- External network and software download flags
- Detailed violation descriptions
# Example workflow integration
- name: Scan plugins for policy compliance
uses: anthropics/claude-plugins-community/.github/actions/scan-plugins@v1
with:
marketplace_path: marketplace.json
base_ref: ${{ github.event.before }}
allowed_hosts: 'github.com gitlab.com'
scan_timeout_secs: 300
fail_on_findings: true
Summary
- The scan-plugins action automates security review by invoking Claude CLI against changed external plugins in the marketplace.
- It validates the runtime environment and ensures the
ANTHROPIC_API_KEYis properly configured before processing. - Target resolution uses
lib/targets.shto identify either all external plugins or only those modified in the current pull request. - Each plugin is checked out at its exact commit hash into a temporary directory for isolated analysis.
- Claude evaluates plugins using the policy prompt (
policy/prompt.md) and returns structured JSON validated against schema.json (policy/schema.json). - Results are parsed with
jqto extractpasses,violations, and safety flags, optionally failing the CI run based on theFAIL_ON_FINDINGSsetting.
Frequently Asked Questions
What triggers the scan-plugins action to run?
The action triggers on pull requests that modify the marketplace.json file or external plugin definitions. According to .github/workflows/validate-plugins.yml, it executes as part of the plugin validation pipeline, comparing changes against the BASE_REF to determine which specific plugins require scanning.
How does the action prevent Claude from executing malicious code during scanning?
The action restricts Claude's capabilities using the --allowed-tools flag limited to Read, Glob, and Grep only. This read-only configuration ensures Claude can analyze file contents but cannot execute any code within the checked-out plugin repositories.
What happens if Claude returns invalid JSON or fails to parse the plugin?
If jq cannot parse Claude's output, the action logs a warning message identifying the specific plugin and continues processing the remaining targets. This prevents one malformed response from blocking the entire validation pipeline, though the individual plugin receives no verdict.
Can the scan-plugins action be used with private plugin repositories?
Yes, provided the GitHub Actions runner has appropriate authentication credentials and the repository URLs are included in the ALLOWED_HOSTS list. The action performs standard git clone operations, so it respects the repository access permissions of the workflow environment.
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 →