Browse guides
Back to Guides
IntermediateSecurity & HardeningOpenClaw

SKILL.md security best practices: what an OpenClaw skill can do and how to contain it

SKILL.md security best practices for OpenClaw: what a skill can reach, how to vet one, where its credentials belong, and why SOUL.md is not a control.

Mira Castellan20 minutes

A skill is a Markdown file plus gating metadata, so installing one does not hand it any new power by itself. OpenClaw's skills reference is explicit that "an eligible skill does not grant tool access": the frontmatter decides whether the agent sees the skill at all, and the agent's tool policy and the Gateway's sandbox settings decide what the commands it suggests can actually reach. So the work splits in two. Vet the text with openclaw skills verify @owner/<slug> --card and your own reading, then bound the tool side with sandboxing, tool policy and SecretRefs. Every command and field below was checked against v2026.9.5, the latest release listed in the OpenClaw docs as of September 19, 2026.

#What can a skill actually do on your machine?

By itself, it talks to the model. OpenClaw's skills reference describes skills as "markdown instruction files that teach the agent how and when to use tools", and says "Each skill lives in a directory containing a SKILL.md file with YAML frontmatter and a markdown body". There is no permission block in that file and no capability grant. What the body contains is instructions the model reads once it selects the skill.

The frontmatter's metadata.openclaw block is a gate, not a grant. OpenClaw "loads bundled skills plus any local overrides, and filters them at load time based on environment, config, and binary presence". Six keys do that filtering, and they are the whole of the documented gating surface:

Gating keyWhat the docs say it checks
requires.bins"Each binary must exist on PATH."
requires.anyBins"At least one binary must exist on PATH."
requires.env"Each env var must exist in the process or be provided via config."
requires.config"Each openclaw.json path must be truthy."
os"Hard platform filter." over darwin, linux and win32
alwaysInclude the skill whenever os is compatible, "bypassing requires.bins, requires.anyBins, requires.env, and requires.config"

Read that table the right way round. A failed check hides the skill from the agent; a passed check only makes it visible. Leaving the block out entirely is the permissive case, not the restrictive one: "A skill with no metadata.openclaw block is always eligible unless explicitly disabled." Nothing else in the block gates: primaryEnv, for instance, is the "Env var name associated with skills.entries.<name>.apiKey" and names a credential rather than filtering anything, which is the subject of the credentials section below.

The same separation holds one level up. Agent allowlists under agents.defaults.skills and agents.entries.*.skills control which skills an agent can see, and the skills reference states their limit in one sentence: "This is not a host shell authorization boundary." If the agent can call exec, the docs say to constrain that shell separately with sandboxing, OS-user isolation, exec deny and allowlists, and per-resource credentials.

So the honest threat model is a text one. A skill folder is code you are asking the model to follow, which is why the tool permissions page says to "Treat skill folders as trusted code and restrict who can modify them." It is also why the docs tell you not to use a skill as a place to put secrets: on a shared Gateway, "Skills attached to a shared session are inputs to that session, not secret storage", and the instruction is blunt, "Keep credentials out of skill content."

OpenClaw's skills reference says to treat third-party skills as untrusted code and to "Read them before enabling." That is the one step nothing in the toolchain does for you.

#Does SOUL.md belong in your security model?

Not as a control, and OpenClaw's own SOUL.md reference is the reason. It describes the file as where "your agent's voice lives" and says "OpenClaw injects it into normal sessions, so it carries real weight", with that weight scoped to tone, opinions, brevity, humor, boundaries and default level of bluntness. Then it draws the line that matters here: "Do not turn it into a life story, a changelog, a security policy dump, or a wall of vibes with no behavioral effect." The division of labour it recommends is one sentence long: "Keep AGENTS.md for operating rules; keep SOUL.md for voice, stance, and style."

Read that as a scoping statement, not a warning about persona files being dangerous. SOUL.md is a high-priority instruction layer that shapes how the agent sounds. The docs simply do not present it as the place where operating rules live, let alone where they are enforced.

What does enforce things is named on the security trust model page: gateway authentication, which "Authenticates callers to gateway APIs"; tool policy over session visibility and agent-to-agent messaging; sandboxing, where "Sandboxed sessions stay clamped to their own spawn tree by default"; and operator approval for node commands. The same page is careful about what is not a boundary, including that "sessionKey (session IDs, labels) is a routing selector, not an authorization token" and that "Exec approvals (allowlist + ask) are guardrails for operator intent, not hostile multi-tenant isolation." The prompt-injection page pushes in the same direction, telling you to "keep hard enforcement - tool policy, exec approvals, sandboxing, and channel allowlists" rather than relying on instructions alone.

Worth being precise about where the documentation stops. OpenClaw's prompt-injection and trust-model pages do not mention SOUL.md, AGENTS.md or SKILL.md by name at all, and the SOUL.md reference contains no sentence about security beyond telling you not to make the file a security policy dump. We found no documented statement about whether a persona file can restrict tool access, or about SOUL.md as a prompt-injection surface, so this guide does not make one.

Where SOUL.md and SKILL.md genuinely meet is the trust question. Both are instruction text injected into what the model reads, so both deserve the posture the tool permissions page sets for skill folders: "Treat skill folders as trusted code and restrict who can modify them." Anyone who can edit either file can change how your agent behaves. For the file itself, see the SOUL.md guide.

#How do you vet a skill before installing it?

Start with the file, because ClawHub's checks and your own reading catch different things.

Read the SKILL.md for three mismatches. First, description against body: the description is the text the model matches on, so a narrow description over a broad body is the shape worth noticing. Second, declared credentials against used credentials. ClawHub's skill format page explains why this matters on their side, saying "ClawHub's security analysis checks that what your skill declares matches what it actually does", and that if the body references a variable the frontmatter does not declare "under requires.env, primaryEnv, or envVars", then "the analysis will flag a metadata mismatch". The inverse is your signal: a body that reaches for a credential the frontmatter never names is a skill whose real requirements are not on the label. Third, the network destinations and binaries the body uses against what the skill claims to be for.

Then ask ClawHub what it knows. Two commands read the stored decision:

bash
openclaw skills verify @owner/<slug>
openclaw skills verify @owner/<slug> --card

The plain form prints ClawHub's clawhub.skill.verify.v1 trust envelope as JSON. The --card form "Prints the generated Skill Card Markdown instead of JSON. Exits non-zero when ClawHub returns ok: false" or a fail decision. The skills reference states the same exit behaviour for the underlying check: "The command exits non-zero when ClawHub marks verification as failed." Note what verify does not do: it "reads the stored scan and does not start another scan", so it reports a decision ClawHub already made rather than analysing the bundle for you.

Install from the registry with the owner-qualified form, and know which directory you are writing to:

bash
openclaw skills install @owner/<slug>
openclaw skills install @owner/<slug> --global

The first "installs into the active workspace skills/ directory by default"; --global installs into the shared ~/.openclaw/skills directory instead, "visible to all local agents unless agent allowlists narrow it". The skills CLI states the boundary for the whole family: "By default, install, update, and verify target the active workspace skills/ directory; with --global, they target the shared managed skills directory."

Other sources carry less assurance, and the docs label them. Community installs and updates check release trust before downloading, and "Malicious or blocked community releases are refused", but external skills-sh: listings are shown as "Not scanned by ClawHub", and that state survives updates and verification. Git and local directory installs are unmanaged. At the other end, "Official ClawHub skill publishers and bundled OpenClaw skill sources bypass this release-trust check."

If you want a gate of your own in front of every install, the documented hook is an operator policy command: "Configure security.installPolicy to run a trusted local policy command before skill installs continue." It applies to ClawHub, uploaded, Git, local, update and dependency-installer paths, and it "fails closed when the command cannot return a valid decision". A warn result stops before commit. "When security.installPolicy returns warn in an interactive terminal", OpenClaw prints the reason and findings and asks you to type the skill name; a non-interactive command stops instead, and --acknowledge-install-policy-warning is "the explicit noninteractive approval for every warning in that command invocation". A block result is terminal either way.

#How do you keep credentials out of a skill's reach?

Declare the variable name in the skill, keep the value somewhere else, and let OpenClaw join them at run time.

The name goes in the frontmatter. primaryEnv is the "Env var name associated with skills.entries.<name>.apiKey", and requires.env is what makes the skill ineligible when the variable is absent. Neither field holds a value.

The value goes in the secret store, entered so it never lands in shell history:

bash
openclaw secrets store set WEATHER_API_KEY --kind secret
openclaw secrets reload

The secrets CLI refuses the convenient spelling on purpose: "For secret values, --value is refused with exit code 2 because command-line arguments can leak through shell history and process listings." Pipe the value on stdin, pass --value-file <path>, or type it into the no-echo prompt. Stored secrets stay write-only, since "Secret values never appear in human, --json, or --plain output."

The binding goes in openclaw.json:

json5
{
skills: {
entries: {
"morning-brief": {
enabled: true,
apiKey: { source: "store", provider: "default", id: "WEATHER_API_KEY" },
},
},
},
}

apiKey is a "Convenience field for skills that declare metadata.openclaw.primaryEnv." and it "Supports a plaintext string or a SecretRef object." Use the SecretRef. Which source you point it at is open: Building custom skills binds the same field to the env source shown on the creating-skills page, while this guide routes the value through the shared store, and both are documented SecretRef sources. OpenClaw "supports additive SecretRefs so supported credentials do not need to live as plaintext in configuration", and its secrets page is direct about what the alternative costs you: "Plaintext credentials remain agent-readable when they sit in files the agent can inspect, including openclaw.json, .env, retired auth-profile JSON archives, or generated agents/*/agent/models.json files." A key pasted into a SKILL.md body is in the worst category of all, because the model reads that file by design.

Two boundaries decide who can see the resolved value.

Kind. In the shared store, a secret entry is write-only after saving. An env entry is not: those values are added as plaintext to Gateway-hosted commands run through the exec tool, and the docs say "The agent can print, transmit, or persist these values." Anything credential-shaped belongs to secret.

Process. Injection is scoped to the host agent run, and to one turn of it: the creating-skills page says "The key is injected into the host process for that agent turn only." The skills reference adds the sandbox exception, "Inside a sandbox, env and apiKey have no effect.", and the same page's rule for everything else is "Keep secrets out of prompts and logs."

One caveat the docs state plainly: "Store values are not encrypted at rest." They sit in the shared state SQLite database under the same 0600 file and 0700 directory permissions as other credentials, and the secrets docs point operators who need stronger isolation at an external exec provider instead.

API key setup covers the provider side of the same problem, including the other SecretRef sources OpenClaw accepts.

#How do you contain what a skill's commands can reach?

Gating cannot do this, and the docs do not pretend otherwise. Containment lives on the agent and Gateway side, in four documented controls.

ControlWhere it is configuredWhat it bounds
Tool sandboxagents.defaults.sandbox, agents.entries.*.sandboxWhich execution runs inside a sandbox backend rather than on the host
Workspace accessagents.defaults.sandbox.workspaceAccessWhether the sandbox sees the agent workspace at all, read-only, or read/write
Tool policytools.deny, per-agent agents.entries.*.toolsWhich tools the agent may call, evaluated before sandbox rules
Secret egress--allow-host bindings and secrets.egressProxy.allowedHostsWhich hosts a stored secret may be substituted into, and where non-sentinel traffic may go. Off by default; enable with secrets.egressProxy.enabled: true

Start with the sandbox, because it is off until you turn it on. The docs give the mechanism in one line, "OpenClaw can run tool execution inside a sandbox backend to reduce blast radius.", and the default in the next, "Sandboxing is off by default and controlled by agents.defaults.sandbox (global), agents.entries.*.sandbox (per-agent), or a required creator-role sandbox policy." The docs are equally clear about what you get: "This is not a perfect security boundary, but it materially limits filesystem and process access when the model does something dumb."

Workspace access is the setting that decides whether a sandboxed skill can read your files. On the default "none", "tools see a sandbox workspace under ~/.openclaw/sandboxes; agent workspace is off-limits". "ro" "mounts the agent workspace read-only at /agent" and disables the write, edit and apply_patch tools. "rw" "mounts the agent workspace read/write at /workspace".

json5
{
agents: {
defaults: {
sandbox: {
mode: "non-main",
scope: "session",
workspaceAccess: "none",
},
},
},
}

Tool policy sits in front of all of it: "Tool allow/deny policies still apply before sandbox rules. If a tool is denied globally or per-agent, sandboxing doesn't bring it back." For any agent handling untrusted content, the tool permissions page recommends denying gateway, cron, sessions_spawn and sessions_send by default. The one hole to watch is tools.elevated, which "is an explicit escape hatch that runs exec outside the sandbox"; keep its allowFrom list tight.

Egress is bounded per secret rather than per skill: "Each secret must also name the exact HTTPS hosts where substitution is allowed", and "A secret with no allowed hosts is never substituted." The mechanism is the "default-off secret egress proxy", so none of this applies until you turn it on. The docs' instruction is to "Enable it explicitly, then restart the Gateway", by setting secrets.egressProxy.enabled to true. A separate traffic allowlist covers requests that carry no secret at all, where "An empty array is lockdown mode: only per-secret bound hosts and bypassHosts remain reachable." Read its own caveat before relying on it: "the allowlist is defense in depth; destination-bound sentinels remain the primary defense because they survive proxy bypass".

These four are the containment surface the OpenClaw docs describe for skills. There is no per-skill permission list, no per-skill network policy and no per-skill filesystem scope in the documented format. A skill is bounded by the agent that runs it, which is why a risky skill and a permissive agent is the combination to avoid.

#How do you keep skills current and remove bad ones?

Three commands cover the routine, and removal goes through a different CLI than installation.

bash
openclaw skills check
openclaw skills update --all
openclaw skills verify @owner/<slug> --card

check is the readiness report. It "reports missing prerequisites independently of agent exclusion", so a skill whose declared binary or environment variable disappeared shows up under missing requirements instead of silently dropping out of the prompt. update tracks ClawHub installs only; Git and local sources have to be reinstalled to refresh. Re-running verify --card after an update is the step that catches a listing whose ClawHub decision changed since you installed it.

Removal uses the standalone ClawHub CLI, and the workdir has to match where the skill was installed:

bash
npm i -g clawhub
clawhub uninstall @owner/my-skill
clawhub --workdir ~/.openclaw uninstall @owner/my-skill

"The CLI asks for confirmation before deleting the skill directory and its .clawhub/lock.json entry." Use the agent workspace for a workspace install and the OpenClaw state directory for one installed with --global, or "$OPENCLAW_STATE_DIR" when that variable is set. The default skills watcher then "picks up the removal on the next agent turn"; if watching is disabled, start a new session.

#What does ClawHub check, and what does it not?

It checks the release, not your deployment, and it publishes what it found rather than certifying the skill.

At publish time, "ClawHub checks that your token can publish for that owner, validates the metadata, name, version, files, and source information, then stores the release and starts automated security checks." The gate is real but narrow: "If validation fails, nothing is published. New releases may also stay out of normal install and download surfaces until review finishes."

What those checks found is visible before you install. OpenClaw's skills reference puts it plainly: "ClawHub skill pages expose the latest security scan state before install, with detail pages for VirusTotal, ClawScan, and static analysis." The underlying reports exist as files too, though reaching them is a publisher's privilege rather than a reader's: clawhub scan says "Published scans require ownership or publisher management access.", and its "--output <file.zip> downloads the full report archive with manifest.json, clawscan.json, skillspector.json, static-analysis.json, virustotal.json, and README.md."

The Security Audits page names what sits behind that state. "The audit page combines" three things: SkillSpector, A.I.G and Risk analysis. A.I.G is an outside scanner, and the page is specific about both its provenance and its authority: "Skill audits include findings from Tencent Zhuque Lab's A.I.G scanner.", which "checks agent instructions for vulnerability patterns and supplies supporting evidence to ClawScan's artifact-wide review", and "It does not issue ClawHub's final verdict or independently block installation."

The verdict comes from ClawHub's own system. "Risk analysis is powered internally by ClawScan, ClawHub's own security audit system." Its frame of reference is a published one: "Risk analysis uses the OWASP Agentic Skills Top 10 as a lens for risks such as prompt injection, tool misuse, credential exposure, unsafe execution, memory or context poisoning, and excessive agency." And it is not looking for scary-sounding capabilities. "ClawScan does not treat a scary-looking capability as automatically malicious." It asks instead whether the capability is disclosed, purpose-aligned and supported by the stated use case.

Static analysis is the one detail page that does not stand on its own: "Static scan signals are internal context for this review; they are not a standalone public audit section or install-blocking verdict."

The result is published as two separate signals on the listing, which the Security Audits page defines like this.

Audit statusMeaning
Pass"No visible issue above low risk was found."
Review"Read the findings before installing. The release may still be legitimate."
Warn"Use extra caution. ClawHub found a high-impact concern or warning signal."
Malicious"Do not install."
Pending"Audits have not finished yet."
Error"The audit could not be completed."

Risk level answers a different question. It "describes blast radius: how much power the release appears to have if you use it as intended", across Low ("Little sensitive authority or user impact was found."), Medium ("The release has meaningful authority, such as account access or data changes.") and High ("The release has high-impact authority, severe findings, or malicious signals."). A publishing skill at Review and Medium is the page's own example of a result that is not an accusation.

What the audits look for is coherence rather than danger: "The main question is coherence: do the name, summary, metadata, requested authority, and actual content line up with what users would reasonably expect?" That is the same declaration check the skill format page describes, seen from the registry's side.

Three limits are worth holding onto.

A listing is not an audit of your deployment. The Security Audits page says "Audits are strong safety signals, but they are not a guarantee that a release is risk-free", and closes its checklist with "Install only content you understand and trust."

Publishing is open. "ClawHub is open by default: anyone can upload, but publishing requires a GitHub account old enough to pass the upload gate."

The license is fixed. "All skills published on ClawHub are licensed under MIT-0", attribution is not required, and per-skill license overrides are not supported.

#What should you do if a skill misbehaves?

Work in this order: remove, rotate, read, audit, report.

Remove it. clawhub uninstall @owner/my-skill from the workdir it was installed into, as above. Removing the folder stops the instructions reaching the next turn, but it does not undo anything the agent already did.

Rotate what it could see. OpenClaw's incident response page treats leaked secrets as compromised and lists "Rotate provider/API credentials" alongside Gateway and remote client secrets. Every variable the skill declared under requires.env, primaryEnv or envVars, and every env-kind store entry, was readable by the run. Replace them at the provider first, then update the store and run openclaw secrets reload. API key setup has the per-provider revocation steps.

Read the logs and the transcript. The same page names both locations. For logs, run openclaw logs, where "The default path is /tmp/openclaw/openclaw-YYYY-MM-DD.log" unless logging.file overrides it. For the conversation itself, "Review the relevant transcript(s): ~/.openclaw/agents/<agentId>/sessions/*.jsonl." The transcript is where you can see what the agent was actually told to do and which tools it called.

Re-audit the deployment. OpenClaw ships a command for exactly this. Its CLI page describes openclaw security as "Security tools: audit plus optional safe fixes.", and the security overview calls it the drift check, "one command tells you if you have drifted". The incident response page names the deeper form in its audit step: "Re-run openclaw security audit --deep and confirm critical findings are resolved."

bash
openclaw security audit
openclaw security audit --deep

The two differ in coverage rather than severity. Plain security audit "stays on the cold config/filesystem/read-only path", while "--deep adds best-effort live Gateway probes and plugin-owned security audit collectors". Neither is documented as inspecting a skill bundle. The audit checks the deployment the skill was running in, which is the part a bad skill may have talked you into widening, and its repair mode stays out of the skill tree entirely: --fix does not "remove or rewrite plugins/skills".

Report the listing. ClawHub's moderation page gives the route: "Use the Report skill button on a skill page, or the package reporting command/API for packages." Reports are for unsafe marketplace content such as malicious listings, misleading metadata, and undeclared credentials or permission requirements. They are not the channel for a bug in someone's code: "Do not use ClawHub reports for vulnerabilities in a third-party skill or plugin's own source code." Those go to the publisher or the source repository linked from the listing.

  • Building custom skills is the authoring side of the same schema, including the full metadata.openclaw field list and how primaryEnv binds to config.
  • API key setup covers where provider credentials live, the SecretRef sources OpenClaw accepts, and how to revoke a key that leaked.
  • Security hardening takes the containment section wider, to the Gateway bind, channel access and the rest of the deployment.

Every command, field and path on this page was checked against the OpenClaw and ClawHub documentation on September 19, 2026. The skills surface changes quickly, so re-read the sources below before copying a config block out of any tutorial, including this one.

Frequently asked questions

What can a skill actually access on my machine?
Nothing on its own. OpenClaw's skills reference calls skills markdown instruction files that teach the agent how and when to use tools, and says that an eligible skill does not grant tool access. A skill's frontmatter gates whether the agent sees it; the agent's tool policy and the Gateway's sandbox settings decide what the resulting commands can touch.
How do I vet a ClawHub skill before installing it?
Read the SKILL.md, then ask ClawHub. Compare the description against the body and check that every credential the body uses is declared under requires.env, primaryEnv or envVars. Then run openclaw skills verify @owner/slug --card, which prints the generated Skill Card and, in the docs' words, exits non-zero when ClawHub returns ok: false or decision: fail.
How do I keep API keys out of a skill?
Never put the value in SKILL.md or in a config literal. Declare the variable name with metadata.openclaw.primaryEnv, store the value with openclaw secrets store set, and bind it with skills.entries.<name>.apiKey as a SecretRef. OpenClaw's secrets docs warn that plaintext credentials remain agent-readable when they sit in files the agent can inspect, including openclaw.json and .env.
What does ClawHub check before a skill is listed?
ClawHub validates the metadata, name, version, files and source information, stores the release, then starts automated security checks. Its Security Audits page publishes an audit status of Pass, Review, Warn, Malicious, Pending or Error alongside a Low, Medium or High risk level, and states that audits are strong safety signals, but they are not a guarantee that a release is risk-free.
What should I do if an installed skill misbehaves?
Remove it with clawhub uninstall @owner/my-skill from the workdir it was installed into, then rotate every credential it could read. OpenClaw's incident response page points you at openclaw logs, the default log path /tmp/openclaw/openclaw-YYYY-MM-DD.log, and the session transcripts under ~/.openclaw/agents/<agentId>/sessions/. Report the listing with the Report skill button on its ClawHub page.
Does sandboxing stop a bad skill?
It limits the damage rather than preventing it. OpenClaw's sandboxing page says sandboxing is off by default, that it runs tool execution inside a sandbox backend to reduce blast radius, and that this is not a perfect security boundary, but it materially limits filesystem and process access when the model does something dumb. Tool allow and deny policies apply before sandbox rules.

Sources

  1. OpenClaw: Skills
  2. OpenClaw: Creating skills
  3. OpenClaw: SOUL.md
  4. OpenClaw: Security trust model
  5. OpenClaw: Prompt injection
  6. OpenClaw: Release notes
  7. OpenClaw: Skills CLI
  8. OpenClaw: Security
  9. OpenClaw: Security CLI
  10. OpenClaw: Tool and agent permissions
  11. OpenClaw: Operator incident response
  12. OpenClaw: Sandboxing
  13. OpenClaw: Secrets management
  14. OpenClaw: Shared secret store and egress proxy
  15. OpenClaw: Secrets CLI
  16. ClawHub
  17. ClawHub: CLI
  18. ClawHub: Publishing
  19. ClawHub: Skill format
  20. ClawHub: Security Audits
  21. ClawHub: Moderation and Account Safety

Skip the trial and error

ClawDocx members get 500+ tested prompts, ready-to-install SKILL.md files, SOUL.md and CLAUDE.md templates, and an MCP server so your agent can pull all of it directly while it works.