From 1f77c8a9191de153af38cf27ef02f39af2c4d3c9 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 27 Feb 2026 08:55:42 -0800 Subject: [PATCH] docs(tools): document skill capability metadata and enforcement matrix --- docs/tools/skills.md | 208 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 204 insertions(+), 4 deletions(-) diff --git a/docs/tools/skills.md b/docs/tools/skills.md index de3fe807ed2..d74358a8913 100644 --- a/docs/tools/skills.md +++ b/docs/tools/skills.md @@ -68,12 +68,201 @@ that up as `/skills` on the next session. ## Security notes -- Treat third-party skills as **untrusted code**. Read them before enabling. +- Treat third-party skills as **untrusted** until you have reviewed them. Runtime enforcement reduces blast radius but does not eliminate risk — read a skill's SKILL.md and declared capabilities before enabling it. +- **Capabilities**: Community skills (from ClawHub) must declare `capabilities` in `metadata.openclaw` to describe what system access they need. Skills that don't declare capabilities are treated as read-only. Undeclared dangerous tool usage (e.g., `exec` without `shell` capability) is blocked at runtime for community skills. SKILL.md content is scanned for prompt injection before entering the system prompt. +- Local and workspace skills are exempt from capability enforcement. If someone can write to your skill folders, they can inject instructions into the system prompt — restrict who can modify them. - Prefer sandboxed runs for untrusted inputs and risky tools. See [Sandboxing](/gateway/sandboxing). - `skills.entries.*.env` and `skills.entries.*.apiKey` inject secrets into the **host** process for that agent turn (not the sandbox). Keep secrets out of prompts and logs. - For a broader threat model and checklists, see [Security](/gateway/security). +### Tool enforcement matrix + +When community skills are loaded, every tool falls into one of three tiers. Enforcement is applied by a hard code gate in the before-tool-call hook — prompt injection cannot bypass it. + +**Always denied** — blocked unconditionally when community skills are loaded, regardless of capability declarations: + +| Tool | Reason | +| --------- | --------------------------------------------------------------- | +| `gateway` | Control-plane reconfiguration (restart, shutdown, auth changes) | +| `nodes` | Cluster node management (add/remove devices, redirect traffic) | + +**Capability-gated** — blocked by default, allowed when the skill declares the matching capability in `metadata.openclaw.capabilities`: + +| Capability | Tools | What it unlocks | +| ------------ | ---------------------------------------------- | ----------------------------------------- | +| `shell` | `exec`, `process`, `lobster` | Run shell commands and manage processes | +| `filesystem` | `write`, `edit`, `apply_patch` | File mutations (`read` is always allowed) | +| `network` | `web_fetch`, `web_search` | Outbound HTTP requests | +| `browser` | `browser` | Browser automation | +| `sessions` | `sessions_spawn`, `sessions_send`, `subagents` | Cross-session orchestration | +| `messaging` | `message` | Send messages to configured channels | +| `scheduling` | `cron` | Schedule recurring jobs | + +**Always allowed** — safe read-only or output-only tools, no capability required: + +| Tool | Why safe | +| ----------------------------------------------------- | --------------------------------- | +| `read` | Read-only file access | +| `memory_search`, `memory_get` | Read-only memory access | +| `agents_list` | List agents (read-only) | +| `sessions_list`, `sessions_history`, `session_status` | Session introspection (read-only) | +| `canvas` | UI rendering (output-only) | +| `image` | Image generation (output-only) | +| `tts` | Text-to-speech (output-only) | + +A community skill with no capabilities declared gets access only to the always-allowed tier. + +### Example: correct capability declaration + +This skill runs shell commands and makes HTTP requests. It declares both capabilities, so OpenClaw allows the tool calls: + +```markdown +--- +name: git-autopush +description: Automate git commit, push, and PR workflows. +metadata: + { "openclaw": { "capabilities": ["shell", "network"], "requires": { "bins": ["git", "gh"] } } } +--- + +# git-autopush + +When the user asks to push their changes: + +1. Run `git add -A && git commit` via the exec tool. +2. Run `git push` via the exec tool. +3. If requested, create a PR using `gh pr create`. +``` + +`openclaw skills info git-autopush` shows: + +``` +git-autopush + Ready + + Automate git commit, push, and PR workflows. + + Source openclaw-managed + Path ~/.openclaw/skills/git-autopush/SKILL.md + + Capabilities + >_ shell Run shell commands + 🌐 network Make outbound HTTP requests + + Security + Scan + clean +``` + +### Example: missing capability declaration + +This skill runs shell commands but doesn't declare `shell`. OpenClaw blocks the `exec` calls at runtime: + +```markdown +--- +name: deploy-helper +description: Deploy to production. +metadata: { "openclaw": { "requires": { "bins": ["rsync"] } } } +--- + +# deploy-helper + +When the user asks to deploy, run `rsync -avz ./dist/ user@host:/var/www/` via the exec tool. +``` + +This skill has no `capabilities` declared, so it's treated as read-only. When the model tries to call `exec` on behalf of this skill's instructions, OpenClaw denies it. `openclaw skills info deploy-helper` shows: + +``` +deploy-helper + Ready + + Deploy to production. + + Source openclaw-managed + Path ~/.openclaw/skills/deploy-helper/SKILL.md + + Capabilities + (none — read-only skill) + + Security + Scan + clean +``` + +The fix is to add `"capabilities": ["shell"]` to the metadata. + +### Example: blocked skill (failed security scan) + +If a SKILL.md contains prompt injection patterns, the scan blocks it from loading entirely: + +``` +evil-injector x Blocked (security) + + Totally harmless skill. + + Source openclaw-managed + Path ~/.openclaw/skills/evil-injector/SKILL.md + + Capabilities + >_ shell Run shell commands + + Security + Scan [blocked] prompt injection detected +``` + +This skill never enters the system prompt. It shows as `x blocked` in `openclaw skills list`. + +### How the model sees skills + +The model does not see the full SKILL.md in the system prompt. It only sees a compact XML listing with three fields per skill: `name`, `description`, and `location` (the file path). The model then uses the `read` tool to load the full SKILL.md on demand when the task matches. + +This is what the model receives in the system prompt: + +``` +## Skills (mandatory) +Before replying: scan entries. +- If exactly one skill clearly applies: read its SKILL.md at with `read`, then follow it. +- If multiple could apply: choose the most specific one, then read/follow it. +- If none clearly apply: do not read any SKILL.md. +Constraints: never read more than one skill up front; only read after selecting. + +The following skills provide specialized instructions for specific tasks. +Use the read tool to load a skill's file when the task matches its description. +When a skill file references a relative path, resolve it against the skill +directory (parent of SKILL.md / dirname of the path) and use that absolute +path in tool commands. + + + + git-autopush + Automate git commit, push, and PR workflows. + /home/user/.openclaw/skills/git-autopush/SKILL.md + + + todoist-cli + Manage Todoist tasks, projects, and labels. + /home/user/.openclaw/skills/todoist-cli/SKILL.md + + +``` + +**What this means for skill authors:** + +- **`description` is your pitch** — it's the only thing the model reads to decide whether to load your skill. Make it specific and task-oriented. "Manage Todoist tasks, projects, and labels from the command line" is better than "Todoist integration." +- **`name` must be lowercase `[a-z0-9-]`**, max 64 characters, must match the parent directory name. +- **`description` max 1024 characters.** +- **Your SKILL.md body is loaded on demand** — it needs to be self-contained instructions the model can follow after reading. +- **Relative paths in SKILL.md** are resolved against the skill directory. Use relative paths to reference supporting files. + +The `Skill` type from `@mariozechner/pi-coding-agent`: + +```typescript +interface Skill { + name: string; // from frontmatter (or parent dir name) + description: string; // from frontmatter (required, max 1024 chars) + filePath: string; // absolute path to SKILL.md + baseDir: string; // parent directory of SKILL.md + source: string; // origin identifier + disableModelInvocation: boolean; // if true, excluded from prompt +} +``` + ## Format (AgentSkills + Pi-compatible) `SKILL.md` must include at least: @@ -116,6 +305,7 @@ metadata: { "requires": { "bins": ["uv"], "env": ["GEMINI_API_KEY"], "config": ["browser.enabled"] }, "primaryEnv": "GEMINI_API_KEY", + "capabilities": ["browser", "network"], }, } --- @@ -125,8 +315,19 @@ Fields under `metadata.openclaw`: - `always: true` — always include the skill (skip other gates). - `emoji` — optional emoji used by the macOS Skills UI. -- `homepage` — optional URL shown as “Website” in the macOS Skills UI. +- `homepage` — optional URL shown as "Website" in the macOS Skills UI. - `os` — optional list of platforms (`darwin`, `linux`, `win32`). If set, the skill is only eligible on those OSes. +- `capabilities` — list of system access the skill needs. Used for security enforcement and user-facing display. Allowed values: + - `shell` — run shell commands (maps to `exec`, `process`) + - `filesystem` — read/write/edit files (maps to `write`, `edit`, `apply_patch`; `read` is always allowed) + - `network` — outbound HTTP (maps to `web_search`, `web_fetch`) + - `browser` — browser automation (maps to `browser`) + - `sessions` — cross-session orchestration (maps to `sessions_spawn`, `sessions_send`, `subagents`) + - `messaging` — send messages to configured channels (maps to `message`) + - `scheduling` — schedule recurring jobs (maps to `cron`) + + No capabilities declared = read-only, model-only skill. Community skills with undeclared capabilities that attempt to use dangerous tools will be blocked at runtime. See [Tool enforcement matrix](#tool-enforcement-matrix) below and [Security](/gateway/security) for full details. + - `requires.bins` — list; each must exist on `PATH`. - `requires.anyBins` — list; at least one must exist on `PATH`. - `requires.env` — list; env var must exist **or** be provided in config. @@ -195,7 +396,7 @@ Bundled/managed skills can be toggled and supplied with env values: entries: { "nano-banana-pro": { enabled: true, - apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" }, // or plaintext string + apiKey: "GEMINI_KEY_HERE", env: { GEMINI_API_KEY: "GEMINI_KEY_HERE", }, @@ -221,7 +422,6 @@ Rules: - `enabled: false` disables the skill even if it’s bundled/installed. - `env`: injected **only if** the variable isn’t already set in the process. - `apiKey`: convenience for skills that declare `metadata.openclaw.primaryEnv`. - Supports plaintext string or SecretRef object (`{ source, provider, id }`). - `config`: optional bag for custom per-skill fields; custom keys must live here. - `allowBundled`: optional allowlist for **bundled** skills only. If set, only bundled skills in the list are eligible (managed/workspace skills unaffected).