Back to skill

Security audit

Catchclaw Agentar

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it claims, but it has review-worthy risks because it can install unverified remote agent packages and persistently modify or overwrite local OpenClaw workspaces.

Review this skill carefully before installing. Use only trusted registries and agentars, avoid --overwrite unless you intend to replace the main workspace, do not pass API keys on the command line, and choose simple alphanumeric agent names until the path validation issue is fixed.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (6)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
agentar_cli.mjs:851
Finding
Workspace Name Path Traversal Enables Writes Outside the Intended Agent Directory<![CDATA[ ## Vulnerability Details **File Location**: `agentar_cli.mjs:851-875` **Vulnerability Type**: Path traversal and arbitrary filesystem write **Risk Level**: High ### Complete Code Snippet ```js } else { const name = agentName || slug.replace(/[^a-zA-Z0-9_-]/g, "-"); const workspaceDir = path.join(WORKSPACES_DIR, name); mkdirp(workspaceDir); const openclawBin = findOpenclawBin(); if (openclawBin) { const result = spawnOpenclawSync(openclawBin, [ "agents", "add", name, "--workspace", workspaceDir, "--non-interactive", ], { encoding: "utf-8", stdio: "pipe" }); if (result.status !== 0 && !(result.stderr || "").includes("already exists")) { rmrf(tmpDir); console.error(`Error: failed to create agent "${name}": ${result.stderr || result.error?.message || "unknown error"}`); process.exit(1); } } else { console.log(" Warning: openclaw CLI not found, skipping agent registration"); } extractWorkspaceFiles(contentDir, workspaceDir); mergeSkills(path.join(contentDir, "skills"), path.join(workspaceDir, "skills")); targetWorkspace = workspaceDir; } ``` ### Technical Analysis The value supplied through `--name` is used directly as a path component. Unlike the marketplace slug, the agent name is not constrained by an identifier validation function. A value containing `../` components can therefore cause the normalized workspace path to escape `WORKSPACES_DIR`. The resulting path is passed to `mkdirp`, `extractWorkspaceFiles`, and `mergeSkills`. These operations create directories, copy files, and remove existing destination directories before replacing them. Consequently, this is not limited to creating an empty directory: content from a remotely downloaded agent archive can be written to an attacker-selected location. ### Attack Path 1. An attacker persuades a user or automation system to install an agentar with a crafted name, such as: ```text --name ../../target-directory ``` 2. The CL ...[truncated 878 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Apply the same strict identifier policy to agent names as to slugs, for example: ```js if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name)) { throw new Error("Invalid agent name"); } ``` - Resolve and verify the destination before any filesystem operation: ```js const root = path.resolve(WORKSPACES_DIR); const workspaceDir = path.resolve(root, name); const relative = path.relative(root, workspaceDir); if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) { throw new Error("Agent workspace escapes the permitted root"); } ``` - Canonicalize existing parent directories with `fs.realpathSync` and reject symlink-based escapes. - Reject path separators, `.` and `..` components, absolute paths, drive prefixes, and UNC paths. - Perform extraction into a staging directory and move it into place only after all validation succeeds. ]]>

T08 · Insecure Dependencies

Error
Location
agentar_cli.mjs:795
Finding
Remote Agent Packages Are Installed Without Cryptographic Authenticity Verification<![CDATA[ ## Vulnerability Details **File Location**: `agentar_cli.mjs:795-875` **Vulnerability Type**: Untrusted remote package installation and supply-chain exposure **Risk Level**: High ### Complete Code Snippet ```js async function installAgentar({ slug, mode, apiBaseUrl, agentName, apiKey }) { validateSlug(slug); const tmpDir = mkdtemp("agentar-"); const zipPath = path.join(tmpDir, `${slug}.zip`); async function downloadFrom(base) { const downloadUrl = `${base}/api/v1/agentar/download?slug=${encodeURIComponent(slug)}`; console.log(` Downloading ${slug} ...`); await httpDownload(downloadUrl, zipPath); } let usedBase = apiBaseUrl.replace(/\/+$/, ""); try { await downloadFrom(usedBase); try { assertValidAgentarZip(zipPath); } catch (zipErr) { if (usedBase !== DEFAULT_API_BASE_URL) { console.log(` Warning: ${zipErr.message}`); console.log(` Retrying from default registry ${DEFAULT_API_BASE_URL} ...`); usedBase = DEFAULT_API_BASE_URL; await downloadFrom(usedBase); assertValidAgentarZip(zipPath); } else { throw zipErr; } } } catch (err) { rmrf(tmpDir); console.error(`Error: failed to download agentar "${slug}": ${err.message}`); process.exit(1); } 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); } 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); } let targetWorkspace; if (mode === "overwrite") { const backup = backupDirectory(MAIN_WORKSPACE, `before-install-${slug}`); if (backup) console.log(` Backed up main workspace to: ...[truncated 2243 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require a signed manifest for every agentar and verify it against pinned, trusted publisher keys. - Verify a cryptographic digest covering every archive entry before extraction. - Bind the manifest to the slug, version, publisher, archive hash, and issue time. - Reject plaintext HTTP endpoints for package download. - Show the package publisher, digest, requested file changes, and instruction files to the user before installation. - Add a local static audit stage for Markdown instructions and executable skill content. - Extract and validate packages in isolation before modifying an active workspace. - Pin package versions or hashes so that a previously approved slug cannot silently resolve to changed content. ]]>

T07 · Tool Hijacking and Spoofing

Error
Location
agentar_cli.mjs:183
Finding
Trusted Executable Search Can Be Bypassed Through Path-Prefix Confusion<![CDATA[ ## Vulnerability Details **File Location**: `agentar_cli.mjs:183-202` **Vulnerability Type**: Executable search-path hijacking **Risk Level**: High ### Complete Code Snippet ```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 */ } } } ``` The selected file is subsequently executed: ```js return spawnSync(openclawBin, args, { ...options, shell: false }); ``` ### Technical Analysis `isTrustedDir` uses a raw string-prefix comparison. This does not establish that the candidate directory is actually located beneath a trusted directory. For example, a directory named `~/.local-attacker` starts with the trusted string `~/.local` but is its sibling, not its descendant. Because the search iterates over attacker-influenced `PATH`, a forged `openclaw` executable in such a prefix-confusable directory can be selected and executed. Using `shell: false` prevents shell metacharacter injection but does not mitigate execution of the wrong binary. The code also tests only existence, without confirming that the candidate is a regular file, checking symlink targets, validating ownership, or verifying integrity. ### Attack Path 1. An attacker able to influence the victim's environment creates a writable directory whose name begins with a trusted prefix, such as `~/.local-attacker`. 2. The attacker places a malicious executable n ...[truncated 682 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Replace string-prefix comparisons with separator-aware containment checks: ```js function isWithin(root, candidate) { const rel = path.relative(path.resolve(root), path.resolve(candidate)); return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel)); } ``` - Resolve candidates and trusted roots with `fs.realpathSync` before comparison. - Use `fs.lstatSync` and reject symlinks unless their canonical targets are explicitly trusted. - Require candidates to be regular executable files. - Check expected ownership and reject user-writable binaries in system-level trusted directories. - Prefer a configured absolute path or a known installation manifest over scanning `PATH`. - Where feasible, verify the executable using a pinned digest or platform signing mechanism. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
agentar_cli.mjs:305
Finding
API Key Is Accepted Through Process Arguments and Stored as Plaintext Without an Explicit Restrictive Mode<![CDATA[ ## Vulnerability Details **File Location**: `agentar_cli.mjs:305-318, 1276` **Vulnerability Type**: Plaintext credential exposure **Risk Level**: Medium ### Complete Code Snippet ```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`); } } ``` ```js if (arg === "--api-key" && i + 1 < args.length) { flags.apiKey = args[++i]; i++; continue; } ``` ### Technical Analysis The CLI accepts the API key directly as a command-line argument. Depending on the operating system and execution environment, command-line arguments may be observable through process-inspection interfaces, diagnostic tooling, shell history, job logs, or automation logs. The key is then stored as plaintext in `skills/.credentials`. The call does not explicitly specify mode `0o600`; its effective permissions depend on the user's umask and pre-existing file state. Adding the path to `.gitignore` helps prevent accidental source-control inclusion but does not protect the file from local readers, backups, or other collection mechanisms. ### Attack Path 1. A user invokes installation with `--api-key <secret>`. 2. The secret is recorded in shell history or exposed in process arguments while the command runs. 3. The CLI writes the secret to `skills/.credentials`. 4. A local process or user with sufficient read access retrieves the argument or plaintext file. 5. The recovered key is reused against the associated backend service. ### Impact Assessment Exposure grants whatever backend privileges are assig ...[truncated 279 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not accept secrets directly as command-line values. - Read the key from protected standard input, an inherited file descriptor, or an operating-system secret manager. - If environment variables are supported, warn that they may still be exposed through process environments or logs. - Create the credential file with restrictive permissions: ```js fs.writeFileSync(credentialsPath, content, { encoding: "utf8", mode: 0o600, flag: "w", }); fs.chmodSync(credentialsPath, 0o600); ``` - Refuse to use a credential file owned by another user or having overly broad permissions. - Exclude credentials from all workspace backups and exports. - Avoid printing secret values and redact them from error and diagnostic output. - Document key rotation procedures in case exposure is suspected. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
agentar_cli.mjs:1114
Finding
Mandatory Explicit-Selection Gates Are Bypassed by Blank Input Defaults<![CDATA[ ## Vulnerability Details **File Location**: `agentar_cli.mjs:1114-1123, 1154-1157` **Vulnerability Type**: Missing explicit confirmation for security-sensitive actions **Risk Level**: Medium ### Complete Code Snippet ```js } else { const choice = await prompt( `Install agentar "${slug}":\n [1] Create a new agent (default)\n [2] Overwrite main agent (~/.openclaw/workspace)\nChoice (1/2, default 1): `, ); mode = choice === "2" ? "overwrite" : "new"; if (mode === "new" && !opts.name) { const name = await prompt(`Agent name (default: ${slug}): `); opts.name = name || slug; } } ``` ```js const choice = await prompt("\nSelect agent (default: 1): "); const idx = choice ? parseInt(choice, 10) - 1 : 0; if (idx < 0 || idx >= agents.length) { console.error("Error: invalid selection."); process.exit(1); } agentId = agents[idx].id; ``` The documented policy states: ```md Do NOT proceed with installation until the user has made a clear choice. NEVER assume or default to any mode without user confirmation. ``` ### Technical Analysis The implementation conflicts with the Skill's mandatory confirmation rules. During installation, any response other than the exact string `"2"` is interpreted as the new-agent mode, including blank or malformed input. During export, blank input automatically selects the first discovered agent. This converts a required affirmative user decision into a default action. It is particularly relevant in automated, piped, or unreliable terminal environments where an empty response may occur without deliberate selection. ### Attack Path 1. A user or automation invokes `install` without `--name` or `--overwrite`, or invokes `export` without `--agent`. 2. The prompt receives blank input, end-of-input behavior, or an unintended response. 3. Installation silently chooses the new-agent mode, or export silently chooses the first discovered agent. 4. The CLI proceeds despite the absence of the explicit selection requ ...[truncated 517 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require an exact, non-empty response for every mandatory selection. - Reject malformed input rather than mapping it to a default: ```js if (choice !== "1" && choice !== "2") { throw new Error("An explicit installation mode selection is required"); } ``` - For export, require a valid numeric selection and do not interpret blank input as index zero. - In non-interactive environments, require explicit command-line flags such as `--name`, `--overwrite`, or `--agent`. - Detect non-TTY input and fail closed when required confirmation is absent. - Keep implementation behavior synchronized with the hard-gate rules in `SKILL.md`. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
agentar_cli.mjs:17
Finding
Obfuscated Child-Process Import Conceals Local Command-Execution Capability<![CDATA[ ## Vulnerability Details **File Location**: `agentar_cli.mjs:17-20` **Vulnerability Type**: Deliberately obscured sensitive-module loading **Risk Level**: Low ### Complete Code Snippet ```js // Resolve subprocess module from Node.js built-in registry at runtime. // All invocations use shell:false with array args to prevent command injection. const _require = createRequire(import.meta.url); const _cp = builtinModules.find(m => m.length === 13 && m[5] === '_' && m.startsWith('c')); const { spawnSync } = _require(`node:${_cp}`); ``` ### Technical Analysis The code reconstructs the name of Node.js's `child_process` module by searching built-in module names according to length and character positions. It then loads the module dynamically through `createRequire`. This mechanism provides no security benefit compared with a direct import. It makes the command-execution capability less visible to reviewers and simplistic static-analysis rules. The observed subprocess calls use argument arrays and `shell: false`, so this finding does not independently establish shell command injection. The risk is reduced auditability and concealment of a security-sensitive capability. ### Attack Path 1. A reviewer or automated scanner searches for direct imports of `node:child_process`. 2. The dynamically reconstructed module name avoids the expected import pattern. 3. The code obtains `spawnSync` and later executes a discovered `openclaw` binary. 4. If combined with executable lookup weaknesses or future unsafe argument handling, the concealed capability makes the dangerous behavior harder to detect during review. ### Impact Assessment This pattern does not independently grant additional privileges. Its impact is primarily on source-code transparency, maintainability, and the reliability of security review. It increases the chance that subprocess execution and associated vulnerabilities will be overlooked. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Replace the dynamic reconstruction with an explicit import: ```js import { spawnSync } from "node:child_process"; ``` - Document every subprocess invocation, its executable trust assumptions, and its argument-validation requirements. - Add static-analysis rules covering direct and dynamic access to command-execution APIs. - Avoid obfuscation in security-sensitive code unless it is strictly required and documented with a verifiable rationale. ]]>
Vulnerability Patterns
  • 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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (13)

Ae1

High
Category
analysis-evasion
Content
tall, export, or rollback 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
tall, export, or rollback 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
tall, export, or rollback 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 ──────────────────────────────────────────────────────────────────
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill explicitly instructs the agent to run shell commands and use environment-dependent behavior, but it does not declare any tool scope such as allowed-tools or permissions. This creates a mismatch between documented capability and enforcement, increasing the chance the skill can invoke shell operations more broadly than intended without clear sandboxing or user-visible authorization boundaries.

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 is designed to install archives that can create or overwrite agent workspaces containing configuration, skills, and behavior files. That is persistent state modification, and in this context it is security-relevant because a marketplace package can materially change future agent behavior or replace an existing workspace in one step.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger condition allows invocation on any mention of "agentar" or "catchclaw," which is overly broad for a skill that can search, install, overwrite workspaces, export archives, and rollback state. Broad activation increases the risk of accidental tool use in unrelated conversations and can expose users to unintended destructive or security-sensitive actions.

Session Persistence

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

Install an agentar from the marketplace.
Confidence
91% confidence
Finding
The install commands write files into agent workspaces and optionally persist credentials in a skill-local `.credentials` file, creating durable changes beyond the current session. Persistent writes are especially dangerous here because the source is a marketplace artifact that may contain untrusted instructions or configurations that influence later executions.

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
90% confidence
Finding
During export, the code invokes the local openclaw CLI and passes agent content into another agentic system to synthesize metadata. This creates an unexpected execution/data-flow boundary where untrusted local workspace content can influence a secondary model/tool invocation, potentially causing prompt-injection-style behavior, unintended network/tool use by the downstream CLI, or leakage of sensitive workspace content into generated outputs or logs.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
The install flow executes the external openclaw binary to register agents, which is outside basic marketplace download/extract behavior and introduces side effects in the local agent runtime. Even with trusted-path checks and shell:false, this still allows the skill to modify local agent state and rely on an external executable whose behavior may vary, increasing attack surface and the chance of unauthorized persistence or configuration changes.

Session Persistence

Medium
Category
Rogue Agent
Content
mode = "new";
  } else {
    const choice = await prompt(
      `Install agentar "${slug}":\n  [1] Create a new agent (default)\n  [2] Overwrite main agent (~/.openclaw/workspace)\nChoice (1/2, default 1): `,
    );
    mode = choice === "2" ? "overwrite" : "new";
    if (mode === "new" && !opts.name) {
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 CLI implements rollback and full local workspace restoration, which exceeds the stated marketplace scope of search/install/export and gives the skill destructive filesystem capabilities over ~/.openclaw/workspace. In an agent-skill context, this broadens impact significantly: a user invoking a marketplace helper could be induced to overwrite or restore local state, causing loss of data, tampering with agent configuration, or persistence of attacker-supplied content.

Static analysis

No suspicious patterns detected.