Back to skill

Security audit

Doc2Markdown

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it claims, but it can upload local documents to a third-party cloud service during ordinary read or summarize requests, so users should review it before installing.

Install only if you are comfortable sending selected documents to lab.hjcloud.com for conversion. Avoid using it on confidential, regulated, or proprietary files unless you have verified the service's handling terms, and prefer explicit conversion requests over automatic use for general reading or summarization. Do not configure DOCCHAIN_SKILLS_API_KEY unless you intend authenticated requests to that service.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/doc2markdown.js:148
Finding
Unbounded Response Buffering and ZIP Extraction Enable Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/doc2markdown.js:148-169`, `scripts/doc2markdown.js:541-583` **Vulnerability Type**: Uncontrolled resource consumption through unbounded network buffering and archive extraction **Risk Level**: Medium ### Vulnerable Code ```js const req = httpModule.request(reqOptions, (res) => { const chunks = []; res.on('data', (chunk) => { chunks.push(chunk); }); res.on('end', () => { const buffer = Buffer.concat(chunks); let data; if (options.responseType === 'arraybuffer') { data = buffer; } else { const text = buffer.toString('utf8'); try { data = JSON.parse(text); } catch (e) { data = text; } } resolve({ status: res.statusCode, data: data }); }); }); ``` ```js const entries = []; const diskEntries = buffer.readUInt16LE(pos + 8); const dirStart = buffer.readUInt32LE(pos + 16); pos = dirStart; for (let i = 0; i < diskEntries; i++) { if (buffer.readUInt32LE(pos) !== 0x02014b50) break; const flags = buffer.readUInt16LE(pos + 8); const method = buffer.readUInt16LE(pos + 10); const nameLen = buffer.readUInt16LE(pos + 28); const extraLen = buffer.readUInt16LE(pos + 30); const commentLen = buffer.readUInt16LE(pos + 32); const offset = buffer.readUInt32LE(pos + 42); const name = buffer.toString('utf8', pos + 46, pos + 46 + nameLen); entries.push({ offset, method, name, encrypted: !!(flags & 1) }); pos += 46 + nameLen + extraLen + commentLen; } // Extract each file for (const ent of entries) { if (ent.encrypted || ent.name.endsWith('/')) continue; const o = ent.offset; const sig = buffer.readUInt32LE(o); if (sig !== 0x04034b50) continue; const nameLen = buffer.readUInt16LE(o + 26); const extraLen = buffer.readUInt16LE(o + 28); const csize = buffer ...[truncated 3475 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Enforce a maximum download size** - Validate `Content-Length` when present and reject responses above a configured threshold. - Track the cumulative number of bytes received and immediately destroy the request when the limit is exceeded. - Do not rely solely on `Content-Length`, because responses may omit or falsify it. 2. **Stream downloads instead of buffering entire responses** - Stream archive data to a securely created temporary file. - Use exclusive file creation and restrictive permissions. - Remove temporary and partially extracted files in a `finally` block. 3. **Apply strict archive limits before and during extraction** - Limit the total number of ZIP entries. - Limit compressed and uncompressed size per entry. - Limit total uncompressed bytes across the archive. - Reject entries with an excessive compression ratio. - Verify that offsets, lengths, and metadata remain within the archive buffer or file boundaries. 4. **Use bounded asynchronous decompression** - Replace `zlib.inflateSync` with streaming asynchronous decompression. - Count decompressed bytes while streaming and abort as soon as an entry or aggregate limit is exceeded. - Apply an extraction timeout or cancellation mechanism to constrain CPU use. 5. **Protect filesystem capacity** - Check available storage where supported. - Extract into a staging directory and only move completed output into place after successful validation. - Apply deployment-level filesystem quotas and process memory limits as defense in depth. 6. **Validate the downloaded format** - Verify the expected content type and ZIP signatures before extraction. - Reject malformed central-directory records and unsupported compression methods. - Treat all conversion-service output as untrusted, even though it is delivered over HTTPS. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill is presented as a lightweight local conversion utility with no external dependencies, but the content later reveals that it uploads documents to a remote cloud service for parsing and may use authentication credentials from env/config. This mismatch is dangerous because users or agents may send sensitive document contents off-host under the false assumption that processing is local, creating confidentiality and compliance risk.

Ae1

High
Category
analysis-evasion
Content
node scripts/doc2markdown.js convert <file_path> # Downloads MD package
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/doc2markdown.js convert <file_path> # Downloads MD package
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/doc2markdown.js convert <file_path> # Downloads MD package
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/doc2markdown.js convert <file_path> # Downloads MD package
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The skill description presents this as a lightweight local conversion utility with no external dependencies, but the code hardcodes a remote service endpoint and uploads user documents to it for processing. This is dangerous because it creates a significant confidentiality and trust mismatch: users or agents may provide sensitive local files under the false assumption that processing is local-only.

Lp3

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding
The skill exposes capability to read environment-based secrets (`DOCCHAIN_SKILLS_API_KEY`) but does not declare any tool scope or permission boundary. In agent environments, undeclared access to environment data weakens user visibility and policy enforcement, making secret use less auditable and easier to invoke unexpectedly.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger guidance includes broad phrases such as requests to 'read', 'view', 'analyze', or 'summarize' a document, which can cause the skill to be invoked in many ordinary workflows. Because invocation results in uploading the referenced file to an external service, overbroad triggers increase the chance of unintended data exfiltration from routine document-handling requests.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The code loads an API key from environment/config and uses it to authenticate requests to a third-party service, which exceeds what a user would reasonably infer from the manifest's stated document-conversion purpose. While credential use itself is not inherently malicious, undisclosed remote credentialed access increases the blast radius if the service is untrusted, compromised, or used in a higher-privilege environment.

Missing User Warnings

Medium
Confidence
99% confidence
Finding
This code reads arbitrary local files and uploads their contents to a remote service without any explicit warning, confirmation, or privacy notice at the point of use. In an agent setting, that is especially risky because the tool may be invoked on confidential documents and exfiltrate them off-host under the guise of a normal file conversion operation.

Missing User Warnings

Low
Confidence
80% confidence
Finding
The script saves downloaded Markdown into the source file directory, and the output filename is derived from the input basename, which can overwrite an existing .md file with the same name. The code performs the write silently without confirmation or a warning about where files will be created or replaced.

Static analysis

No suspicious patterns detected.