Skip to content
Beta Multi-model support (OpenAI, Gemini, and more) launching soon.

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.

These are non-negotiable; the validator hard-blocks any PR that breaks them.

  1. No bare Bash. Always Bash(specific-command *). The validator (R1) fails CI on bare Bash.
  2. No WebFetch(domain:*) wildcard. Enumerate specific domains. The validator (R7) fails CI on the literal wildcard.
  3. No credential paths in tool patterns. Patterns referencing .aws, .ssh, .azure, .gcloud, or Crypto are blocked — CRITICAL severity (R8). Credentials never belong inside an allowed-tools line; route them through env vars or a keychain instead.
  4. computer_use requires a written justification. Any artifact whose tools reference computer_use (or computerUse / computer-use) must declare a justifications.computer_use string — at least 20 characters explaining why this artifact needs computer use. Plugin-bundled artifacts declare it in rba-meta.json; standalone artifacts in their own frontmatter (R5).
RuleSeverityWhat it blocksHow to fix
R1HIGHBare Bash in allowed-tools / tools.Scope to specific commands — Bash(npm run *), Bash(git log *).
R2warnBare 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).
R3warnSKILL.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.
R5HIGHcomputer_use declared without a justification.Add justifications.computer_use (≥20 chars) in rba-meta.json or frontmatter.
R7HIGHWebFetch(domain:*) wildcard.Enumerate domains.
R8CRITICALTool 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.

# UNSAFE — bare Bash and WebFetch; credential-directory access.
---
name: my-skill
description: 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-skill
description: Audits a project.
allowed-tools: Read, Grep, Bash(git log *), Bash(git diff *), WebFetch(domain:docs.example.com)
---
// 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 bash
do-some-check || true
exit 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 bash
if ! do-some-check; then
echo "Refusing: <specific reason, including the file path involved>" >&2
exit 2
fi
exit 0

exit 2 is Anthropic’s documented blocking-error code for hooks. See the hook standards for the full contract.

# UNSAFE — Anthropic *allows* omitting tools (inherit-all); RBA *forbids* it.
---
name: my-reviewer
description: Reviews PRs.
---
# SAFE — tools declared explicitly, narrower than the main session.
---
name: my-reviewer
description: Reviews PRs.
tools:
- Read
- Grep
- Glob
- Bash(git diff *)
- Bash(git log *)
---

Each artifact type has its own standards page that bakes the rules above into per-artifact specifics:

ArtifactStandard
Plugin bundlePlugin authoring standards
SkillSkill authoring standards
AgentAgent authoring standards
Slash commandSlash command standards
MCP serverMCP server standards
HookHook authoring standards
ResourceResource authoring standards

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:

  1. Re-check the rule’s intent. R8 (credential paths) is non-negotiable; R7 (no domain wildcard) is non-negotiable; R5 (computer_use justification) is non-negotiable. The others sometimes have legitimate exceptions.
  2. Scope it as narrowly as possible. Bash(rm -rf ./.cache/*) beats Bash(rm *).
  3. 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.
  4. 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.