Back to skill

Security audit

Industry Research Agent

Security checks for vulnerabilities and agentic risk

Overview

This skill appears to do what it says, but it downloads and stores remote report files and installs an unpinned dependency in ways that need careful review before use.

Install only in a trusted environment with a scoped EM_API_KEY. Prefer an isolated virtual environment, pin dependencies yourself, set INDUSTRY_RESEARCH_REPORT_OUTPUT_DIR to a controlled location, and treat generated attachments and share links as untrusted remote content until verified.

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)

T08 · Insecure Dependencies

Warning
Location
SKILL.md:12
Finding

Unpinned Third-Party Dependency Installation

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/get_data.py:89
Finding

Unbounded Decoding and Storage of Remote Attachments

Content
View full analysis
bool: """将 base64 字符串解码后写入文件。""" if not b64_str: return False try: raw = base64.b64decode(b64_str) file_path.parent.mkdir(parents=True, exist_ok=True) file_path.write_bytes(raw) return True except Exception as exc: print(f"[WARN] 保存附件失败 ({file_path.name}): {exc}", file=sys.stderr) return False ``` ```python safe_base = _safe_filename(title, "industry_research_report") pdf_output_path = output_dir / f"{safe_base}.pdf" pdf_saved = _save_base64_file( result_data_api["data"]["pdfBase64"], pdf_output_path ) word_output_path = output_dir / f"{safe_base}.docx" word_saved = _save_base64_file( result_data_api["data"]["wordBase64"], word_output_path ) ``` ### Technical Analysis The script accepts Base64 attachment fields from the remote API, decodes each complete value into memory, and writes the resulting bytes to disk. It does not enforce limits on the HTTP response body, encoded string, decoded attachment, or total storage consumed. The use of `base64.b64decode(b64_str)` creates a complete decoded copy in memory. The original encoded value is already held in the parsed JSON response, so a sufficiently large response can consume substantial memory through both representations. `Path.write_bytes()` then writes the complete payload without checking available disk space or applying a storage quota. The code also assigns `.pdf` and `.docx` extensions without validating file signatures or structure. A compromised upstream service could therefore cause arbitrary non-executable content to be stored under misleading file extensions. The audit did not find code that automatically opens or exec ...[truncated 1275 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/get_data.py:104
Finding

Unbounded DOCX Member Decompression

Content
View full analysis
str: """ Best-effort extract plain text from a DOCX file without extra dependencies. This is only used as a fallback when API content is empty. """ try: with zipfile.ZipFile(docx_path, "r") as z: xml = z.read("word/document.xml").decode( "utf-8", errors="replace" ) except Exception: return "" # DOCX text typically appears in nodes; keep simple & robust. text = re.sub(r"(?s)]*>(.*?)", r"\1\n", xml) text = re.sub(r"(?s)<[^>]+>", " ", text) text = text.replace("&", "&").replace("<", "<").replace(">", ">") text = re.sub(r"[ \t]+\n", "\n", text) text = re.sub(r"\n{3,}", "\n\n", text) return text.strip() ``` ```python truncated_text = content.strip() # Fallback: if API returns empty content, try extracting from DOCX if not truncated_text and word_saved and word_output_path.is_file(): truncated_text = _extract_docx_text(word_output_path) ``` ### Technical Analysis DOCX files are ZIP archives. The fallback extraction code calls `ZipFile.read()` on `word/document.xml` without inspecting the member's declared uncompressed size or compression ratio. A small compressed member can therefore expand into a very large in-memory byte string. After decompression, the implementation creates additional string objects during UTF-8 decoding and successive regular-expression substitutions. These copies can amplify memory consumption. The fallback is reachable whenever the API returns empty report content and a successfully saved DOCX attachment. The exception handler does not prevent memory exhaustion because the operating system may terminate the process before Python can catch an exception. It ...[truncated 1014 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
scripts/get_data.py:183
Finding

Unvalidated Remote Share URL Propagated to Users

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (5)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill invokes a Python script, requires an environment secret, writes report files to disk, and performs network-backed data retrieval, yet it declares no explicit tool scope or allowed-tools boundary. This increases the blast radius of execution because the runtime may grant broader-than-necessary capabilities, making misuse, accidental data exposure, or unintended side effects harder to contain.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

技能说明与触发示例全部以中文表述,并以中文场景为默认输入输出方式,但未说明是否支持其他语言或允许用户指定语言。这会形成默认语言强制,若组织要求按用户偏好提供语言选择,则可能构成自然语言策略违规。

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The trigger conditions are broad enough to match many ordinary industry-related questions, so the skill may execute code and call external services when the user only wanted a normal discussion or lightweight analysis. In this context, over-triggering is security-relevant because activation causes network access, local file creation, and use of a sensitive API key.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The module docstring, argument descriptions, usage text, and error messages are written only in Chinese, which imposes a specific language on users. The file does not indicate that the skill is region-specific or provide any opt-in or alternative language handling, matching the language/locale policy concern.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The module docstring and manifest consistently describe this skill as generating deep industry research reports from an industry query. However, the --query argument help text says the input is natural language such as '股价大于1000元的股票', which is a stock screening request and not an industry research request. This is an active documentation contradiction rather than a mere omission.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.