Back to skill

Security audit

Zotero PDF Local Import

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Zotero PDF importer, but it requires an automatic unpinned Python package install that can change the user's environment.

Install only if you are comfortable with the agent reading the PDFs or folders you name, sending those PDFs to your local Zotero connector, and potentially reading your Zotero database for the check command. Before use, prefer installing dependencies yourself in a dedicated virtual environment with a pinned `requests` version, and do not let the agent run `--auto-install-deps` without explicit approval.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T08 · Insecure Dependencies

Warning
Location
scripts/zotero_tool.py:237
Finding
Mandatory Runtime Installation of an Unpinned Dependency<![CDATA[ ## Vulnerability Details **File Location**: `scripts/zotero_tool.py:237-249` **Related Locations**: `scripts/requirements.txt:1`, `SKILL.md:34-42`, `SKILL.md:57-75` **Vulnerability Type**: Supply-chain exposure through mutable dependency resolution **Risk Level**: Medium ### Vulnerable Code ```python req_mod = requests if req_mod is None: print("dep_requests=missing") if args.auto_install_deps: print("dep_requests=installing") r = subprocess.run([sys.executable, "-m", "pip", "install", "requests>=2.31.0"], capture_output=True, text=True) if r.returncode != 0: print("dep_requests=install_failed") print((r.stderr or r.stdout or "").strip()[:500]) return 10 import importlib req_mod = importlib.import_module("requests") print(f"dep_requests=installed version={getattr(req_mod, '__version__', 'unknown')}") ``` The dependency declaration is also unpinned: ```text requests>=2.31.0 ``` The Skill instructions make this installation path part of the mandatory execution flow: ```text Required execution flow: 1. Run `doctor --auto-install-deps` 2. If successful, run `import` ``` ### Technical Analysis The `doctor --auto-install-deps` command invokes pip to retrieve and install any version of `requests` satisfying `>=2.31.0`. The package version is not pinned, no artifact hashes are verified, and no trusted package index is explicitly selected. Consequently, the code executed by the Skill can differ from the code originally audited. If dependency resolution is influenced by a compromised package repository, compromised upstream release, malicious mirror, or local pip configuration, pip may install an attacker-controlled artifact. A source distribution can involve build-backend code during installation, and the installed package is subsequently imported with `importlib.import_module("requests")`. This behavior is not required for Zotero's local import semantics. D ...[truncated 1601 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove automatic dependency installation from the mandatory execution flow. Detect missing dependencies and require explicit user approval before modifying the Python environment. 2. Pin dependencies to reviewed exact versions rather than using an open-ended lower bound: ```text requests==<reviewed-version> ``` 3. Generate a lock file containing cryptographic hashes and install with hash verification: ```bash python -m pip install --require-hashes -r requirements.lock ``` 4. Restrict installation to a trusted, explicitly configured package index and avoid inheriting untrusted pip configuration where feasible. 5. Install dependencies in an isolated virtual environment rather than modifying the agent's or system's shared Python environment. 6. Prefer a deployment process that installs and verifies dependencies before the Skill is invoked. The runtime `doctor` command should only validate their presence and approved versions. 7. If runtime installation must remain available, display the package source and resolved version, obtain user confirmation, reject unexpected versions or indexes, and verify artifacts against maintained hashes. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (11)

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill instructs the agent to execute shell commands, access local files, contact a localhost HTTP service, and potentially install dependencies, but it does not declare any explicit tool scope or allowed-tools/permissions metadata. That mismatch increases the chance an agent grants broader capabilities than users expect, enabling unintended local file access or command execution in a workflow that handles user-supplied paths and network parameters.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if sys.platform.startswith("win"):
        os.startfile(url)  # type: ignore[attr-defined]
    elif sys.platform == "darwin":
        subprocess.run(["open", url], check=True)
    else:
        subprocess.run(["xdg-open", url], check=True)
    time.sleep(1.2)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
elif sys.platform == "darwin":
        subprocess.run(["open", url], check=True)
    else:
        subprocess.run(["xdg-open", url], check=True)
    time.sleep(1.2)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
Automatic package installation is beyond the stated purpose of importing PDFs into Zotero and materially increases the trust boundary of the skill. It enables network-based dependency retrieval and environment mutation, which can expose users to supply-chain compromise, unexpected package resolution, or breaking changes in the local Python setup.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The tool can run pip and modify the active Python environment without presenting a clear safety warning about side effects, trust implications, or scope of changes. In a local-agent context, silent environment mutation is dangerous because users may not realize the command downloads and installs executable third-party code.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print("dep_requests=missing")
        if args.auto_install_deps:
            print("dep_requests=installing")
            r = subprocess.run([sys.executable, "-m", "pip", "install", "requests>=2.31.0"], capture_output=True, text=True)
            if r.returncode != 0:
                print("dep_requests=install_failed")
                print((r.stderr or r.stdout or "").strip()[:500])
Confidence
94% confidence
Finding
The doctor command can modify the user's Python environment by invoking pip to install packages, which exceeds the core import/check functionality and creates a supply-chain and environment-integrity risk. Even though it uses a fixed package name and no shell, it still performs code-fetching and package installation from configured indexes, which can introduce untrusted code or unexpected system changes.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if sys.platform.startswith("win"):
            print("url_opener=ok method=os.startfile")
        elif sys.platform == "darwin":
            r = subprocess.run(["which", "open"], capture_output=True, text=True)
            print("url_opener=ok method=open" if r.returncode == 0 else "url_opener=fail missing=open")
            ok = ok and (r.returncode == 0)
        else:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print("url_opener=ok method=open" if r.returncode == 0 else "url_opener=fail missing=open")
            ok = ok and (r.returncode == 0)
        else:
            r = subprocess.run(["which", "xdg-open"], capture_output=True, text=True)
            print("url_opener=ok method=xdg-open" if r.returncode == 0 else "url_opener=fail missing=xdg-open")
            ok = ok and (r.returncode == 0)
    except Exception as e:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.31.0
Confidence
95% confidence
Finding
The dependency is specified as `requests>=2.31.0`, which allows installation of any future version and does not ensure reproducible builds. Unpinned dependencies increase supply-chain risk because different environments may resolve to different releases, including versions with regressions or newly introduced vulnerabilities.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding
Because the manifest does not pin `requests` to a specific version, it is impossible to verify whether the installed package version avoids the listed advisories. In a skill that communicates with a local HTTP service on `127.0.0.1`, dependency behavior matters; leaving the version unconstrained weakens assurance that a safe, reviewed release will be used.

Natural-Language Policy Violations

Low
Confidence
97% confidence
Finding
The message hardcodes Chinese output ('我的文库') for the default target selection. This imposes a specific language in user-facing behavior without any opt-in or locale selection, which matches the language/locale policy violation criteria.

Static analysis

No suspicious patterns detected.