Back to skill

Security audit

ClawHub Push Skill

Security checks for vulnerabilities and agentic risk

Overview

This skill is a real ClawHub publishing helper, but it can upload more local files than users may expect and uses stored credentials for remote publication without enough safeguards.

Review this skill before installing. Use it only on clean skill directories that contain no secrets, .env files, private keys, local configs, or symlinks, and be aware it will use your local ClawHub token to publish to clawhub.ai while automatically accepting license terms. Prefer a version with dry-run output, explicit confirmation, stronger file exclusions, symlink rejection, and fixed batch behavior.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
push.js:81
Finding
Unrestricted Recursive Upload and Symbolic-Link Traversal Can Disclose Local Files<![CDATA[ ## Vulnerability Details **File Location**: `push.js:23-28`, `push.js:81-107`, `push.js:158-175`, `push.js:183-190` **Vulnerability Type**: Uncontrolled file collection, symbolic-link traversal, and unintended sensitive-data transmission **Risk Level**: High ### Vulnerable Code ```js // Files/directories excluded from publication const EXCLUDE_PATTERNS = [ '.git', 'node_modules', '.DS_Store', '*.log', ]; ``` ```js async function getSkillFiles(skillPath) { const files = []; async function walk(dir, relativePath = '') { try { const items = await fs.readdir(dir); for (const item of items) { if (shouldExclude(item)) continue; const fullPath = path.join(dir, item); const relPath = relativePath ? `${relativePath}/${item}` : item; const stat = await fs.stat(fullPath); if (stat.isDirectory()) { await walk(fullPath, relPath); } else { const content = await fs.readFile(fullPath, 'utf8'); files.push({ path: relPath, content, fullPath }); } } } catch (e) { console.error(`Error reading ${dir}: ${e.message}`); } } await walk(skillPath); return files; } ``` ```js const formData = new FormData(); formData.append('payload', JSON.stringify(payload)); // Add files for (const file of files) { const blob = new Blob([file.content], { type: 'text/plain' }); formData.append('files', blob, file.path); } // Call API const url = `${API_BASE}/skills`; try { const response = await fetch(url, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, }, body: formData, }); ``` ### Technical Analysis The publisher recursively reads every file beneath the user-supplied Skill directory, except for a small denylist containing `.git`, `node_modules`, `.DS_Store`, and log files. It does not exclude common sensitive resources such as: - ...[truncated 3074 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Reject symbolic links explicitly** - Replace `fs.stat()` with `fs.lstat()`. - Refuse to publish any entry for which `stat.isSymbolicLink()` is true. - Do not recursively traverse directory links. 2. **Enforce the canonical Skill-root boundary** - Resolve the Skill root once with `fs.realpath()`. - Resolve every candidate file with `fs.realpath()`. - Confirm that the candidate remains beneath the canonical root using a boundary-aware relative-path check. - Reject paths that escape the root. 3. **Adopt an allowlist or publication manifest** - Prefer an explicit manifest listing the files to publish. - Alternatively, allow only expected Skill file types and directories. - Do not rely exclusively on a short denylist. 4. **Exclude sensitive file patterns** - At minimum, reject `.env*`, private keys, credentials, tokens, certificates, backup files, and common cloud-provider credential files. - Use correctly anchored glob matching rather than dynamically constructing a partially anchored regular expression. 5. **Require informed user confirmation** - Print the complete normalized file list before transmission. - Clearly display total file count and upload size. - Require confirmation unless an explicit noninteractive option is supplied. 6. **Apply resource limits** - Limit maximum recursion depth, individual file size, total file count, and aggregate upload size. - Track visited canonical directories to prevent cycles. 7. **Fail closed** - Abort publication if traversal encounters a symbolic link, inaccessible entry, path escape, or validation error. - Do not silently continue with an uncertain upload set. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
batch.js:62
Finding
Batch Publisher Executes Import-Time CLI Logic and May Upload the Wrong Directory<![CDATA[ ## Vulnerability Details **File Location**: `batch.js:62`, `push.js:208-225` **Vulnerability Type**: Unsafe module side effects and incorrect publication scope **Risk Level**: Medium ### Vulnerable Code In `batch.js`, the selected child Skill is intended to be passed to an exported function: ```js try { const result = await import('./push.js').then(m => m.pushSkill(skillPath)); console.log(''); } catch (error) { console.error(`❌ Failed: ${error.message}\n`); } ``` However, `push.js` does not export `pushSkill`. Instead, it unconditionally runs its command-line entry point whenever the module is imported: ```js // CLI const args = process.argv.slice(2); const skillPath = args[0] || '.'; // Parse command-line arguments const options = {}; for (let i = 1; i < args.length; i++) { if (args[i] === '--slug' && args[i + 1]) { options.slug = args[++i]; } else if (args[i] === '--version' && args[i + 1]) { options.version = args[++i]; } else if (args[i] === '--name' && args[i + 1]) { options.name = args[++i]; } } pushSkill(skillPath, options).catch(error => { console.error(`❌ ${error.message}`); process.exit(1); }); ``` ### Technical Analysis Dynamic import executes the top-level code of the imported module. When `batch.js` imports `push.js`, the CLI block in `push.js` immediately reads the batch process's command-line arguments and calls `pushSkill()`. For a command such as: ```bash clawhub-push-batch ./skills ``` the imported module interprets `./skills` as a single Skill path. This is not the selected child path held in the local `skillPath` variable in `batch.js`. After the import-side publication begins, `batch.js` attempts to call `m.pushSkill(skillPath)`. Because `push.js` does not export `pushSkill`, this call fails with a type error. The batch operation is therefore both functionally broken and capable of initiating publication against a directory different from the selected child Skill. If the batch root cont ...[truncated 2085 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Separate reusable logic from CLI startup** - Move `pushSkill()` and its supporting functions into a side-effect-free library module. - Export `pushSkill` explicitly. - Import that library from both command-line entry points. 2. **Guard direct CLI execution** - Ensure the CLI block runs only when `push.js` is invoked directly, not when it is imported. - For ECMAScript modules, compare `import.meta.url` with the URL derived from `process.argv[1]`. 3. **Use a direct static import in batch mode** - Replace the dynamic import chain with an explicit import: ```js import { pushSkill } from './publisher.js'; ``` - Call `await pushSkill(skillPath)` only for the validated child directory. 4. **Validate publication scope** - Before each upload, verify that the target is exactly the discovered child Skill directory. - Print the resolved target path and reject the batch root unless it was explicitly selected for single-Skill publication. 5. **Add automated tests** - Verify that importing the publisher module produces no network or filesystem side effects. - Verify that batch mode publishes each selected child exactly once. - Verify that the batch root is never passed to the publisher unless explicitly requested. - Verify behavior when the batch root itself contains `SKILL.md`. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The documented purpose understates and misrepresents the actual operational behavior, especially around batch scanning, broad push behavior, and rough change detection. When a skill can publish multiple local skills remotely while claiming a simpler or narrower function, users may authorize actions they did not meaningfully understand, which can lead to accidental data disclosure or unwanted publication.

Known Vulnerable Dependency: js-yaml==4.1.1 — 4 advisory(ies): CVE-2026-84375 (js-yaml: maxTotalMergeKeys does not limit CPU use for empty merge sources); CVE-2026-59869 (js-yaml: YAML merge-key chains can force quadratic CPU consumption); GHSA-5p4m-2wfm-xmqj (JS-YAML: Quadratic CPU consumption in !!omap resolution (3.x and 4.x) — CVE-2026) +1 more

High
Category
Supply Chain
Confidence
97% confidence
Finding
The lockfile pins js-yaml to version 4.1.1, which is identified by the scanner as affected by multiple denial-of-service issues involving crafted YAML structures that can trigger excessive CPU consumption during parsing. Given this skill explicitly processes skill/package metadata and file format issues, YAML parsing is plausibly part of normal operation, so attacker-controlled input could make the tool hang or consume significant resources.

Known Vulnerable Dependency: js-yaml==4.1.1 — 4 advisory(ies): CVE-2026-84375 (js-yaml: maxTotalMergeKeys does not limit CPU use for empty merge sources); CVE-2026-59869 (js-yaml: YAML merge-key chains can force quadratic CPU consumption); GHSA-5p4m-2wfm-xmqj (JS-YAML: Quadratic CPU consumption in !!omap resolution (3.x and 4.x) — CVE-2026) +1 more

High
Category
Supply Chain
Confidence
98% confidence
Finding
The package depends on js-yaml 4.1.1, which is flagged with multiple CPU exhaustion/ReDoS-style advisories. Because this skill's purpose includes handling file format issues and likely parsing YAML/skill metadata supplied by users or repositories, a crafted YAML file could trigger excessive CPU consumption and cause denial of service during push or batch processing.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README explicitly advertises automatic handling of `acceptLicenseTerms`, which can cause users to submit a legal/contractual acknowledgement they may not have reviewed or intended to accept. In a publishing workflow, silently setting this field reduces informed consent and can create compliance, legal, and trust risks, especially if users assume the tool is only performing formatting fixes.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The README documents discovery of local auth token files and use of Bearer authentication for publishing, but provides no warning that the skill accesses stored credentials and transmits them to a remote registry. In the context of an installable push helper, this increases risk because users may not realize the tool reads sensitive tokens from disk and could expose them through misuse, logging, or unintended publication flows.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill describes behavior that uses local environment data and performs network publication, but it does not declare any tool scope or permissions boundary. That makes the trust boundary unclear to users and hosts, increasing the chance of silent token use or unintended remote actions when the skill is invoked.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The usage and feature description omit a clear warning that the skill will read locally stored authentication tokens and send content to a remote registry. This is dangerous because users may run it without realizing it uses existing credentials to perform authenticated publication, which can cause unintentional uploads under their account.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The script reads authentication tokens directly from the user's home-directory config files and then uses them to perform a network publish operation. While this matches the stated purpose of automating publication, accessing credentials from disk without an explicit consent prompt or clear disclosure increases the risk of unintended credential use and silent remote actions under the user's identity.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill accesses local credential files without prominently informing the user that it will read authentication material from their home directory. In context, this is tied to the publish workflow rather than obvious malware, but undisclosed credential access is still risky because it normalizes silent token harvesting behavior and can be abused if the endpoint or code changes.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The script recursively uploads nearly the entire skill directory to a remote API, with only a small exclusion list and no interactive confirmation or dry-run summary. This can unintentionally exfiltrate sensitive files included in the directory, such as secrets, local configs, test artifacts, or proprietary data, especially because users may not realize exactly what will be transmitted.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The manifest description and the body of the markdown instructions are presented in Chinese, and there is no indication that users may choose another language or that the skill is intentionally limited to a Chinese-speaking audience. This can violate language/locale policy when a skill forces a specific language without user opt-in.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
The manifest description is written entirely in Chinese and presents the skill's behavior in that locale without indicating any language choice or opt-in. Under the policy, language or locale constraints should either offer user choice or be clearly justified as region-specific.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"clawhub-push-batch": "./batch.js"
  },
  "dependencies": {
    "js-yaml": "^4.1.1"
  }
}
Confidence
93% confidence
Finding
Using a caret range for dependencies allows future installs to resolve to newer package versions without exact reproducibility, which can introduce unexpected behavior or supply-chain risk. In a publishing/push skill that likely processes user-provided files before uploading them, dependency drift can make builds non-deterministic and increase exposure to compromised or breaking releases.

Natural-Language Policy Violations

Low
Confidence
85% confidence
Finding
The file header describes the skill in Chinese only ('一键推送 skill 到 ClawHub registry...') with no indication that users can opt into another language. Under the policy, language or locale constraints should be optional or explicitly justified rather than implicitly imposed.

Context-Inappropriate Capability

Low
Confidence
92% confidence
Finding
The code imports execSync from child_process, which is a shell-execution capability unrelated to the stated purpose of pushing skills to the registry. Although it is not used in this file, its presence introduces an unjustified privileged capability relative to the documented intent.

Static analysis

Detected: suspicious.env_credential_access, suspicious.potential_exfiltration

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
push.js:17

Sensitive-looking file read is paired with a network send.

Warn
Code
suspicious.potential_exfiltration
Location
push.js:50