Back to skill

Security audit

百度图片下载

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real Baidu image downloader, but it needs review because it follows untrusted image URLs and redirects and can consume unbounded memory, disk, and network resources.

Install only if you are comfortable with a local Node script making outbound requests to Baidu and image-origin hosts and writing batches of files. Prefer the default thumb or middle source, keep counts small, avoid broad custom output paths, and run it in a network environment where requests to internal services or metadata endpoints are not exposed.

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

Warning
Location
scripts/baidu_img.js:91
Finding
<![CDATA[Unrestricted Remote URL and Redirect Handling Enables SSRF]]><![CDATA[ ## Vulnerability Details **File Location**: `scripts/baidu_img.js:91-111` **Related Data Flow**: `scripts/baidu_img.js:181-182`, `scripts/baidu_img.js:207-212`, `scripts/baidu_img.js:318` **Vulnerability Type**: Server-Side Request Forgery through unvalidated image URLs and redirect destinations **Risk Level**: Medium ### Vulnerable Code ```js /** Follow at most 5 redirects */ function httpRequest(rawUrl, headers) { return new Promise((resolve, reject) => { let redirected = 0; const visit = (u) => { const lib = u.startsWith('https') ? https : http; const req = lib.get( u, { headers, timeout: TIMEOUT_MS }, (res) => { const status = res.statusCode || 0; if (status === 301 || status === 302) { res.resume(); if (redirected++ > 5) return reject(new Error('Too many redirects')); const loc = res.headers.location; if (!loc) return reject(new Error('Redirect missing Location')); return visit(new URL(loc, u).toString()); } resolve(res); }, ); req.on('error', reject); req.on('timeout', () => req.destroy(new Error('timeout'))); }; visit(rawUrl); }); } ``` The untrusted URL is selected from remote search-result metadata and later passed to the request function: ```js for (const it of items) { const u = pickUrl(it, source); if (!u) continue; const host = (it.fromURLHost || '').trim(); ``` ```js async function downloadOne(url, outPath, referer) { const headers = { 'User-Agent': UA, Accept: 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8', }; if (referer) headers.Referer = referer; let lastErr = null; for (let i = 0; i <= RETRY; i++) { try { const buf = await httpGetBinary(url, headers); if (buf.length < 256) throw new Error(`Response data too small (${buf.length}B)`); fs.writeFileSync(outPath, buf); return buf.l ...[truncated 3293 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse each initial and redirected destination using `new URL()` and explicitly allow only `http:` and `https:`. 2. Resolve the hostname before connecting and reject all non-public destinations, including: - IPv4 loopback, private, link-local, multicast, unspecified, and reserved ranges. - IPv6 loopback, unique-local, link-local, multicast, unspecified, and IPv4-mapped private addresses. - Cloud metadata and platform-specific internal service addresses. 3. Apply the same checks to every redirect destination rather than only to the initial URL. 4. Protect against DNS rebinding by connecting to the validated resolved address while preserving the intended TLS server name and `Host` header, or use a hardened outbound-request library or proxy that enforces destination policy. 5. Prefer an explicit host allowlist for `thumb` and `middle` modes. Treat arbitrary original-image hosts as higher risk. 6. Consider disabling redirects for original-image downloads or requiring explicit user approval before crossing to a different origin. 7. Validate that successful responses have an expected image media type before saving them. 8. Use a strict redirect counter and reject unsupported redirect status codes or malformed `Location` values safely. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/baidu_img.js:132
Finding
<![CDATA[Unbounded Response Buffering Enables Memory, Disk, and Execution-Time Exhaustion]]><![CDATA[ ## Vulnerability Details **File Location**: `scripts/baidu_img.js:132-144` **Related Data Flow**: `scripts/baidu_img.js:49-70`, `scripts/baidu_img.js:207-212`, `scripts/baidu_img.js:293-338` **Vulnerability Type**: Uncontrolled resource consumption **Risk Level**: Medium ### Vulnerable Code ```js function httpGetBinary(rawUrl, headers) { return new Promise((resolve, reject) => { httpRequest(rawUrl, headers) .then((res) => { const chunks = []; res.on('data', (c) => chunks.push(c)); res.on('end', () => resolve(Buffer.concat(chunks))); res.on('error', reject); }) .catch(reject); }); } ``` The complete response is then copied into a single buffer and written synchronously: ```js async function downloadOne(url, outPath, referer) { const headers = { 'User-Agent': UA, Accept: 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8', }; if (referer) headers.Referer = referer; let lastErr = null; for (let i = 0; i <= RETRY; i++) { try { const buf = await httpGetBinary(url, headers); if (buf.length < 256) throw new Error(`Response data too small (${buf.length}B)`); fs.writeFileSync(outPath, buf); return buf.length; } catch (e) { lastErr = e; await sleep(500 * (i + 1)); } } throw new Error('Download failed: ' + (lastErr ? lastErr.message : 'unknown')); } ``` The requested count also has no upper bound: ```js else if (a === '-n' || a === '--count') args.count = parseInt(next(), 10) || 30; ``` ### Technical Analysis Every response chunk is retained in the `chunks` array until the remote server ends the response. `Buffer.concat(chunks)` then allocates a contiguous buffer for the complete body, temporarily increasing memory pressure because both the individual chunks and concatenated buffer may coexist. The implementation does not enforce: - A maximum response size. - An aggregate byte quota for the complete run. - A v ...[truncated 2390 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Stream each response to a temporary file rather than accumulating it in memory. 2. Track received bytes and abort the request immediately when a per-file limit is exceeded. 3. Enforce an aggregate byte quota for the entire invocation. 4. Validate `Content-Length` before downloading when it is present, while still enforcing a streaming limit because the header may be absent or dishonest. 5. Add a total request deadline independent of socket activity. 6. Validate `--count` as a finite positive integer and enforce a conservative maximum. 7. Validate `--delay` as a finite, non-negative number within an operationally safe range. 8. Require an allowed image media type and optionally verify the file signature before committing the file. 9. Write to a uniquely named temporary file in the destination directory and atomically rename it only after successful validation. 10. Destroy the response stream and delete the temporary file on timeout, size-limit violation, invalid media type, or any other error. 11. Check available disk space where practical and stop cleanly before the aggregate quota is exhausted. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (8)

Ae1

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

Ae1

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

Ae1

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

Ae1

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

Ae1

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

Vague Triggers

Medium
Confidence
93% confidence
Finding
The trigger phrases are broad enough that ordinary user requests about downloading or searching for images could activate the skill without clear user intent boundaries. Because the skill performs external network retrieval and bulk local file writes, accidental invocation can cause unintended downloads, disk usage, and outbound requests to third-party hosts.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill description does not clearly warn that it will retrieve content from external sites and create many files under a local directory. In this context, lack of disclosure is security-relevant because users may trigger mass downloads from Baidu and origin sites without understanding the privacy, bandwidth, storage, and content-safety implications.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The HTTP request hard-codes an `Accept-Language` header preferring `zh-CN` and `zh`, which imposes a specific locale on outbound requests. There is no option for the user to choose or override the language/locale behavior, so this is a natural-language locale policy issue.

Static analysis

No suspicious patterns detected.