Back to skill

Security audit

Business Opportunity Screenshot

Security checks for vulnerabilities and agentic risk

Overview

This skill appears intended to make a business-opportunity report and screenshot, but its script can run unintended system commands and write files outside intended folders when given crafted inputs.

Review before installing. Only run this in a restricted workspace with trusted arguments, and avoid using a browser profile containing sensitive sessions. The script should be fixed to use argument-array process execution, validate slugs and filenames, escape HTML output, close the Chromium process, and update or pin dependencies from an approved registry.

Vulnerability Patterns
  • 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
  • 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 (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/screenshot.js:41
Finding
OS Command Injection Through the User-Controlled Search Query<![CDATA[ ## Vulnerability Details **File Location**: `scripts/screenshot.js:41-59` **Vulnerability Type**: OS command injection **Risk Level**: Critical ### Vulnerable Code ```javascript function exec(command, options = {}) { console.log(`[EXEC] ${command}`); try { return execSync(command, { stdio: 'pipe', encoding: 'utf8', timeout: 15000, ...options }).trim(); } catch (e) { console.log(`[WARN] Command failed: ${e.message}`); return ''; } } function searchSkills(query) { console.log(`[INFO] Searching for skills: ${query}`); const result = exec(`clawhub search ${query}`); ``` The `query` value originates from the first command-line argument: ```javascript const args = process.argv.slice(2); const query = args[0] || 'opportunity'; ``` ### Technical Analysis The application constructs a command string by directly interpolating an untrusted command-line argument: ```javascript `clawhub search ${query}` ``` That string is passed to `child_process.execSync`, which executes it through a command shell. Consequently, shell metacharacters in `query`, including command separators, substitutions, redirects, and pipelines, are interpreted by the shell rather than passed as literal ClawHub search text. The 15-second timeout only limits how long the child process may run. It does not prevent an injected command from modifying files, spawning detached processes, accessing credentials, or initiating network requests before the timeout. ### Attack Path 1. An attacker supplies or influences the first argument passed to `scripts/screenshot.js`. 2. The argument contains shell syntax in addition to an apparent search query. 3. `searchSkills` concatenates the value into `clawhub search ${query}`. 4. `exec` passes the resulting string to `execSync`. 5. The operating-system shell interprets the injected syntax. 6. The injected command executes with the same operating-system identity and environment as the Node.js proc ...[truncated 744 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not construct shell command strings from user input. Invoke the executable directly with an argument array and without a shell: ```javascript const { execFileSync } = require('child_process'); function searchSkills(query) { const result = execFileSync( 'clawhub', ['search', query], { stdio: 'pipe', encoding: 'utf8', timeout: 15000 } ).trim(); return result; } ``` Additional hardening should include: 1. Reject control characters and impose a reasonable maximum query length. 2. Set `shell: false` explicitly when using `spawnSync`. 3. Avoid logging raw attacker-controlled values without sanitizing terminal control characters. 4. Run the Skill under a dedicated, least-privileged account. 5. Add tests containing shell metacharacters to verify that they are passed as literal argument content. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/screenshot.js:68
Finding
OS Command Injection Through ClawHub-Provided Skill Slugs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/screenshot.js:68-89` **Vulnerability Type**: Second-order OS command injection **Risk Level**: High ### Vulnerable Code ```javascript const lines = result.split('\n').filter(l => l.includes('- ')); const skills = []; for (const line of lines) { const match = line.match(/-\s+(\S+)\s+(.+)/); if (match) { skills.push({ slug: match[1], name: match[2] }); } } console.log(`[INFO] Found ${skills.length} skills from API`); return skills.slice(0, 15); } function inspectSkill(slug) { try { const result = exec(`clawhub inspect ${slug}`); ``` ### Technical Analysis Skill slugs parsed from the output of `clawhub search` are treated as trusted even though they originate outside the application. The parser accepts any contiguous non-whitespace value as a slug: ```javascript (\S+) ``` This permits shell metacharacters to be included in the parsed value. The slug is later interpolated into a shell command and executed through the same `execSync` wrapper used by the search operation. This is a second-order injection issue: the dangerous value is first received and stored as parsed search data, then reaches a shell execution sink during the inspection phase. Exploitation requires control over or compromise of the ClawHub command output, a spoofed local `clawhub` executable, or another mechanism that can alter the returned search text. ### Attack Path 1. An attacker causes `clawhub search` to return a crafted result line containing a slug with shell syntax. 2. The regular expression accepts the crafted non-whitespace value as `match[1]`. 3. The value is stored as `s.slug`. 4. The main processing loop passes the value to `inspectSkill`. 5. `inspectSkill` builds `clawhub inspect ${slug}`. 6. `execSync` executes the string through a shell. 7. The injected command runs with the privileges of the Skill process. ### Impact Assessment If the external search response or local ClawHub tool is attac ...[truncated 429 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Invoke ClawHub without a command shell: ```javascript const result = execFileSync( 'clawhub', ['inspect', slug], { stdio: 'pipe', encoding: 'utf8', timeout: 15000 } ).trim(); ``` Validate every externally sourced slug before use: ```javascript const SAFE_SLUG = /^[A-Za-z0-9][A-Za-z0-9._-]*$/; if (!SAFE_SLUG.test(slug)) { throw new Error('Invalid skill slug'); } ``` The implementation should also: 1. Prefer structured ClawHub output, such as JSON, if the CLI supports it. 2. Reject malformed records rather than processing loosely parsed terminal output. 3. Resolve the expected `clawhub` executable from a trusted absolute path. 4. Ensure the process `PATH` cannot be modified by untrusted callers. 5. Treat all registry metadata as untrusted data throughout the report pipeline. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/screenshot.js:306
Finding
Arbitrary File Placement Through Output Name Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/screenshot.js:306-307, 385-386, 434` **Vulnerability Type**: Path traversal and unintended file overwrite **Risk Level**: High ### Vulnerable Code ```javascript const htmlPath = path.join(WORKSPACE, `${outputName}.html`); fs.writeFileSync(htmlPath, html, 'utf8'); ``` The filename is derived from a command-line argument: ```javascript const args = process.argv.slice(2); const query = args[0] || 'opportunity'; const outputName = args[1] || `商业发现-${getDateString()}`; ``` The same value is also used for the screenshot path: ```javascript const screenshotPath = path.join(OUTPUT_DIR, `${outputName}.jpg`); ``` ### Technical Analysis `outputName` is intended to be a filename, but it is not validated or reduced to a basename before being passed to `path.join`. Components such as `../` are normalized by Node.js path handling and can cause the resulting path to leave `WORKSPACE` or `OUTPUT_DIR`. Appending `.html` or `.jpg` does not prevent traversal. It only constrains the final suffix. If the resolved destination is writable, the HTML report is written directly with `fs.writeFileSync`, while Puppeteer writes the screenshot to the second attacker-influenced path. Absolute-path behavior varies by path construction and platform, but relative traversal sequences are sufficient to escape the intended output directories. ### Attack Path 1. An attacker controls the second argument supplied to the script. 2. The attacker includes parent-directory components in `outputName`. 3. `path.join` normalizes those components. 4. The resolved HTML or screenshot path leaves its intended base directory. 5. The report generation or screenshot operation creates or overwrites a file at the unintended destination. 6. If a sensitive writable location and useful file suffix are available, the attacker can alter application content or plant attacker-influenced files. ### Impact Assessment The vulnerability allows creatio ...[truncated 545 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Restrict the output name to a filename rather than a path: ```javascript function validateOutputName(value) { if (!/^[A-Za-z0-9._-]{1,100}$/.test(value)) { throw new Error('Invalid output name'); } if (value === '.' || value === '..') { throw new Error('Invalid output name'); } return value; } ``` Resolve and verify each final destination: ```javascript function safeOutputPath(baseDir, name, extension) { const base = path.resolve(baseDir); const candidate = path.resolve(base, `${name}${extension}`); if (!candidate.startsWith(base + path.sep)) { throw new Error('Output path escapes the permitted directory'); } return candidate; } ``` Further hardening should include: 1. Generate internal random filenames when caller-selected names are unnecessary. 2. Use exclusive creation flags where overwriting is not required. 3. Reject path separators on both POSIX and Windows platforms. 4. Apply separate containment checks to the HTML and screenshot destinations. 5. Run with filesystem permissions limited to the designated output directory. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/screenshot.js:131
Finding
HTML and Script Injection in the Generated Report<![CDATA[ ## Vulnerability Details **File Location**: `scripts/screenshot.js:131-307` **Vulnerability Type**: HTML injection and local report script execution **Risk Level**: Medium ### Vulnerable Code ```javascript function generateReport(skills, outputName, isFallback = false) { const fallbackNote = isFallback ? '<p style="text-align:center;color:#ff9800;margin-bottom:20px;">⚠️ 数据来源:预设列表(API 限流)</p>' : ''; const html = `<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>${outputName}</title> ``` External Skill fields are also interpolated directly: ```javascript ${skills.slice(0,3).map(s => ` <div class="skill-card"> <div class="skill-header"> <span class="skill-name">${s.slug}</span> <span class="skill-owner">@${s.owner}</span> </div> <p class="skill-desc">${s.summary}</p> <div class="skill-meta"> <span>📅 更新: ${s.updated}</span> <span>📦 版本: ${s.version}</span> </div> </div> `).join('')} ``` The generated content is written as HTML: ```javascript const htmlPath = path.join(WORKSPACE, `${outputName}.html`); fs.writeFileSync(htmlPath, html, 'utf8'); ``` It is subsequently opened in Chromium: ```javascript await startBrowser(`file://${htmlPath}`); ``` ### Technical Analysis The code places `outputName` and multiple externally sourced ClawHub fields directly into HTML markup without context-sensitive escaping. Values containing HTML tags or event-handler attributes can therefore alter the generated document. A payload capable of introducing executable markup may run when Chromium opens the local report. The vulnerable fields include at least: - `outputName` - `s.slug` - `s.owner` - `s.summary` - `s.updated` - `s.version` The same rendering pattern is repeated for each report category. Because the report is loaded automatically in a browser, exploitation does not require the user to open ...[truncated 1229 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Escape every dynamic value according to its HTML context. For plain text nodes and the title, a minimal encoder can be used: ```javascript function escapeHtml(value) { return String(value) .replaceAll('&', '&amp;') .replaceAll('<', '&lt;') .replaceAll('>', '&gt;') .replaceAll('"', '&quot;') .replaceAll("'", '&#39;'); } ``` Apply it to every untrusted field: ```javascript <span class="skill-name">${escapeHtml(s.slug)}</span> <span class="skill-owner">@${escapeHtml(s.owner)}</span> <p class="skill-desc">${escapeHtml(s.summary)}</p> ``` Additional controls should include: 1. Prefer a DOM builder that assigns untrusted values through `textContent`. 2. Disable JavaScript on the Puppeteer page if report scripting is unnecessary. 3. Add a restrictive Content Security Policy that disallows inline scripts and unnecessary network destinations. 4. Validate and limit the length of every external metadata field. 5. Avoid loading the report in a persistent browser profile containing authenticated sessions or sensitive state. 6. Add tests covering tags, quotes, event handlers, and encoded markup in every rendered field. ]]>

T08 · Insecure Dependencies

Note
Location
package-lock.json:16
Finding
Dependency Lockfile Uses a Non-Default Package Registry Mirror<![CDATA[ ## Vulnerability Details **File Location**: `package-lock.json:16-18` **Vulnerability Type**: Third-party dependency supply-chain exposure **Risk Level**: Low ### Vulnerable Code ```json "node_modules/@puppeteer/browsers": { "version": "2.13.0", "resolved": "https://registry.npmmirror.com/@puppeteer/browsers/-/browsers-2.13.0.tgz", "integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==" } ``` The same mirror is used for Puppeteer Core and numerous transitive dependencies, including: ```json "resolved": "https://registry.npmmirror.com/puppeteer-core/-/puppeteer-core-24.39.1.tgz" ``` ### Technical Analysis The lockfile directs dependency downloads through `registry.npmmirror.com` rather than the default npm registry. This expands the project's supply-chain trust boundary to include the availability, security, synchronization, and administrative controls of that mirror. The recorded Subresource Integrity hashes provide meaningful protection against a mirror returning bytes that differ from those locked by the project. However, they do not remove all risk associated with: - A lockfile generated from an already compromised or unexpected source. - Future lockfile updates that accept malicious package content and new hashes. - Availability or synchronization failures at the alternate registry. - Organizational policy requiring dependency retrieval from an approved registry. No evidence in the reviewed files proves that the named mirror or the locked package contents are malicious. The issue is therefore a supply-chain hardening concern rather than evidence of an embedded malicious dependency. ### Attack Path 1. A developer or build environment runs `npm install` or `npm ci`. 2. npm follows the `resolved` URLs recorded in the lockfile. 3. Package archives are downloaded from the alternate mirror. 4. If an attacker compromises the dependency update process, mirror, or lockfile and can als ...[truncated 713 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Confirm that the alternate mirror is explicitly approved by the project's security and build policies. 2. If it is not required, configure npm to use `https://registry.npmjs.org/` and regenerate the lockfile from a trusted environment. 3. Use `npm ci` in automated builds so installed versions match the reviewed lockfile. 4. Preserve and verify integrity hashes. 5. Pin reviewed dependency versions and review lockfile changes before merging. 6. Run dependency vulnerability and provenance checks in continuous integration. 7. Restrict package lifecycle scripts during high-risk review workflows where practical. 8. Use a controlled internal registry proxy if organizational caching or mirroring is required. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (11)

Ae1

High
Category
analysis-evasion
Content
node scripts/screenshot.js
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/screenshot.js
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/screenshot.js
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Known Vulnerable Dependency: basic-ftp==5.2.0 — 4 advisory(ies): GHSA-6v7q-wjvx-w8wg (basic-ftp: Incomplete CRLF Injection Protection Allows Arbitrary FTP Command Exe); CVE-2026-39983 (basic-ftp has FTP Command Injection via CRLF); CVE-2026-41324 (basic-ftp vulnerable to denial of service via unbounded memory consumption in Cl) +1 more

High
Category
Supply Chain
Confidence
97% confidence
Finding
The lockfile pins basic-ftp to 5.2.0, which is flagged with multiple advisories including FTP command injection and denial-of-service issues. Although this package is only a transitive dependency here (via get-uri/pac-proxy-agent/proxy-agent in the Puppeteer stack), keeping a known vulnerable version in the dependency tree creates risk if any code path allows attacker-influenced FTP/PAC URI handling.

Known Vulnerable Dependency: extract-zip==2.0.1 — 2 advisory(ies): CVE-2026-19693 (extract-zip allows arbitrary file writes through symlink archive entries); CVE-2026-56876 (extract-zip unvalidated symlink path traversal)

High
Category
Supply Chain
Confidence
98% confidence
Finding
extract-zip 2.0.1 is present as a transitive dependency and has advisories for arbitrary file write and symlink path traversal during archive extraction. In this dependency graph it is brought in by @puppeteer/browsers, so if the skill ever downloads and extracts browser archives or other attacker-influenced ZIP content, exploitation could lead to writes outside the intended directory.

Known Vulnerable Dependency: ip-address==10.1.0 — 2 advisory(ies): CVE-2026-69192 (ip-address: Address4 decodes leading-zero octets as decimal while resolvers deco); CVE-2026-42338 (ip-address has XSS in Address6 HTML-emitting methods)

High
Category
Supply Chain
Confidence
89% confidence
Finding
ip-address 10.1.0 is a known vulnerable transitive dependency through socks/socks-proxy-agent/proxy-agent. The listed issues require specific use of IP parsing or HTML-emitting methods; in this lockfile-only context there is no evidence the vulnerable functionality is actually exercised, but the dependency is still genuinely vulnerable and could become reachable if proxy parsing or related output handling is used.

Known Vulnerable Dependency: ws==8.19.0 — 2 advisory(ies): CVE-2026-45736 (ws: Uninitialized memory disclosure); CVE-2026-48779 (ws: Memory exhaustion DoS from tiny fragments and data chunks)

High
Category
Supply Chain
Confidence
96% confidence
Finding
ws 8.19.0 is included by puppeteer-core and is flagged for memory disclosure and memory exhaustion denial-of-service vulnerabilities. Because Puppeteer communicates with browser/debugging endpoints over WebSocket, a vulnerable ws version can matter if the skill connects to untrusted or attacker-controlled endpoints, making the issue more relevant than a dormant utility dependency.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The HTML output explicitly sets lang="zh-CN" and the visible UI text throughout the report is in Chinese, which forces a specific language/locale. There is no opt-in, fallback, or documented region-specific justification in the file, so this matches the language/locale policy concern.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"license": "ISC",
  "type": "commonjs",
  "dependencies": {
    "puppeteer-core": "^24.39.1"
  }
}
Confidence
94% confidence
Finding
The dependency is version-ranged with a caret (^24.39.1), so installs may resolve to newer minor/patch releases over time rather than a single audited artifact. This creates a software supply chain risk: a compromised or breaking upstream release could be pulled in automatically, reducing build reproducibility and potentially introducing vulnerable code into the skill.

Missing User Warnings

Low
Confidence
86% confidence
Finding
The script creates a new HTML file under the workspace via fs.writeFileSync, which is a file-write operation covered by the warning rule for code files. Although there is an informational log after the write, there is no prior disclosure, confirmation, or inline documentation warning that the script will create artifacts in the user's workspace.

Missing User Warnings

Low
Confidence
89% confidence
Finding
The code creates the output directory and writes a screenshot image to disk, which affects user files and storage. The later success log confirms the action after it occurs, but the file lacks any explicit warning or descriptive comment near entrypoint behavior that screenshots and directories will be created.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/screenshot.js:41