Back to skill

Security audit

Mainctrl

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed access-control skill, but it can weaken its own guardrail by automatically delegating blocked actions to unrestricted sub-agents and by using fragile exec and state handling.

Install only if you specifically want a workflow policy that blocks selected tools for main and automatically hands those blocked operations to other agents. Review the sub-agents' permissions first, avoid relying on the exec allow-except feature as a strong security boundary, and treat refresh-memory as a persistent change to future agent context.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (5)

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:171
Finding
Mandatory delegation redirects blocked operations to unrestricted sub-agents without renewed authorization<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 171-179 **Vulnerability Type**: Agent instruction and safety-policy redirection **Risk Level**: Critical ### Vulnerable Code ```markdown ## Agent behavior rule When the main agent receives the block message: > Delegate this work to a sub-agent instead. > Use sessions_spawn to dispatch to coder, tester, auditor, or publicist. it MUST: 1. Briefly inform the user that the operation has been blocked and is being delegated. 2. Immediately spawn a sub-agent (coder, tester, auditor, or publicist) to complete the blocked operation. Do NOT wait for the user to confirm — report and delegate in the same turn. ``` ### Technical Analysis The Skill changes the controlled Agent's execution policy by requiring it to transfer blocked operations to a sub-agent immediately and explicitly forbidding renewed user confirmation. This is particularly significant because the documented architecture intentionally leaves agents outside `controlledAgents` unaffected by the tool restrictions. Consequently, blocking an operation at the main Agent does not necessarily prevent that operation. Instead, the Skill directs the Agent to move it to another execution context that may retain `write`, `edit`, `exec`, `process`, and `apply_patch` privileges. The Skill frontmatter limits automatic invocation, but once a user invokes the Skill and activates its behavior, the mandatory delegation instruction still replaces a denial with automatic privileged execution through another Agent. ### Attack Path 1. A user invokes the `mainctrl` Skill and enables blocking. 2. The main Agent attempts an operation involving a blocked tool. 3. The plugin rejects the tool call and returns the fixed delegation message. 4. The Skill instructions require the main Agent to spawn `coder`, `tester`, `auditor`, or `publicist` immediately. 5. No renewed user confirmation is requested for the delegated operation. 6. If the selected sub-agent i ...[truncated 719 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace mandatory automatic delegation with a neutral denial. 2. Require explicit user approval before transferring a blocked operation to another Agent. 3. Display the exact requested operation, destination Agent, tools required, and expected side effects before requesting approval. 4. Apply equivalent restrictions to delegated agents so delegation cannot bypass the original policy. 5. Use capability-scoped delegation, granting only the minimum tool and filesystem access required for the approved task. 6. Bind approval to the exact command or file operation and reject material changes after approval. 7. Record the delegation chain and approval decision in an audit log. 8. Remove the instruction stating, “Do NOT wait for the user to confirm.” ]]>

T09 · Insecure Skill Coding Practices

Error
Location
plugin/index.js:72
Finding
Shell-naive exec allowlist permits compound-command bypasses<![CDATA[ ## Vulnerability Details **File Location**: `plugin/index.js`, lines 72-87 **Vulnerability Type**: Incomplete shell-command validation **Risk Level**: High ### Vulnerable Code ```js // exec allow-except: allow safe read-only commands with allow-except checks if (event.toolName === "exec" && state.blockedTools.includes("exec")) { const cmd = event.params?.command || ""; const firstWord = cmd.trim().split(/\s/)[0]; const allowExcept = state.execAllowExcept[firstWord]; if (allowExcept) { // Command is allowlisted — check allow-except patterns const blocked = allowExcept.find(p => cmd.includes(p)); if (blocked) { return { block: true, blockReason: `exec blocked: "${firstWord}" matched allow-except pattern "${blocked}". Delegate this work to a sub-agent instead.`, }; } return; // allowed } // Not in allow-except map → falls through to generic block below } ``` ### Technical Analysis The plugin determines whether an entire shell command is permitted by: 1. Extracting the first whitespace-delimited word. 2. Looking up that word in `execAllowExcept`. 3. Searching the complete command string for a small set of blocked substrings. 4. Permitting the entire command if none of those substrings appears. This is not shell-aware parsing. The default configuration for commands such as `ls`, `pwd`, and `cat` blocks redirection and pipes but does not block several common command-composition mechanisms, including: - Semicolon separators - Newline separators - `&&` - Backtick command substitution - `$()` command substitution for allowlisted commands other than `find` For example, the default `ls` configuration contains only `>`, `>>`, and `|`. A command such as the following starts with an allowlisted executable and does not match those patterns: ```sh ls ; destructive-command ``` The plugin therefore returns without blocking, leaving the complete command string to the `exec` tool. If that tool evaluate ...[truncated 1351 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not pass allowlisted commands as arbitrary shell strings. 2. Execute approved programs directly with validated argument arrays and shell evaluation disabled. 3. Maintain an explicit allowlist for executable paths rather than trusting the first textual token. 4. Define per-command argument schemas and reject unsupported flags, operands, environment assignments, expansions, and control operators. 5. If shell strings must be accepted, parse them with a grammar appropriate to the exact target shell and reject: - Multiple commands - Redirections - Pipelines - Command substitutions - Process substitutions - Variable expansions - Newlines and control operators 6. Prefer blocking `exec` unconditionally for controlled agents and provide dedicated read-only inspection tools. 7. Add regression tests covering semicolons, newlines, `&&`, `||`, backticks, `$()`, escaped metacharacters, quoted syntax, and alternate whitespace. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
plugin/index.js:21
Finding
Missing or malformed state disables all tool-call enforcement<![CDATA[ ## Vulnerability Details **File Location**: `plugin/index.js`, lines 21-51 **Vulnerability Type**: Fail-open access-control behavior **Risk Level**: High ### Vulnerable Code ```js function readState() { try { if (!existsSync(STATE_FILE)) { return { enabled: false, controlledAgents: DEFAULT_CONTROLLED_AGENTS, blockedTools: DEFAULT_BLOCKED_TOOLS, execAllowExcept: DEFAULT_EXEC_ALLOW_EXCEPT, }; } const raw = readFileSync(STATE_FILE, "utf-8"); const state = JSON.parse(raw); return { enabled: state.enabled !== false, controlledAgents: Array.isArray(state.controlledAgents) && state.controlledAgents.length > 0 ? state.controlledAgents : DEFAULT_CONTROLLED_AGENTS, blockedTools: Array.isArray(state.blockedTools) ? state.blockedTools : DEFAULT_BLOCKED_TOOLS, execAllowExcept: state.execAllowExcept && typeof state.execAllowExcept === "object" && !Array.isArray(state.execAllowExcept) ? state.execAllowExcept : DEFAULT_EXEC_ALLOW_EXCEPT, }; } catch { return { enabled: false, controlledAgents: DEFAULT_CONTROLLED_AGENTS, blockedTools: DEFAULT_BLOCKED_TOOLS, execAllowExcept: DEFAULT_EXEC_ALLOW_EXCEPT, }; } } ``` The disabled result is then accepted by the hook: ```js const state = readState(); // When the safety is off (enabled=false), allow everything if (!state.enabled) return; ``` ### Technical Analysis The security control explicitly defaults to `enabled: false` whenever: - The state file does not exist. - The file cannot be read. - JSON parsing fails. - Another exception occurs during state processing. This is a fail-open design. The state file is located inside the workspace Skill directory: ```js const STATE_FILE = resolve( homedir(), ".openclaw/workspace/skills/mainctrl/scripts/state.json" ); ``` An actor or Agent capable of modifying that workspace can disable enforcement ...[truncated 1248 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Fail closed when an installed and enabled security plugin cannot read or validate its policy. 2. Distinguish initial unconfigured installation from corruption of an existing configuration. 3. Store enforcement state outside Agent-writable Skill and workspace directories. 4. Enforce restrictive ownership and permissions on the policy file and its parent directory. 5. Continue using atomic replacement, but also validate the temporary file before activation and synchronize writes where appropriate. 6. Emit prominent structured security logs when policy loading fails. 7. Expose a degraded or error state through plugin status rather than silently reporting safety as off. 8. Consider signing or authenticating policy state if untrusted processes can write to the workspace. 9. Add tests for missing, truncated, malformed, unreadable, and wrong-type state files. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mainctrl.sh:109
Finding
Configuration commands accept syntactically valid values without enforcing the required schema<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mainctrl.sh`, lines 109-121 **Vulnerability Type**: Insufficient configuration validation **Risk Level**: Medium ### Vulnerable Code ```bash cmd_agents() { shift local agents_json="${1:-}" if [[ -z "$agents_json" ]]; then echo "Usage: mainctrl agents '<json-array>'" >&2 echo "Example: mainctrl agents '[\"main\",\"coder\"]'" >&2 exit 1 fi # Validate JSON echo "$agents_json" | node -e "const{stdin}=process;let d='';stdin.on('data',c=>d+=c);stdin.on('end',()=>{try{JSON.parse(d)}catch(e){process.exit(1)}})" 2>/dev/null || die "invalid JSON array" local state state="$(read_state)" state="$(echo "$state" | node -e "const{stdin,stdout}=process;let d='';stdin.on('data',c=>d+=c);stdin.on('end',()=>{try{const s=JSON.parse(d);s.controlledAgents=JSON.parse(process.argv[1]);stdout.write(JSON.stringify(s))}catch(e){process.exit(1)}})" "$agents_json")" 2>/dev/null || die "failed to update agents" write_state "$state" echo "mainctrl: controlled agents updated" } ``` Equivalent syntax-only validation is used for `blockedTools` at lines 159-169 and `execAllowExcept` at lines 175-186. ### Technical Analysis The comments and usage messages claim that these commands require specific data structures, but validation only checks whether the supplied text can be parsed as JSON. It does not verify that: - `controlledAgents` is an array of non-empty strings. - `blockedTools` is an array containing recognized tool names. - `execAllowExcept` is an object. - Each `execAllowExcept` property is an array of strings. - Values have safe length and size limits. Some malformed structures are replaced with defaults by `readState()`, which can create behavior different from what the administrator requested. Other structures can reach the execution path. For example, an object-valued allow-except entry is truthy but does not implement `.find()`, causing an exception during the global `before_tool ...[truncated 1269 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define and enforce a strict schema before writing any state. 2. Require `controlledAgents` to be an array of unique, non-empty strings. 3. Require `blockedTools` to be an array restricted to recognized tool identifiers. 4. Require `execAllowExcept` to be a plain object whose values are arrays of bounded strings. 5. Reject inherited object keys such as `__proto__`, `constructor`, and `prototype`. 6. Apply maximum lengths and entry-count limits to prevent resource exhaustion. 7. Validate the complete merged state, not only the newly supplied field. 8. Repeat defensive schema validation in the plugin before using state values. 9. Return a clear error without replacing the active known-good configuration when validation fails. 10. Add automated tests for nulls, numbers, strings, nested objects, mixed arrays, oversized input, and unknown tools. ]]>

T02 · Agent Memory Poisoning

Warning
Location
scripts/mainctrl.sh:190
Finding
Management command writes Skill-controlled content into persistent Agent memory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mainctrl.sh`, lines 190-213 **Vulnerability Type**: Persistent Agent-memory modification **Risk Level**: Medium ### Vulnerable Code ```bash cmd_refresh_memory() { local status_output memory_file tmpfile status_output="$(cmd_status)" memory_file="$HOME/.openclaw/workspace/MEMORY.md" tmpfile="$(mktemp)" # Build the new block content { echo "### mainctrl 运行状态" echo "" echo '```' echo "$status_output" echo '```' } > "$tmpfile" node -e "const fs=require('fs');const newBlock=fs.readFileSync(process.argv[1],'utf8');const memFile=process.argv[2];let content='';try{content=fs.readFileSync(memFile,'utf8')}catch(e){}const m=content.match(/^### mainctrl 运行状态\\n+\x60{3}\\n[\\s\\S]*?\\n\x60{3}/m);if(m){content=content.slice(0,m.index)+newBlock+content.slice(m.index+m[0].length)}else{if(content){if(!content.endsWith('\\n'))content+='\\n';content+='\\n'+newBlock}else{content=newBlock}}fs.writeFileSync(memFile,content)" "$tmpfile" "$memory_file" rm -f "$tmpfile" echo "mainctrl: memory refreshed → $memory_file" } ``` ### Technical Analysis The `refresh-memory` command writes generated Skill content directly into: ```text ~/.openclaw/workspace/MEMORY.md ``` That file is intended to persist information across Agent sessions. Writing operational Skill content there creates a cross-session influence channel. The current generated block contains status output rather than a concealed payload, but the implementation gives the Skill an explicit mechanism to modify persistent Agent context. Several status fields originate from mutable state, including Agent identifiers and allow-except entries. These values are converted to text and inserted into the memory document without a trust-boundary distinction indicating that the content is untrusted configuration data. If later Agent behavior treats memory text as instructions, crafted state values could influence future sessions. ...[truncated 1185 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store runtime status in a dedicated machine-readable file that is not included in Agent prompts or long-term memory. 2. Remove `refresh-memory` if persistence in Agent context is not essential. 3. Require explicit user confirmation immediately before modifying `MEMORY.md`. 4. Show the exact content and destination before writing. 5. Treat all state-derived fields as untrusted data and encode them into a non-instructional structured format. 6. Add a clear delimiter stating that the block is untrusted status data and must not be interpreted as instructions. 7. Restrict permitted characters and lengths for Agent names and configuration keys. 8. Use the same language as the rest of the Skill documentation to make generated sections easier to identify and audit. 9. Record memory modifications in an audit log and provide a command that safely removes the generated block. ]]>
Vulnerability Patterns
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (64)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill is presented as a runtime safety guard, but the documented behavior materially exceeds that scope: it can install/remove a plugin, modify persistent state, write to a global MEMORY.md file, and disable protections entirely. That mismatch can mislead users and downstream agents into granting trust or invoking it under false assumptions, increasing the chance of unintended environment changes or weakened safeguards.

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
1. Run `./scripts/mainctrl.sh status` to read the current runtime state.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Static analysis

No suspicious patterns detected.