Back to skill

Security audit

zotero-skills

Security checks for vulnerabilities and agentic risk

Overview

This Zotero skill has a coherent purpose, but its weak arXiv URL handling can make unintended network requests and upload the response to the user's Zotero account.

Install only if you are comfortable giving the skill a Zotero API key that can add items to your library. Use a least-privilege Zotero key, avoid passing URLs from untrusted sources, and treat ZOTERO_CREDENTIALS as a secret. The publisher should fix the arXiv URL validation before broad use.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/save_paper.py:98
Finding

Weak arXiv URL Validation Enables Server-Side Request Forgery and Unintended Data Upload

Content
View full analysis

Vulnerability Details

File Location: scripts/save_paper.py, lines 98-127
Vulnerability Type: Server-Side Request Forgery caused by insufficient URL and redirect validation
Risk Level: High

Vulnerable Code

python
            # 下载并附加 PDF
            if 'arxiv.org' in args.url:
                try:
                    import urllib.request
                    import tempfile

                    # 将摘要链接转换为 PDF 链接
                    pdf_url = args.url.replace('/abs/', '/pdf/')
                    if not pdf_url.endswith('.pdf'):
                        pdf_url += '.pdf'

                    print(f"正在下载 PDF...")

                    # 设置 User-Agent 以支持下载
                    opener = urllib.request.build_opener()
                    opener.addheaders = [('User-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/120.0.0.0')]
                    urllib.request.install_opener(opener)

                    # 创建安全的文件名
                    safe_title = "".join(c for c in args.title if c.isalnum() or c in (" ", "-", "_")).strip()
                    safe_title = safe_title[:50] # 限制长度
                    safe_filename = f"{safe_title}.pdf"

                    # 使用临时目录,但指定文件名
                    with tempfile.TemporaryDirectory() as td:
                        pdf_path = os.path.join(td, safe_filename)
                        urllib.request.urlretrieve(pdf_url, pdf_path)

                        print(f"正在上传 PDF 附件({safe_filename})...")
                        zot.attachment_simple([pdf_path], item_key)
                        print("PDF 已附加。")

                except Exception as e:
                    print(f"附加 PDF 失败:{e}", file=sys.stderr)

Technical Analysis

The script decides whether a URL is an arXiv resource by checking whether the untrusted string supplied through --url contains the substring arxiv.org. A substring match does not establish that th ...[truncated 2289 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse the input with urllib.parse.urlsplit instead of using substring matching.
  2. Require https and an exact, case-normalized hostname allowlist, such as arxiv.org and explicitly approved arXiv subdomains.
  3. Reject URLs containing credentials, nonstandard ports, fragments, malformed hostnames, or unexpected path formats.
  4. Extract and strictly validate an arXiv identifier, then construct the PDF URL from a fixed trusted base URL rather than modifying the supplied URL.
  5. Resolve the destination hostname and reject loopback, private, link-local, multicast, reserved, and unspecified IP addresses.
  6. Disable automatic redirects or validate the scheme, hostname, port, and resolved IP address after every redirect.
  7. Apply connection and read timeouts, a maximum response size, and a download quota.
  8. Verify the response status, Content-Type, and PDF file signature before uploading the file.
  9. Where possible, enforce an outbound network policy that only permits access to approved Zotero and arXiv endpoints.

T08 · Insecure Dependencies

Warning
Location
scripts/save_paper.py:2
Finding

Unbounded Runtime Dependency Resolution for PyZotero

Content
View full analysis

Vulnerability Details

File Location: scripts/save_paper.py, lines 2-5
Vulnerability Type: Unpinned third-party dependency and unsafe runtime package resolution
Risk Level: Medium

Vulnerable Code

python
# /// script
# requires-python = ">=3.10"
# dependencies = ["pyzotero>=1.6.0"]
# ///

Technical Analysis

The inline dependency declaration specifies only a minimum PyZotero version. The documented execution method uses uv run, which can resolve and install a currently available package release satisfying pyzotero>=1.6.0.

Because there is no exact version constraint, committed lockfile, or package integrity hash, the code executed by the Skill can change after the project has been audited. A future compromised, malicious, or unexpectedly incompatible PyZotero release could therefore be selected without any change to this repository.

Imported Python packages execute code in the same process and security context as the script. PyZotero consequently has access to the process environment, including ZOTERO_CREDENTIALS, as well as the user's filesystem and network permissions.

Attack Path

  1. A malicious or compromised PyZotero release is published under the legitimate package name with a version greater than or equal to 1.6.0.
  2. A user invokes the documented uv run command in an environment without a previously locked dependency set.
  3. The resolver selects and installs the compromised release because it satisfies the lower-bound constraint.
  4. The script executes from pyzotero import zotero.
  5. Malicious package initialization code executes with the permissions of the Skill process.
  6. That code may read ZOTERO_CREDENTIALS, access files available to the user, make network connections, or alter the behavior of Zotero API operations.

This path depends on compromise of the upstream package or its publication channel; the audited repository itself does not contain evidence ...[truncated 681 chars]

Remediation
View remediation

Remediation Suggestions

  1. Pin PyZotero to an exact version that has been reviewed, for example pyzotero==<audited-version>.
  2. Generate and commit a uv.lock file so dependency and transitive-dependency resolution is reproducible.
  3. Use package integrity hashes or an equivalent verification mechanism where supported.
  4. Configure the package installer to use only an approved package index over authenticated TLS, and disable untrusted supplemental indexes.
  5. Review dependency updates before changing the pinned version or lockfile.
  6. Run dependency vulnerability and provenance checks in continuous integration.
  7. Execute the Skill with minimal filesystem and network permissions, and expose ZOTERO_CREDENTIALS only for the duration of the operation.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (4)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill declares executable capabilities that use both environment variables and network access, but it does not define an explicit tool scope such as permissions or allowed-tools. This increases the risk of overbroad execution, because the runtime may grant more access than users expect when the skill reads credentials from the environment and sends data to Zotero over the network.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill instructs users to place a Zotero userid and API key into ZOTERO_CREDENTIALS, but it does not warn that these are sensitive secrets or explain the exposure risks. In a skill that performs networked actions, missing secret-handling guidance can lead to accidental disclosure through logs, shell history, screenshots, error messages, or downstream tooling.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

All user-facing natural-language content in the skill description is written in Chinese, and there is no indication that the skill supports other languages or that Chinese is a required locale for a region-specific purpose. This can constitute a language/locale policy issue because the skill implicitly enforces one language without user opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The argument parser description and help text are written only in Chinese, and the rest of the script's user-facing output follows the same pattern. This imposes a specific language on all users without opt-in or documentation that the skill is intended only for a Chinese-speaking or region-specific context.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.