Back to skill

Security audit

paper-reading

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a coherent paper-reading helper, but its PDF downloader can reach non-arXiv network locations despite presenting itself as arXiv-scoped.

Review this skill before installing if it will run in an environment with access to private networks or sensitive writable paths. Prefer using local PDFs or trusted arXiv URLs, run it in a constrained workspace, avoid privileged execution, and pin dependencies if you adopt it.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetch_pdf.py:30
Finding
Weak arXiv URL Validation Permits Arbitrary Network Retrieval<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch_pdf.py`, lines 30–31, 77–81, and 101–110 **Vulnerability Type**: Insufficient URL validation and server-side request forgery exposure **Risk Level**: Medium ### Vulnerable Code ```python def is_arxiv_url(url: str) -> bool: return bool(re.search(r'arxiv\.org', url, re.IGNORECASE)) ``` ```python def download_pdf(url: str, output: str) -> str: url = normalize_arxiv_pdf_url(url) print(f"Downloading from: {url}", file=sys.stderr) urllib.request.urlretrieve(url, output) return os.path.abspath(output) ``` ```python if source.startswith("http://") or source.startswith("https://"): if not is_arxiv_url(source): print(f"Error: Only arXiv URLs are supported. Got: {source}", file=sys.stderr) sys.exit(1) output = args.output if not output: match = re.search(r'arxiv\.org/(?:abs|pdf)/([\d.]+)', source) if match: arxiv_id = match.group(1).replace(".", "_") output = f"arxiv_{arxiv_id}.pdf" else: output = "paper.pdf" pdf_path = download_pdf(source, output) ``` ### Technical Analysis The URL allowlist is implemented as an unanchored regular-expression search over the entire URL string. It verifies only that the text `arxiv.org` appears somewhere; it does not parse the URL or confirm that the destination hostname is an approved arXiv host. For example, a URL such as the following passes the check even though its destination is not arXiv: ```text http://127.0.0.1:8080/resource?source=arxiv.org ``` The URL remains unchanged by `normalize_arxiv_pdf_url()` unless it contains a matching `arxiv.org/abs/<numeric-id>` substring. It is then passed directly to `urllib.request.urlretrieve()`. Redirect destinations are not independently validated, and the downloaded response is not checked for an expected media type, PDF signature, or maximum size. This creates an arbitrary network-retrieval primitive and p ...[truncated 1612 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the URL with `urllib.parse.urlsplit()` rather than searching the raw string. 2. Require the `https` scheme. 3. Compare the normalized hostname against an explicit allowlist such as `arxiv.org` and `export.arxiv.org`; do not use substring or suffix checks that permit deceptive domains. 4. Reject embedded credentials, fragments, unexpected ports, and unsupported arXiv path formats. 5. Restrict accepted paths to recognized `/abs/<id>` and `/pdf/<id>` formats, then construct the final PDF URL internally. 6. Disable automatic redirects or validate the scheme, hostname, port, and path of every redirect destination. 7. Stream the response with explicit connection and read timeouts and enforce a maximum download size. 8. Confirm the expected content type and verify that the downloaded file begins with a valid PDF signature. 9. Write to a newly created file and refuse to overwrite an existing destination unless the user explicitly confirms it. 10. Where appropriate, block loopback, link-local, private, and other internal address ranges after DNS resolution as defense in depth. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetch_pdf.py:25
Finding
arXiv Search Uses Unauthenticated HTTP and Trusts the Returned Download URL<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch_pdf.py`, lines 25 and 48–70 **Vulnerability Type**: Cleartext transport and unvalidated remote URL **Risk Level**: Medium ### Vulnerable Code ```python ARXIV_API = "http://export.arxiv.org/api/query" ``` ```python def search_arxiv(title: str) -> Optional[str]: """Search arXiv by title and return the PDF URL of the most relevant result.""" params = urllib.parse.urlencode({ "search_query": f'ti:"{title}"', "max_results": "1", "sortBy": "relevance", "sortOrder": "descending", }) url = f"{ARXIV_API}?{params}" req = urllib.request.Request(url, headers={"User-Agent": "PaperReading/1.0"}) try: with urllib.request.urlopen(req, timeout=30) as resp: data = resp.read().decode("utf-8") except Exception as e: print(f"Error searching arXiv: {e}", file=sys.stderr) return None ns = {"atom": "http://www.w3.org/2005/Atom", "arxiv": "http://arxiv.org/schemas/atom"} root = ET.fromstring(data) entries = root.findall("atom:entry", ns) if not entries: print(f"No results found on arXiv for title: {title}", file=sys.stderr) return None entry = entries[0] # Try to find PDF link links = entry.findall("atom:link", ns) for link in links: if link.get("title") == "pdf": return link.get("href") ``` The returned URL is later downloaded without validating its scheme or hostname: ```python pdf_url = search_arxiv(source) if not pdf_url: print(f"Error: Could not find paper on arXiv: {source}", file=sys.stderr) sys.exit(1) print(f"Found: {pdf_url}", file=sys.stderr) output = args.output if not output: safe_title = re.sub(r'[^\w\s-]', '', source)[:50].strip().replace(" ", "_") output = f"{safe_title}.pdf" pdf_path = download_pdf(pdf_url, output) ``` ### Technical Analysis The arXiv API endpoint uses plaintext HTTP. Consequently, neither the aut ...[truncated 1792 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Change the API endpoint to `https://export.arxiv.org/api/query`. 2. Require successful TLS certificate validation and do not downgrade to HTTP. 3. Parse every returned PDF URL and require HTTPS with an exact approved arXiv hostname. 4. Validate that the path conforms to the expected arXiv PDF path format. 5. Prefer extracting and validating an arXiv identifier from the response, then construct the final URL from a trusted constant rather than trusting an arbitrary `href`. 6. Validate every redirect destination against the same hostname, scheme, port, and path policy. 7. Verify the response content type, PDF signature, and maximum size before accepting the download. 8. Handle malformed XML and unexpected response structures explicitly so invalid network data fails closed. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:36
Finding
Unpinned Runtime Installation of a Third-Party PDF Dependency<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, line 36 **Vulnerability Type**: Unpinned dependency installation **Risk Level**: Low ### Vulnerable Code ```bash pip install pdfplumber # if not already installed ``` ### Technical Analysis The documented workflow instructs users or agents to install `pdfplumber` without specifying a version, hash, lock file, package index, or controlled environment. The resolved package and its transitive dependency versions can therefore change between executions. Although no typosquatted package name was identified, dynamically installing the latest available package makes the environment non-reproducible and expands supply-chain exposure. A compromised package-index account, malicious future release, altered package source, or compromised transitive dependency could introduce unexpected code. Python package installation can execute build-related code for source distributions. Installed package code also executes later when `read_pdf.py` imports `pdfplumber`. ### Attack Path 1. A user or automated agent follows the installation command in `SKILL.md`. 2. `pip` resolves the current `pdfplumber` release and its transitive dependencies from the environment's configured package index. 3. If the selected package source or resolved release is compromised, malicious build or package code is installed. 4. Build-time code may execute during installation, or package code may execute when `read_pdf.py` imports `pdfplumber`. 5. The malicious dependency operates with the permissions of the Python installation or invoking user. This is a supply-chain exposure rather than evidence that the currently named package is malicious. ### Impact Assessment If dependency resolution is compromised, attacker-controlled package code could run with the privileges of the user or environment performing the installation. Depending on those privileges, the code could access files, environment variables, credentials, and network res ...[truncated 206 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Declare `pdfplumber` and all required dependencies in a version-controlled dependency manifest. 2. Pin an audited exact version instead of resolving the latest release at runtime. 3. Use a lock file and cryptographic hashes, such as `pip --require-hashes`, to verify package artifacts. 4. Install packages only from a trusted, explicitly configured package index. 5. Perform installation in an isolated virtual environment or container with minimal privileges. 6. Prefer prebuilt, reviewed environments over package installation during Skill execution. 7. Regularly scan direct and transitive dependencies for known vulnerabilities and review updates before changing the lock file. 8. Avoid running package installation with administrative or root privileges. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description promises a full paper-reading and note-generation skill, including summarization/analysis and standardized Chinese notes. The supplied code chunk performs only the acquisition stage: local path validation, arXiv title lookup, and PDF download, followed by printing the resolved file path. While PDF download and arXiv search are consistent with part of the description, the primary declared purpose centers on reading papers and generating notes, which this code does not do. Therefore, the description materially overstates the behavior of this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared description presents a higher-level paper reading and note-generation tool that can accept multiple input types (local PDF, arXiv URL, paper title) and produce structured Chinese notes. The supplied code chunk is much narrower: it is a local PDF text/table extractor CLI. Its primary purpose is extraction, not summarization or note generation. Several key declared capabilities are absent, including arXiv lookup/download and title-based retrieval. Additionally, the code has a minor undeclared capability of writing extracted text to an output file. This is a material description-behavior mismatch.

Hidden Instructions

High
Category
Prompt Injection
Content
**动机:** HC 通过增加拓扑复杂度和残差流宽度提升了性能,且不显著增加 FLOPs。但随着模型规模增大,HC 暴露出了严重的稳定性问题。

<!-- 这是一张图片,ocr 内容为: -->
![](https://cdn.nlark.com/yuque/0/2026/png/57569306/1767535158747-34ab19e4-bec9-4f80-bb38-e36d876b77d9.png)

## 2. 核心问题 (Problem Statement)
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
**动机:** HC 通过增加拓扑复杂度和残差流宽度提升了性能,且不显著增加 FLOPs。但随着模型规模增大,HC 暴露出了严重的稳定性问题。

<!-- 这是一张图片,ocr 内容为: -->
![](https://cdn.nlark.com/yuque/0/2026/png/57569306/1767535158747-34ab19e4-bec9-4f80-bb38-e36d876b77d9.png)

## 2. 核心问题 (Problem Statement)
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill instructs downloading PDFs from arXiv and saving both PDFs and notes to user-specified absolute paths, which implies network access and file writes without any declared tool scope or permission boundaries. Missing scope declarations increase the chance of overprivileged execution, unintended filesystem modification, or SSRF-like/network misuse if the runtime grants broad defaults.

Vague Triggers

Medium
Confidence
89% confidence
Finding
An overly broad invocation description can cause the skill to trigger on generic research or summarization requests, unnecessarily activating network downloads and file-writing workflows. In context, this increases the risk of unintended data handling, surprise side effects, and execution in situations where the user did not clearly request external fetches or file output.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The description states that the skill will 'generate structured reading notes in Chinese,' imposing a specific language choice by default. There is no indication that the user may choose another language or opt into Chinese output.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
The key requirements specify 'Language: Chinese with English technical terms preserved,' which is a hard language policy. Because no alternative language option or user opt-in is described, this conflicts with the requirement to avoid forcing a specific language unnecessarily.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The markdown explicitly requires notes to be primarily in Chinese ("中文为主") and does not present this as an optional or user-selectable preference. This is a natural-language locale constraint that can violate language-choice policy when not justified or opt-in.

Natural-Language Policy Violations

Low
Confidence
98% confidence
Finding
This markdown file presents all instructional and descriptive content in Chinese, and there is no indication that users can choose another language or that the document is intended only for a Chinese-language audience. Under the policy criteria, forcing a specific language without user opt-in is a natural-language policy concern.

Static analysis

No suspicious patterns detected.