Back to skill

Security audit

CatchClaw

Security checks for vulnerabilities and agentic risk

Overview

The skill largely matches its marketplace-management purpose, but it can install remote agent instructions and skills into live OpenClaw workspaces and has validation weaknesses users should review carefully.

Install only if you trust the CatchClaw marketplace source and understand that downloaded agentars can change future agent behavior. Prefer installing into a new named agent rather than overwriting the main workspace, avoid --api-key unless necessary, review any exported ZIPs and installed AGENTS.md changes, and be cautious with custom AGENTAR_API_BASE_URL values or untrusted PATH configurations.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (5)

T01 · Skill Instruction Hijacking

Error
Location
agentar_cli.mjs:1327
Finding
Remote Team Metadata Can Inject Persistent Agent Instructions<![CDATA[ ## Vulnerability Details **File Location**: `agentar_cli.mjs:1327-1369`, `agentar_cli.mjs:1458-1498`, and `agentar_cli.mjs:1661-1688` **Vulnerability Type**: Persistent instruction injection through remotely controlled team metadata **Risk Level**: High ### Vulnerable Code ```js function buildTeamBlock(teamName, teamYaml, members) { const collab = teamYaml.collaboration_type || "LEAD_FOLLOWER"; const lead = teamYaml.lead || ""; let block = `<!-- TEAM:${teamName}:BEGIN -->\n`; block += `## Team: ${teamName}\n`; block += `Collaboration: ${collab}\n`; block += `Lead: ${lead}\n\n`; block += `### Teammates\n`; for (const m of members) { const localPath = m.local_path || ""; block += `- **${m.id}** (${m.role}): ${localPath}\n`; } block += `\nUse agentToAgent tool to communicate with teammates.\n`; block += `<!-- TEAM:${teamName}:END -->`; return block; } function updateAgentsMd(agentsMdPath, teamName, teamBlock) { let content = ""; if (fs.existsSync(agentsMdPath)) { content = fs.readFileSync(agentsMdPath, "utf-8"); } const beginMarker = `<!-- TEAM:${teamName}:BEGIN -->`; const endMarker = `<!-- TEAM:${teamName}:END -->`; const beginIdx = content.indexOf(beginMarker); const endIdx = content.indexOf(endMarker); if (beginIdx >= 0 && endIdx >= 0) { content = content.slice(0, beginIdx) + teamBlock + content.slice(endIdx + endMarker.length); } else { if (content.length > 0 && !content.endsWith("\n")) content += "\n"; if (content.length > 0) content += "\n"; content += teamBlock + "\n"; } mkdirp(path.dirname(agentsMdPath)); fs.writeFileSync(agentsMdPath, content); } ``` The resulting block is persisted to every installed team member: ```js const teamBlock = buildTeamBlock(teamSlug, resolvedYaml, resolvedMembers); for (const m of resolvedMembers) { if (!m.local_path || !fs.existsSync(m.local_path)) continue; const agentsMdPath = path.join(m.local_path, "AGENTS.md" ...[truncated 2397 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define a strict schema for every team manifest field: - Require slugs and member IDs to match a narrow pattern such as `^[A-Za-z0-9][A-Za-z0-9_-]*$`. - Restrict roles and collaboration modes to explicit enumerations. - Set conservative maximum lengths. 2. Reject carriage returns, line feeds, null bytes, other control characters, HTML comments, and team marker strings in all remotely supplied values. 3. Do not interpolate remote descriptive text into an Agent instruction file. 4. Store team coordination metadata in a structured, non-instruction configuration file and generate only fixed, locally controlled instructions. 5. If `AGENTS.md` must be modified, render only validated identifiers and escape Markdown metacharacters. 6. Present the exact proposed `AGENTS.md` changes to the user before writing them. 7. Verify marketplace manifests cryptographically before trusting any metadata. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
agentar_cli.mjs:804
Finding
Unsigned Remote Agent Packages Are Installed into Live Workspaces<![CDATA[ ## Vulnerability Details **File Location**: `agentar_cli.mjs:80-121` and `agentar_cli.mjs:804-890` **Vulnerability Type**: Unauthenticated remote payload retrieval and installation **Risk Level**: High ### Vulnerable Code ```js function httpDownload(url, dest) { return new Promise((resolve, reject) => { const mod = url.startsWith("https") ? https : http; const req = mod.get(url, { timeout: 120000 }, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { return httpDownload(res.headers.location, dest).then(resolve, reject); } if (res.statusCode !== 200) { res.resume(); return reject(new Error(`HTTP ${res.statusCode} for ${url}`)); } const ws = fs.createWriteStream(dest); res.on("error", reject); ws.on("error", reject); res.pipe(ws); ws.on("finish", () => { ws.close((err) => (err ? reject(err) : resolve())); }); }); req.on("error", reject); req.on("timeout", () => { req.destroy(); reject(new Error("Download timeout")); }); }); } ``` Downloaded content is installed after only structural checks: ```js async function downloadFrom(base) { let downloadUrl = `${base}/api/v1/agentar/download?slug=${encodeURIComponent(slug)}`; if (version) { downloadUrl += `&version=${encodeURIComponent(version)}`; } console.log(` Downloading ${slug}${version ? ` v${version}` : ""} ...`); await httpDownload(downloadUrl, zipPath); } await downloadFrom(usedBase); assertValidAgentarZip(zipPath); extractZip(zipPath, extractDir); const contentDir = resolveContentDir(extractDir); if (!fs.existsSync(path.join(contentDir, "SOUL.md"))) { rmrf(tmpDir); console.error(`Error: invalid agentar "${slug}": missing SOUL.md`); process.exit(1); } extractWorkspaceFiles(contentDir, workspaceDir); mergeSkills( path.join(contentDir, "skills"), path.join(workspaceDir, "skills") ); `` ...[truncated 2271 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require each package to have a signed manifest that includes its slug, version, publisher identity, archive digest, and size. 2. Verify the signature and archive digest before extraction or workspace mutation. 3. Require HTTPS for the API base URL unless an explicit development-only override is enabled. 4. Restrict redirects to HTTPS and an allowlist of registry or content-delivery origins. 5. Reject cross-origin redirects by default. 6. Display verified publisher identity, version, digest, requested capabilities, and modified files before installation. 7. Add a local review or quarantine stage before remote instructions and skills become active. 8. Pin exact member package versions and hashes in signed team manifests. ]]>

T07 · Tool Hijacking and Spoofing

Error
Location
agentar_cli.mjs:175
Finding
Unsafe Trusted-Path Prefix Check Permits OpenClaw Executable Spoofing<![CDATA[ ## Vulnerability Details **File Location**: `agentar_cli.mjs:175-235` **Vulnerability Type**: Executable search-path spoofing **Risk Level**: High ### Vulnerable Code ```js function isTrustedDir(dir) { const resolved = path.resolve(dir); return TRUSTED_PATH_PREFIXES.some(prefix => resolved.startsWith(prefix)); } function findOpenclawBin() { const isWin = process.platform === "win32"; const name = "openclaw"; const pathExts = isWin ? (process.env.PATHEXT || ".CMD;.EXE;.BAT;.PS1") .split(";") .map(e => e.toLowerCase()) : [""]; const pathDirs = (process.env.PATH || "").split(isWin ? ";" : ":"); for (const dir of pathDirs) { if (!dir || !isTrustedDir(dir)) continue; for (const ext of pathExts) { const candidate = path.join(dir, name + ext); try { if (fs.existsSync(candidate)) return candidate; } catch { /* skip */ } } } const fallbacks = isWin ? [path.join( process.env.LOCALAPPDATA || path.join(HOME, "AppData", "Local"), "pnpm", "openclaw.cmd" )] : [path.join(HOME, ".local/share/pnpm/openclaw")]; for (const p of fallbacks) { if (fs.existsSync(p)) return p; } return null; } function spawnOpenclawSync(openclawBin, args, options) { const isWin = process.platform === "win32"; const ext = path.extname(openclawBin).toLowerCase(); if (isWin && (ext === ".cmd" || ext === ".bat")) { const comspec = process.env.ComSpec || "cmd.exe"; return spawnSync( comspec, ["/c", openclawBin, ...args], { ...options, shell: false } ); } return spawnSync(openclawBin, args, { ...options, shell: false }); } ``` ### Technical Analysis `isTrustedDir()` uses a raw string-prefix comparison rather than verifying that the candidate directory is equal to or located beneath a trusted directory boundary. For example: - `/usr/bin-attacker` starts with `/usr/bin`. - `/home/user/.local-malicious ...[truncated 1621 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce directory boundaries: ```js function isWithin(candidate, prefix) { const resolvedCandidate = fs.realpathSync(candidate); const resolvedPrefix = fs.realpathSync(prefix); return resolvedCandidate === resolvedPrefix || resolvedCandidate.startsWith(resolvedPrefix + path.sep); } ``` 2. Canonicalize both candidate and trusted paths with `realpathSync()` to resolve symlinks. 3. Prefer an explicitly configured absolute OpenClaw executable path over searching inherited `PATH`. 4. Verify that the selected path is a regular executable file. 5. On supported platforms, verify file ownership and reject executables or parent directories writable by untrusted users. 6. Consider verifying a trusted executable signature or digest. 7. On Windows, avoid relying on an unverified `ComSpec` environment variable for security-sensitive execution. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
agentar_cli.mjs:383
Finding
Single-Agent Archive Extraction Lacks Effective Decompression Limits<![CDATA[ ## Vulnerability Details **File Location**: `agentar_cli.mjs:383-440`, `agentar_cli.mjs:811-846`, and `agentar_cli.mjs:1180-1241` **Vulnerability Type**: ZIP decompression bomb and resource exhaustion **Risk Level**: Medium ### Vulnerable Code The single-agent extraction implementation inflates entire entries synchronously: ```js function extractZip(zipPath, destDir) { const buf = fs.readFileSync(zipPath); // Central-directory parsing omitted here for clarity. for (const cd of cdEntries) { const lhNameLen = buf.readUInt16LE(cd.localOffset + 26); const lhExtraLen = buf.readUInt16LE(cd.localOffset + 28); const dataStart = cd.localOffset + 30 + lhNameLen + lhExtraLen; const rawData = buf.subarray(dataStart, dataStart + cd.compSize); const normalized = path.normalize(cd.entryName); if (path.isAbsolute(normalized) || normalized.startsWith("..")) continue; const dest = path.join(destDir, normalized); if (cd.entryName.endsWith("/")) { mkdirp(dest); continue; } mkdirp(path.dirname(dest)); if (cd.method === 0) { fs.writeFileSync(dest, rawData); } else if (cd.method === 8) { const inflated = inflateRawSync(rawData); fs.writeFileSync(dest, inflated); } else { throw new Error( `unsupported compression method ${cd.method} for ${cd.entryName}` ); } } } ``` The installation flow calls it directly: ```js await httpDownload(downloadUrl, zipPath); assertValidAgentarZip(zipPath); const extractDir = path.join(tmpDir, "extracted"); mkdirp(extractDir); try { extractZip(zipPath, extractDir); } catch (err) { rmrf(tmpDir); console.error( `Error: failed to extract zip for "${slug}": ${err.message}\n${err.stack}` ); process.exit(1); } ``` A separate security validator contains decompressed-size and entry-count checks: ```js const ZIP_MAX_ENTRIES = 10000; function validateZipSecurity(buf) { // ... if (cdCount > ZIP_MAX_ENTRIES) { t ...[truncated 2084 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Call a hardened ZIP validator for every downloaded archive before extracting any entry. 2. Enforce strict limits for: - Compressed response size. - Number of archive entries. - Per-entry decompressed size. - Total decompressed size. - Maximum compression ratio. 3. Stream downloads and decompression rather than reading and inflating entire files synchronously. 4. Abort extraction immediately when a byte quota is exceeded. 5. Validate central-directory offsets, local-header offsets, lengths, and declared sizes before reading buffers. 6. Verify that actual inflated sizes match validated declarations. 7. Reserve or monitor available disk space and clean temporary files reliably after failures. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
agentar_cli.mjs:316
Finding
API Keys Are Exposed in Process Arguments and Plaintext Workspace Files<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:116-124`, `agentar_cli.mjs:316-329`, and `agentar_cli.mjs:2308` **Vulnerability Type**: Insecure secret input and plaintext credential storage **Risk Level**: Medium ### Vulnerable Code The Skill instructs users to provide the secret as a command-line argument: ```bash $CLI install <slug> --name <name> [--api-key <key>] [--version <ver>] $CLI install <slug> --overwrite [--version <ver>] ``` The CLI parses the key directly from the process argument vector: ```js if (arg === "--api-key" && i + 1 < args.length) { flags.apiKey = args[++i]; i++; continue; } ``` It then writes the key as plaintext without explicitly setting restrictive file permissions: ```js function writeCredentials(workspace, apiKey) { const skillsDir = path.join(workspace, "skills"); mkdirp(skillsDir); fs.writeFileSync( path.join(skillsDir, ".credentials"), `apiKey=${apiKey}\n` ); const gitignore = path.join(workspace, ".gitignore"); const entry = "skills/.credentials"; if (fs.existsSync(gitignore)) { const content = fs.readFileSync(gitignore, "utf-8"); if (!content.includes(entry)) { fs.appendFileSync(gitignore, `\n${entry}\n`); } } else { fs.writeFileSync(gitignore, `${entry}\n`); } } ``` ### Technical Analysis Command-line secrets can be retained in shell history and may be observable through process inspection, telemetry, terminal logging, debugging tools, or automation logs. The credential is also written to a predictable plaintext file. The effective permissions depend on the user's umask because no explicit `mode` is supplied and existing files are not hardened with `chmod`. Adding the path to `.gitignore` helps prevent accidental Git commits, but it does not provide confidentiality or access control. The audit did not find code that sends this API key to the marketplace. The issue is local exposure rather than confirmed network exfiltration. ### Attack Path 1 ...[truncated 1167 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove secret values from command-line arguments. 2. Accept credentials through hidden interactive input, a protected file descriptor, or standard input that is not echoed. 3. Prefer an operating-system credential store or secret manager over workspace plaintext. 4. If a local file is unavoidable, create it atomically with mode `0o600`: ```js fs.writeFileSync(credentialsPath, contents, { encoding: "utf-8", mode: 0o600, flag: "wx" }); ``` 5. Apply `chmodSync(credentialsPath, 0o600)` when updating an existing file. 6. Validate and restrict parent-directory permissions. 7. Document credential rotation and deletion procedures. 8. Ensure logs and error messages never include the key. 9. Continue filtering credentials from exports, but treat that filter as defense in depth rather than the primary protection. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (16)

Ae1

High
Category
analysis-evasion
Content
export, rollback, or team command, you MUST verify the bundled CLI.** The CLI (`agentar_cli.mjs`) is bundled in this skill's directory — no download or copy is
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
export, rollback, or team command, you MUST verify the bundled CLI.** The CLI (`agentar_cli.mjs`) is bundled in this skill's directory — no download or copy is
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
export, rollback, or team command, you MUST verify the bundled CLI.** The CLI (`agentar_cli.mjs`) is bundled in this skill's directory — no download or copy is
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
];
const SKIP_FILES = ["AGENTS.md", "BOOTSTRAP.md"];
const EXPORT_SKIP_DIRS = [".git", ".openclaw", "__MACOSX", "memory"];
const SENSITIVE_PATTERNS = [".credentials", ".env", ".secret", ".key", ".pem"];

// ─── Config (local-only, never sent over the network) ───────────────────────
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Hidden Instructions

High
Category
Prompt Injection
Content
const collab = teamYaml.collaboration_type || "LEAD_FOLLOWER";
  const lead = teamYaml.lead || "";

  let block = `<!-- TEAM:${teamName}:BEGIN -->\n`;
  block += `## Team: ${teamName}\n`;
  block += `Collaboration: ${collab}\n`;
  block += `Lead: ${lead}\n\n`;
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes a bundled Node CLI, performs network-backed marketplace operations, reads environment variables, and writes to multiple filesystem locations, but it declares no explicit tool scope such as permissions or allowed-tools. This creates an authorization gap where a host may expose shell/network/env capabilities more broadly than reviewers or policy engines expect, increasing the chance of unintended command execution or data access.

Session Persistence

Medium
Category
Rogue Agent
Content
**Source:** This skill is from the [CatchClaw skill repository](https://github.com/OpenAgentar/catchclaw).

An agentar is a distributable agent archive (ZIP) containing workspace files such as SOUL.md, skills, and other configuration. It can be installed as a new agent or used to overwrite an existing agent with a single command.

## Trigger Conditions
Confidence
90% confidence
Finding
The skill explicitly supports installing archives that can overwrite an existing agent workspace containing instructions, skills, and configuration. Even with user confirmation language, this is a persistence-capable operation that can replace or implant future behavior in the agent environment, making it a real security-sensitive capability if a malicious or untrusted marketplace package is installed.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger conditions activate on broad keywords like mere mention of "agentar" or "catchclaw," which can cause the skill to engage outside a clearly bounded user intent. In a skill that can install packages, modify workspaces, and write team metadata, over-broad auto-activation raises the risk of accidental invocation and unsafe follow-on actions.

Session Persistence

Medium
Category
Rogue Agent
Content
```bash
$CLI install <slug> --name <name> [--api-key <key>] [--version <ver>]
$CLI install <slug> --overwrite [--version <ver>]
```

Install an agentar from the marketplace.
Confidence
92% confidence
Finding
The install commands write agent content into active workspace locations and optionally overwrite the main workspace, which establishes durable filesystem state across sessions. Because the source is a remote marketplace artifact, this persistence can be exploited to seed malicious instructions or modified configs that affect later runs long after the initial command completes.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
**Important:**
- Do NOT execute install until the user explicitly selects one of the above options
- Do NOT use "new" as a default without asking
- Do NOT use "overwrite" unless the user explicitly selects it
- If the user chooses "new" but doesn't specify a name, use the slug as the default name
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
A marketplace search/install/export skill would be expected to perform file and network operations, but this file also discovers and spawns an external CLI binary. It uses subprocess execution both to manage local agents and to generate enriched metadata, which is an additional capability not justified by the manifest's narrow marketplace-focused purpose.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The export flow sends a prompt to the local openclaw agent runtime using workspace-derived context, which can cause untrusted agent content to influence a subprocessed LLM during export. In a skill marketplace context, installed agent files are adversarial input, so this enrichment step can trigger prompt injection, unintended tool use by the local agent runtime, or leakage through the local model/tool environment depending on openclaw's behavior.

Session Persistence

Medium
Category
Rogue Agent
Content
console.log("Aborted.");
                  return;
                }
                opts.overwrite = true;
                opts.name = name; // overwrite the existing agent's workspace
              }
              break;
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The manifest describes a marketplace skill for searching, installing, and exporting agentars and teams. This code also implements a separate rollback command that deletes and restores the main OpenClaw workspace from local backups, which is a substantial local workspace recovery capability not implied by the marketplace-oriented description.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
Rollback deletes and replaces the main workspace immediately after backup selection, with no final confirmation step before destructive action. In an agent skill context that operates on a user's active workspace, a mistaken invocation or scripted use can cause unexpected loss of local changes and operational disruption.

Intent-Code Divergence

Low
Confidence
94% confidence
Finding
The header comment says environment variables are used solely to resolve local paths and choose the server to query. In practice, the code also reads variables such as PATH, PATHEXT, LOCALAPPDATA, APPDATA, and ComSpec to discover and execute the `openclaw` CLI, so the documentation materially understates their role.

Static analysis

No suspicious patterns detected.