Secure-by-default checklist
This page is the consolidated rule sheet. The per-artifact standards pages bake these rules into their specific artifact; this page is for the times you want them all in one place — for review, for a security audit, or for setting up a new plugin that touches more than one artifact type.
The validator (scripts/validate-bundles.mjs) enforces the deterministic rules. The rest are review-time conventions reviewers will push back on.
The four hard rules
Section titled “The four hard rules”These are non-negotiable; the validator hard-blocks any PR that breaks them.
- No bare
Bash. AlwaysBash(specific-command *). The validator (R1) fails CI on bareBash. - No
WebFetch(domain:*)wildcard. Enumerate specific domains. The validator (R7) fails CI on the literal wildcard. - No credential paths in tool patterns. Patterns referencing
.aws,.ssh,.azure,.gcloud, orCryptoare blocked —CRITICALseverity (R8). Credentials never belong inside anallowed-toolsline; route them through env vars or a keychain instead. computer_userequires a written justification. Any artifact whose tools referencecomputer_use(orcomputerUse/computer-use) must declare ajustifications.computer_usestring — at least 20 characters explaining why this artifact needs computer use. Plugin-bundled artifacts declare it inrba-meta.json; standalone artifacts in their own frontmatter (R5).
Validator rules at a glance
Section titled “Validator rules at a glance”| Rule | Severity | What it blocks | How to fix |
|---|---|---|---|
| R1 | HIGH | Bare Bash in allowed-tools / tools. | Scope to specific commands — Bash(npm run *), Bash(git log *). |
| R2 | warn | Bare WebFetch. | Scope to specific domains — WebFetch(domain:docs.example.com). Stays a warning to allow genuinely open-ended skills (the research plugin is the canonical exception). |
| R3 | warn | SKILL.md without allowed-tools frontmatter. | Declare an explicit least-privilege list. Kept as a warning so historical skills don’t break the build; new authoring must not trip it. |
| R5 | HIGH | computer_use declared without a justification. | Add justifications.computer_use (≥20 chars) in rba-meta.json or frontmatter. |
| R7 | HIGH | WebFetch(domain:*) wildcard. | Enumerate domains. |
| R8 | CRITICAL | Tool pattern references a credential directory (.aws, .ssh, .azure, .gcloud, Crypto). | Remove the reference; route any genuine secret need via env vars. |
The validator also enforces structural rules — SKILL.md ≤500 lines, slug = name = folder, plugin bundle completeness, no primitive redeclaration in plugin-bundled artifacts — that are documented on the standards pages.
Worked examples
Section titled “Worked examples”allowed-tools — unsafe vs. safe
Section titled “allowed-tools — unsafe vs. safe”# UNSAFE — bare Bash and WebFetch; credential-directory access.---name: my-skilldescription: Audits a project.allowed-tools: Read, Grep, Bash, WebFetch, Bash(ls ~/.ssh/*)---# SAFE — every tool is scoped to the minimum the skill actually needs.---name: my-skilldescription: Audits a project.allowed-tools: Read, Grep, Bash(git log *), Bash(git diff *), WebFetch(domain:docs.example.com)---MCP server — secrets and scope
Section titled “MCP server — secrets and scope”// UNSAFE — credentials embedded; no namespace scope; defaults to writes.{ "mcpServers": { "my-crm": { "command": "node", "args": ["server.js", "--api-key", "sk-live-abc123", "--mode", "rw"] } }}// SAFE — credentials via env, scoped namespace, read-only default.// A confirmation env var (e.g. MY_CRM_ALLOW_WRITES=1) is documented in the// server's README for the rare cases a tool actually does need to write.{ "mcpServers": { "my-crm": { "command": "node", "args": ["server.js", "--namespace", "client-acme", "--mode", "ro"], "env": { "MY_CRM_API_KEY": "${MY_CRM_API_KEY}" } } }}Hook — failing loud vs. swallowing errors
Section titled “Hook — failing loud vs. swallowing errors”# UNSAFE — swallows failure, returns success even when something went wrong.#!/usr/bin/env bashdo-some-check || trueexit 0# SAFE — fails loud with an actionable message and the right exit code so# Claude Code surfaces the failure to the user instead of silently continuing.#!/usr/bin/env bashif ! do-some-check; then echo "Refusing: <specific reason, including the file path involved>" >&2 exit 2fiexit 0exit 2 is Anthropic’s documented blocking-error code for hooks. See the hook standards for the full contract.
Agent — tool allowlist
Section titled “Agent — tool allowlist”# UNSAFE — Anthropic *allows* omitting tools (inherit-all); RBA *forbids* it.---name: my-reviewerdescription: Reviews PRs.---# SAFE — tools declared explicitly, narrower than the main session.---name: my-reviewerdescription: Reviews PRs.tools: - Read - Grep - Glob - Bash(git diff *) - Bash(git log *)---Per-artifact pointers
Section titled “Per-artifact pointers”Each artifact type has its own standards page that bakes the rules above into per-artifact specifics:
| Artifact | Standard |
|---|---|
| Plugin bundle | Plugin authoring standards |
| Skill | Skill authoring standards |
| Agent | Agent authoring standards |
| Slash command | Slash command standards |
| MCP server | MCP server standards |
| Hook | Hook authoring standards |
| Resource | Resource authoring standards |
When you’re stuck
Section titled “When you’re stuck”If a legitimate need pushes against one of the rules above — for example, a skill that needs Bash(rm *) for a real cleanup workflow — the answer is usually:
- Re-check the rule’s intent. R8 (credential paths) is non-negotiable; R7 (no domain wildcard) is non-negotiable; R5 (
computer_usejustification) is non-negotiable. The others sometimes have legitimate exceptions. - Scope it as narrowly as possible.
Bash(rm -rf ./.cache/*)beatsBash(rm *). - Document the exception in the artifact. A short comment in the README or frontmatter explaining why the broader pattern is needed makes the review faster and gives future readers context.
- Open a discussion on the PR. Reviewers can approve a wider pattern when the justification is sound; they cannot approve something the validator blocks without a code change to the validator itself.
For the why behind these rules, see the Threat model.