Back to skill

Security audit

Parallel Thinker

Security checks for vulnerabilities and agentic risk

Overview

This skill is purpose-aligned, but it can invoke arbitrary local agents without the limits its documentation claims.

Review before installing in shared or sensitive environments. Only use it where callers are trusted and local agents do not carry privileged tools or private context, and avoid sending secrets or regulated data. A safer version should enforce an allowlist, cap agents to the documented defaults, add timeouts and output limits, and make the synthesizer language behavior explicit.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/run.js:30
Finding

Caller-Controlled Selection of Local Agents Bypasses Intended Least-Privilege Boundaries

Content
View full analysis

Vulnerability Details

File Location: scripts/run.js, lines 30-84
Vulnerability Type: Unrestricted selection and invocation of locally configured agents
Risk Level: High

Vulnerable Code

js
function callAgent(agentId, query) {
  return new Promise((resolve, reject) => {
    const proc = spawn('openclaw', ['agent', '--agent', agentId, '--message', query, '--json'], {
      stdio: ['ignore', 'pipe', 'pipe']
    });

    let stdout = '';
    let stderr = '';

    proc.stdout.on('data', d => stdout += d);
    proc.stderr.on('data', d => stderr += d);

    proc.on('close', (code) => {
      if (code === 0) {
        try {
          const result = JSON.parse(stdout);
          resolve({ agentId, response: result.text || result.response || stdout, error: null });
        } catch (e) {
          resolve({ agentId, response: null, error: `parse error: ${e.message}\nraw: ${stdout}` });
        }
      } else {
        resolve({ agentId, response: null, error: `exit ${code}: ${stderr.trim()}` });
      }
    });

    proc.on('error', (err) => {
      resolve({ agentId, response: null, error: err.message });
    });
  });
}

async function main() {
  try {
    const input = await readInput();
    const { query, agents = ['strategist', 'data-analyst', 'finance', 'expert-coder', 'researcher'], synthesizer_agent = 'synthesizer' } = input;

    // Parallel calls to all expert agents
    const calls = agents.map(agentId => callAgent(agentId, query));
    const results = await Promise.all(calls);

    // ...

    // Call synthesizer
    const synthResult = await callAgent(synthesizer_agent, synthPrompt);

Technical Analysis

The agents array and synthesizer_agent identifier are read directly from attacker-controlled JSON input. Each identifier is passed to the OpenClaw CLI through the --agent argument without an allowlist or an authorization check.

The ...[truncated 1838 chars]

Remediation
View remediation

Remediation Suggestions

  1. Define a server-side allowlist containing only the intended specialist agents and synthesizer.
  2. Reject unknown, malformed, empty, or duplicate agent identifiers before starting any child process.
  3. Do not allow callers to override synthesizer_agent unless an explicit authorization policy permits it.
  4. Apply per-caller authorization checks when different callers are permitted to access different agents.
  5. Run invoked agents with the minimum tools, credentials, filesystem access, and network permissions required for their assigned purpose.
  6. Record the authenticated caller, requested agent IDs, authorization decision, and invocation result in security audit logs.
  7. Keep use of spawn with a fixed executable and an argument array; do not replace it with shell-based command construction.

Example validation approach:

js
const ALLOWED_EXPERTS = new Set([
  'strategist',
  'data-analyst',
  'finance',
  'expert-coder',
  'researcher'
]);
const ALLOWED_SYNTHESIZERS = new Set(['synthesizer']);

function validateAgentList(agents) {
  if (!Array.isArray(agents)) {
    throw new Error('agents must be an array');
  }

  const uniqueAgents = [...new Set(agents)];
  if (uniqueAgents.some(id => typeof id !== 'string' || !ALLOWED_EXPERTS.has(id))) {
    throw new Error('An unauthorized agent was requested');
  }

  return uniqueAgents;
}

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run.js:30
Finding

Unbounded Child-Process Concurrency and Missing Execution Timeouts Enable Denial of Service

Content
View full analysis

Vulnerability Details

File Location: scripts/run.js, lines 30-69
Vulnerability Type: Unbounded resource consumption and indefinite child-process execution
Risk Level: Medium

Vulnerable Code

js
function callAgent(agentId, query) {
  return new Promise((resolve, reject) => {
    const proc = spawn('openclaw', ['agent', '--agent', agentId, '--message', query, '--json'], {
      stdio: ['ignore', 'pipe', 'pipe']
    });

    let stdout = '';
    let stderr = '';

    proc.stdout.on('data', d => stdout += d);
    proc.stderr.on('data', d => stderr += d);

    proc.on('close', (code) => {
      if (code === 0) {
        try {
          const result = JSON.parse(stdout);
          resolve({ agentId, response: result.text || result.response || stdout, error: null });
        } catch (e) {
          resolve({ agentId, response: null, error: `parse error: ${e.message}\nraw: ${stdout}` });
        }
      } else {
        resolve({ agentId, response: null, error: `exit ${code}: ${stderr.trim()}` });
      }
    });

    proc.on('error', (err) => {
      resolve({ agentId, response: null, error: err.message });
    });
  });
}

async function main() {
  try {
    const input = await readInput();
    const { query, agents = ['strategist', 'data-analyst', 'finance', 'expert-coder', 'researcher'], synthesizer_agent = 'synthesizer' } = input;

    // Parallel calls to all expert agents
    const calls = agents.map(agentId => callAgent(agentId, query));
    const results = await Promise.all(calls);

Technical Analysis

The program performs no validation or maximum-length check on the caller-supplied agents array. Array.map() immediately invokes callAgent() for every element, and each call starts a separate openclaw child process. Promise.all() does not impose a concurrency limit.

The child processes also have no execution timeout, abort signal, output- ...[truncated 1689 chars]

Remediation
View remediation

Remediation Suggestions

  1. Validate that agents is an array of strings and impose a strict maximum count, such as five.
  2. Deduplicate agent identifiers before execution.
  3. Use a bounded worker pool or concurrency limiter instead of starting all child processes through an immediate map().
  4. Enforce separate deadlines for expert and synthesizer calls, consistent with the documented 30-second and 60-second limits.
  5. On timeout, send a termination signal and escalate to forced termination if the process does not exit within a short grace period.
  6. Cap captured stdout and stderr sizes. Terminate a child process if it exceeds the configured output limit.
  7. Limit the query length and total stdin input size before parsing and processing it.
  8. Add caller-level rate limits, total execution budgets, and quotas for metered agent calls.
  9. Handle partial failures without waiting indefinitely and report timeout errors in a controlled form.
  10. Add tests covering oversized agent lists, hanging processes, excessive output, malformed input, and repeated invocation.

A hardened implementation should combine an input limit, a concurrency queue, and process termination logic rather than relying only on Promise.race(), because a timeout promise alone does not stop the underlying child process.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (2)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill explicitly forwards the user's query to multiple agents and then forwards both the query and intermediate agent responses to a synthesizer, but it does not warn the user that their input will be replicated across several downstream recipients. This creates a meaningful privacy and data-handling risk because sensitive prompts, credentials, proprietary information, or personal data may be disclosed more broadly than the user expects.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The constructed synthesis prompt uses Chinese labels and instructions, which forces a specific language/locale behavior regardless of the user's input language. This is a natural-language policy concern because the file does not offer any language choice or explain a justified region-specific restriction.

Content

No source excerpt is available for this finding.