Back to skill

Security audit

qrcode

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its QR-code purpose, but it needs review because it auto-installs mutable dependencies, fetches arbitrary remote URLs, and writes untrusted decoded content back into spreadsheets.

Review before installing. Use local image files instead of remote URLs when possible, run it in a restricted environment, avoid opening generated CSV/XLSX results from untrusted QR sources without checking for formulas, back up batch input files before decoding, and pin or manually review dependencies before allowing installation.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/decode.py:20
Finding
Unrestricted URL Fetching Enables SSRF and Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/decode.py:20-35` - `scripts/batch_decode.py:36-55` - `scripts/decode.js:29-51` - `scripts/batch_decode.js:35-52` **Vulnerability Type**: Server-Side Request Forgery and Unbounded Resource Consumption **Risk Level**: High ### Vulnerable Code Python single-image decoder: ```python def is_url(s: str) -> bool: return s.startswith("http://") or s.startswith("https://") def download_image(url: str) -> str: """Download an image into a temporary file and return its path.""" import urllib.request suffix = Path(url.split("?")[0]).suffix or ".png" tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix) try: urllib.request.urlretrieve(url, tmp.name) except Exception as e: tmp.close() os.unlink(tmp.name) raise RuntimeError(f"Image download failed: {e}") tmp.close() return tmp.name ``` Python batch decoder: ```python def _is_url(s: str) -> bool: return s.startswith("http://") or s.startswith("https://") def _try_zxing(source: str) -> str | None: try: import zxingcpp from PIL import Image except ImportError: return None tmp_path = None try: if _is_url(source): import urllib.request suffix = Path(source.split("?")[0]).suffix or ".png" tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix) urllib.request.urlretrieve(source, tmp.name) tmp.close() tmp_path = tmp.name img_path = tmp_path ``` Node.js single-image decoder: ```javascript function isUrl(s) { return s.startsWith("http://") || s.startsWith("https://"); } function downloadToTemp(url) { return new Promise((resolve, reject) => { const ext = path.extname(url.split("?")[0]) || ".png"; const tmp = path.join(os.tmpdir(), `qr_${Date.now()}${ext}`); const mod = url.startsWith("https") ? https : http; const file = fs. ...[truncated 3890 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse URLs with a standards-compliant URL parser and allow only explicitly supported schemes. 2. Prefer HTTPS and reject URLs containing embedded credentials. 3. Resolve the hostname before connecting and block: - Loopback ranges - RFC 1918 private ranges - IPv6 unique-local ranges - Link-local ranges - Multicast and reserved ranges - Known cloud metadata addresses 4. Repeat hostname and IP validation after every redirect to prevent redirect-based bypasses. 5. Limit the number of redirects, such as to three. 6. Add strict connection, response, and total-operation timeouts. 7. Stream responses while enforcing a conservative maximum byte count. 8. Reject non-2xx HTTP responses. 9. Validate the response content type and inspect file signatures before image decoding. 10. Configure Pillow and Sharp limits for image dimensions, pixel counts, and decompression-bomb detection. 11. Consider requiring explicit user confirmation before fetching URLs from batch files. 12. Where practical, require users to download remote images separately and pass a local file to the decoder. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/batch_decode.py:119
Finding
Attacker-Controlled QR Content Is Written to Spreadsheets Without Formula Neutralization<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/batch_decode.py:119-146` - `scripts/batch_decode.py:204-223` - `scripts/batch_decode.js:149-167` - `scripts/batch_decode.js:181-202` **Vulnerability Type**: CSV and Spreadsheet Formula Injection **Risk Level**: High ### Vulnerable Code Python CSV output: ```python decoded_results = [] success = 0 for row in data_rows: url = row[col_idx].strip() if col_idx < len(row) else "" if url: decoded = decode_single(url) else: decoded = FAIL_PLACEHOLDER decoded_results.append(decoded) if decoded != FAIL_PLACEHOLDER: success += 1 if output_txt: with open(output_txt, "w", encoding="utf-8") as f: f.write("\n".join(decoded_results)) return { "total": len(data_rows), "success": success, "failed": len(data_rows) - success, "output_file": os.path.abspath(input_path), "output_txt": os.path.abspath(output_txt), } result_col = "Decode Result" headers.append(result_col) for i, row in enumerate(data_rows): row.append(decoded_results[i]) with open(input_path, "w", encoding="utf-8-sig", newline="") as f: writer = csv.writer(f) writer.writerow(headers) writer.writerows(data_rows) ``` Python Excel output: ```python result_col_idx = ws.max_column + 1 ws.cell(row=1, column=result_col_idx, value="Decode Result") for i, decoded in enumerate(decoded_results): ws.cell(row=i + 2, column=result_col_idx, value=decoded) wb.save(input_path) wb.close() ``` Node.js CSV output: ```javascript const decoded = []; let success = 0; for (const row of dataRows) { const url = (row[colIdx] || "").trim(); const d = url ? await decodeSingle(url) : FAIL; decoded.push(d); if (d !== FAIL) success++; } headers.push("Decode Result"); dataRows.forEach((r, i) => r.push(decoded[i])); const csvContent = [headers, ...dataRows] .map((r) => r.map((c) => `"${String(c).replace(/"/g, '""')}"`).join(",")) .join("\n"); ...[truncated 2319 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every decoded QR value as untrusted text. 2. Before writing CSV data, neutralize values whose first non-whitespace character is `=`, `+`, `-`, or `@`. 3. Prefix dangerous values with an apostrophe or another application-approved text marker. 4. For XLSX output, explicitly create text cells rather than allowing type inference. 5. Preserve the original decoded value separately if exact byte-for-byte recovery is required. 6. Add test cases for formulas, leading whitespace, tabs, carriage returns, and Unicode characters that spreadsheet applications may normalize. 7. Warn users that batch results may contain untrusted content. 8. Prefer writing to a new output file instead of replacing the original workbook or CSV. 9. If formulas are an expected legitimate QR payload, provide an explicit opt-in mode rather than enabling formula interpretation by default. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:24
Finding
Automatic Installation Uses Unpinned and Unlocked Third-Party Dependencies<![CDATA[ ## Vulnerability Details **File Locations**: - `SKILL.md:24-40` - `requirements.txt:1-4` - `package.json:5-10` **Vulnerability Type**: Mutable Dependency Resolution and Unattended Package Installation **Risk Level**: Medium ### Vulnerable Code Automatic installation instructions: ```markdown **Dependency checking and automatic installation:** After selecting the runtime, check whether dependencies are installed. If they are missing, install them automatically without asking the user: - **Python**: ```bash pip install -r requirements.txt ``` - **Node.js**: ```bash npm install ``` ``` Unpinned Python dependencies: ```text zxingcpp Pillow openpyxl qrcode ``` Mutable npm dependency ranges: ```json "dependencies": { "qrcode": "^1.5.0", "qr-scanner-wechat": "^0.1.0", "sharp": "^0.33.0", "xlsx": "^0.18.0", "archiver": "^7.0.0" } ``` ### Technical Analysis The Skill directs the Agent to install missing packages automatically without user confirmation. Python dependencies have no exact versions or integrity hashes. npm dependencies use caret ranges, and the project has no package lockfile. The effective code installed during first use can therefore change after the Skill itself has been reviewed. Package managers may also install mutable transitive dependencies. npm lifecycle scripts and Python build processes can execute code during installation with the privileges of the Agent process. No evidence in the audited repository proves that the listed packages are currently malicious. The issue is that dependency identity and contents are not reproducibly constrained. ### Attack Path 1. The Skill is invoked in an environment without its dependencies. 2. Its instructions cause the Agent to run `pip install` or `npm install` automatically. 3. The package registry resolves versions at installation time. 4. A compromised release or transitive dependency is downloaded. 5. Package build code, import-time code, or npm lifecycle scripts ...[truncated 623 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct Python dependency to an exact reviewed version. 2. Generate and enforce cryptographic hashes with a tool such as `pip-compile`. 3. Commit an npm lockfile generated from reviewed dependency versions. 4. Use reproducible npm installation through `npm ci`. 5. Configure trusted registries and reject unexpected registry substitutions. 6. Audit direct and transitive dependencies before updating lockfiles. 7. Disable npm lifecycle scripts where compatible with required native dependencies. 8. Use isolated virtual environments or containers for package installation. 9. Do not install packages automatically without informing the user and obtaining confirmation. 10. Run dependency installation and QR processing with the minimum required filesystem and network permissions. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/decode.js:33
Finding
Predictable Temporary Filename Permits Local Symlink File Clobbering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/decode.js:33-39` **Vulnerability Type**: Insecure Temporary File Creation **Risk Level**: Low ### Vulnerable Code ```javascript function downloadToTemp(url) { return new Promise((resolve, reject) => { const ext = path.extname(url.split("?")[0]) || ".png"; const tmp = path.join(os.tmpdir(), `qr_${Date.now()}${ext}`); const mod = url.startsWith("https") ? https : http; const file = fs.createWriteStream(tmp); mod.get(url, (res) => { ``` ### Technical Analysis The single-image Node.js decoder creates a temporary path from the current timestamp and a URL-derived extension. It does not use a private temporary directory, cryptographically unpredictable filename, or exclusive file creation. `fs.createWriteStream` opens an existing path for writing by default and may follow a symbolic link. On a multi-user system, a local attacker able to write to the shared temporary directory could predict or race the timestamp-based filename and create a symbolic link at that path. When the decoder opens the path, downloaded data may be written to the symlink target. Exploitation requires local access and successful timing or filename prediction, so the practical risk is lower than the remotely triggerable issues. ### Attack Path 1. A local attacker monitors or predicts when the decoder will run. 2. The attacker creates a symbolic link in the shared temporary directory using the expected timestamp-based filename. 3. The symlink points to a file writable by the Agent account. 4. The decoder calls `fs.createWriteStream` on the predictable path. 5. The runtime follows the symbolic link and truncates or overwrites the target with downloaded bytes. ### Impact Assessment The attacker may overwrite or corrupt files writable by the user running the Skill. The vulnerability does not independently bypass operating-system permissions and cannot overwrite files that the Agent account cannot modify. Th ...[truncated 99 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a private temporary directory with `fs.promises.mkdtemp`. 2. Generate filenames using cryptographically secure random values. 3. Open temporary files with exclusive creation flags such as `wx`. 4. Avoid following symbolic links and verify the opened file is a regular file. 5. Use restrictive file and directory permissions. 6. Remove temporary files and their private directory in a `finally` block. 7. Avoid deriving temporary file extensions directly from untrusted URLs unless the extension is validated against a strict image allowlist. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (73)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
If the underlying decoder can process more than QR codes, the skill's QR-only framing is imprecise and may mislead users about the breadth of data it can extract. This is lower impact than the network and file-write issues, but still a documentation mismatch that affects informed consent and policy gating.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
If the underlying decoder can process more than QR codes, the skill's QR-only framing is imprecise and may mislead users about the breadth of data it can extract. This is lower impact than the network and file-write issues, but still a documentation mismatch that affects informed consent and policy gating.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
If the underlying decoder can process more than QR codes, the skill's QR-only framing is imprecise and may mislead users about the breadth of data it can extract. This is lower impact than the network and file-write issues, but still a documentation mismatch that affects informed consent and policy gating.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
If the underlying decoder can process more than QR codes, the skill's QR-only framing is imprecise and may mislead users about the breadth of data it can extract. This is lower impact than the network and file-write issues, but still a documentation mismatch that affects informed consent and policy gating.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
If the underlying decoder can process more than QR codes, the skill's QR-only framing is imprecise and may mislead users about the breadth of data it can extract. This is lower impact than the network and file-write issues, but still a documentation mismatch that affects informed consent and policy gating.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
If the underlying decoder can process more than QR codes, the skill's QR-only framing is imprecise and may mislead users about the breadth of data it can extract. This is lower impact than the network and file-write issues, but still a documentation mismatch that affects informed consent and policy gating.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
If the underlying decoder can process more than QR codes, the skill's QR-only framing is imprecise and may mislead users about the breadth of data it can extract. This is lower impact than the network and file-write issues, but still a documentation mismatch that affects informed consent and policy gating.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
If the underlying decoder can process more than QR codes, the skill's QR-only framing is imprecise and may mislead users about the breadth of data it can extract. This is lower impact than the network and file-write issues, but still a documentation mismatch that affects informed consent and policy gating.

Ae1

High
Category
analysis-evasion
Content
| 单个解码 | `python scripts/decode.py ...` | `node scripts/decode.js ...` |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| 批量生成 | `python scripts/batch_generate.py ...` | `node scripts/batch_generate.js ...` |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| 批量解码 | `python scripts/batch_decode.py ...` | `node scripts/batch_decode.js ...` |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The header comment says the tool performs local decoding, but the implementation downloads remote images when a URL is supplied. This misleading description can cause users to run the script in sensitive environments under false assumptions, increasing the risk of unwanted data egress and network exposure.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The script accepts URL values from input data and downloads them over HTTP/HTTPS, which contradicts the skill’s stated local-only behavior and expands its trust boundary to the network. This can leak user IP/addressing metadata to attacker-controlled hosts, enable SSRF-like access to internal resources if the environment has network reachability, and process untrusted remote content without restrictions.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The README states that remote image URLs may be downloaded locally for decoding, but it does not prominently warn users that this causes outbound network access and local file handling of untrusted content. In a skill context, that matters because users may assume 'fully local' means no network activity at all, and remote fetches can expose metadata, hit internal resources if misused, or process malicious files.

Rp1

Medium
Category
MCP Rug Pull
Confidence
86% confidence
Finding
The README instructs users to run `npx skills add ...` without pinning the version of the `skills` package or otherwise constraining the fetched code. Because `npx` resolves and executes packages dynamically, users may receive a newer or compromised package version than expected, creating a supply-chain execution risk during installation.

Rp1

Medium
Category
MCP Rug Pull
Confidence
86% confidence
Finding
This installation command uses `npx skills add ...` without a pinned version for the `skills` executable. That means installation behavior depends on whatever version is current at execution time, increasing exposure to malicious or breaking upstream changes in the package supply chain.

Rp1

Medium
Category
MCP Rug Pull
Confidence
86% confidence
Finding
The global install example invokes `npx skills` without a version pin, which causes remote package resolution at runtime. If the upstream package is hijacked or updated unexpectedly, users could execute unintended code during installation.

Rp1

Medium
Category
MCP Rug Pull
Confidence
86% confidence
Finding
The agent-specific installation example also relies on an unpinned `npx skills` invocation. In security terms this is a supply-chain risk because the command implicitly trusts whatever package version the registry serves at that moment.

Rp1

Medium
Category
MCP Rug Pull
Confidence
86% confidence
Finding
This command repeats the same unpinned `npx skills` pattern, which is risky because it executes code fetched on demand from the package ecosystem. Even though the README appears benign, dynamic unpinned bootstrap commands are a common route for supply-chain compromise.

Vague Triggers

Medium
Confidence
96% confidence
Finding
The instruction to trigger the skill by asking the agent "Generate a QR code for me" is broad and does not define boundaries or exclusion conditions. While QR-related, it serves as the primary activation guidance and lacks any narrowing context or negative examples, which could lead to unintended invocation for loosely related requests.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The README states that when decoding a remote image URL, the skill will download the image locally, but it does not present this network access as a prominent warning to users. This can lead to unexpected outbound requests, privacy leakage, SSRF-style access to internal resources if arbitrary URLs are accepted, and processing of untrusted remote content.

Rp1

Medium
Category
MCP Rug Pull
Confidence
83% confidence
Finding
The README instructs users to run `npx skills add ...` without pinning the version of the `skills` package. This creates a supply-chain risk because a future compromised or maliciously updated package version could be fetched and executed at install time. In a skill installation context, this is more dangerous because users are explicitly encouraged to execute the command directly from the documentation.

Rp1

Medium
Category
MCP Rug Pull
Confidence
83% confidence
Finding
This installation example uses `npx skills add` without pinning the version of the invoked package, exposing users to unreviewed upstream changes or package compromise. Because `npx` may download and execute code on demand, the risk is a practical supply-chain execution issue rather than a purely informational concern.

Rp1

Medium
Category
MCP Rug Pull
Confidence
83% confidence
Finding
The documented global install command relies on an unpinned `npx skills` invocation. If the package or one of its transient dependencies is hijacked, users could execute attacker-controlled code during setup, and global installation may broaden system impact.

Rp1

Medium
Category
MCP Rug Pull
Confidence
83% confidence
Finding
This command again uses `npx skills add` without constraining the installer version, leaving the setup path dependent on whatever package version npm resolves at the time. In security-sensitive environments, such floating installer references are a recognized supply-chain weakness.

Static analysis

No suspicious patterns detected.