Back to skill

Security audit

arXiv to Zotero

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its arXiv-to-Zotero purpose, but it needs review because it over-reads a shared secrets file and downloads PDF URLs with insufficient restrictions before uploading attachments.

Review before installing. Use a Zotero API key with the minimum permissions needed, avoid keeping unrelated secrets in ~/.openclaw/.env, and consider disabling summary export. The skill will create Zotero items, attachments, and possibly a collection; the PDF download code should ideally be tightened to arXiv HTTPS hosts with redirect and size limits.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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

Warning
Location
scripts/main.py:179
Finding

Unrestricted PDF URL handling permits SSRF and local file disclosure

Content
View full analysis
Dict[str, Any]: """Download a PDF to a temporary file with curl. curl is used here intentionally so the behavior matches the user's desired workflow: try a real file download first, then decide how to attach it. """ filename = safe_filename_from_url(pdf_url) temp_dir = tempfile.mkdtemp(prefix='arxiv_to_zotero_') target = Path(temp_dir) / filename command = [ 'curl', '--location', '--fail', '--silent', '--show-error', '--max-time', str(max(10, int(timeout))), '--output', str(target), pdf_url, ] try: completed = subprocess.run(command, capture_output=True, text=True, check=False) ``` URL candidates are not restricted to approved schemes or hosts: ```python def resolve_pdf_candidates_from_item_url(item_url: str | None) -> List[str]: """Build likely PDF URLs from the parent item's saved URL.""" candidates: List[str] = [] def add(url: str | None) -> None: value = (url or "").strip() if not value: return value = value.replace("http://", "https://") if value not in candidates: candidates.append(value) raw_url = (item_url or "").strip() if not raw_url: return candidates normalized = raw_url.replace("http://", "https://") if normalized.lower().endswith(".pdf"): add(normalized) return candidates if "arxiv.org/abs/" in normalized: base = normalized.replace("/abs/", "/pdf/") add(base + ".pdf" if not base.endswith(".pdf") else base) add(base[:-4] if base.endswith(".pdf") else base) return candidates if "arxiv.org ...[truncated 2829 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Note
Location
scripts/main.py:471
Finding

Shared environment file is loaded beyond the Skill’s credential requirements

Content
View full analysis
None: """Load KEY=VALUE pairs from a fixed .env file without overriding existing env vars.""" if not path.exists(): return for line in path.read_text(encoding="utf-8").splitlines(): raw = line.strip() if not raw or raw.startswith("#") or "=" not in raw: continue key, value = raw.split("=", 1) key = key.strip() value = value.strip().strip("\"'") if key and key not in os.environ: os.environ[key] = value ``` The Skill later launches curl without a restricted environment: ```python completed = subprocess.run(command, capture_output=True, text=True, check=False) ``` ### Technical Analysis The declared workflow requires only `ZOTERO_API_KEY`. However, `load_simple_env()` reads the complete shared `~/.openclaw/.env` file and copies every parsed entry into the process environment. This grants the Skill access to unrelated credentials stored for other Skills or services. All loaded values are also inherited by the `curl` subprocess because `subprocess.run()` is called without a sanitized `env` argument. No direct code path was found that intentionally prints or sends these unrelated environment values. Nevertheless, making them available to the process and subprocess violates least privilege and increases the consequences of another vulnerability, a compromised executable, diagnostic logging, or environment-sensitive tool behavior. Environment entries can also affect network behavior. For example, proxy-related variables loaded from the shared file may influence HTTP clients or subprocesses when those variables were not already present in the parent environment. ### Attack Path 1. `~/.openclaw/.env` contains `ZOTERO_API_KEY` ...[truncated 1482 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (22)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill description materially understates behavior by omitting undeclared secret access, filesystem output, subprocess use, collection creation, attachment deletion during fallback, and alternate linked-URL attachment behavior. This mismatch can cause users and reviewers to approve a skill under false assumptions while it performs broader and potentially destructive operations against a Zotero library and local environment.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/main.py (reported line 40)May include surrounding context.

python
def fixed_env_path() -> Path:
    return Path("~/.openclaw/.env").expanduser()


ATOM_NS = {"atom": "http://www.w3.org/2005/Atom", "arxiv": "http://arxiv.org/schemas/atom"}

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/main.py (reported line 472)May include surrounding context.

python
# 配置 / Config
# ---------------------------------------------------------------------------
def load_simple_env(path: Path) -> None:
    """Load KEY=VALUE pairs from a fixed .env file without overriding existing env vars."""
    if not path.exists():
        return
    for line in path.read_text(encoding="utf-8").splitlines():

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · README.md (reported line 51)May include surrounding context.

md
1. read [`setup.md`](setup.md)
2. collect the required Zotero settings
3. write non-secret updates back into `config.json`
4. create the setup-state file
5. resume the original request once

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The README instructs the agent that if the user provides Chinese keywords, it should first translate them into English and then construct an English arXiv search query. This imposes a language choice in the skill behavior without presenting it as an option or obtaining user opt-in, which matches the locale/language policy concern.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill invokes shell execution, accesses environment secrets, reads and writes local files, and performs networked API operations, but it does not declare any explicit tool scope or permissions. This weakens review and enforcement boundaries, making it easier for the skill to exercise broader capabilities than a user or host might reasonably expect.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · SKILL.md (reported line 30)May include surrounding context.

md
1. If the user has not already provided them, ask for:
   - topic keywords or phrases
   - a time range
2. Do not ask the user to write an arXiv query.
3. If the user gives keywords in Chinese, translate them into concise, technically accurate English search phrases before building the arXiv query.
4. Build **one** valid arXiv API `search_query` yourself.
5. Run the script once:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The skill reads a fixed ~/.openclaw/.env file automatically, which can surprise users by loading secrets from a sensitive location without explicit runtime disclosure. In a skill ecosystem, implicit secret ingestion increases the chance of unintended credential use and makes provenance of authentication less transparent.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The manifest describes a skill for finding arXiv papers, checking Zotero, and saving imports with tags and PDF attachments. While downloading PDFs is expected, spawning an external program via subprocess is a qualitatively different capability that is not justified by the manifest and could execute whatever binary is resolved as 'curl' in the runtime environment.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/main.py (reported line 188)May include surrounding context.

python
pdf_url,
    ]
    try:
        completed = subprocess.run(command, capture_output=True, text=True, check=False)
    except FileNotFoundError as exc:
        shutil.rmtree(temp_dir, ignore_errors=True)
        return {

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/main.py (reported line 796)May include surrounding context.

python
self.library_type = library_type
        self.api_key = api_key
        self.timeout = timeout
        self.base_url = f"https://api.zotero.org/{library_type}s/{library_id}"
        self.headers = {"Zotero-API-Key": api_key, "Zotero-API-Version": "3"}
        self.force_link_mode = False
        self._resolved_target_collection_key: str | None = None

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The manifest says new imports are saved with parent-item tagging and PDF attachments. In the actual item creation flow, tags are applied only from default_tags plus record.tags, but apply_record_policy overwrites record.tags to default tags only, and attachment creation does not add any tagging metadata; the specific advertised tagging behavior is therefore not implemented as described.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The inline comment says every imported paper receives only the skill-level default tags, and the code enforces that by overwriting record.tags with default tags only. Elsewhere the code includes helpers to derive query-based/storage tags, and the manifest specifically claims parent-item tagging, so this comment reflects an intent that diverges from the stated skill behavior actually advertised to users.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The summary export can write detailed search queries, import decisions, cache strategy, and library activity to an arbitrary configured path on disk without any in-file user disclosure or minimization. In this skill context, those records may reveal research interests, reading history, and Zotero activity, creating avoidable privacy leakage on multi-user systems or through backups/sync.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The setup guide instructs the agent to collect a Zotero API key and write it and other configuration values to local files, but it does not require explicit user consent or provide any warning that secrets and persistent local state will be created. In an agent setting, silent persistence of credentials and config can surprise users, expand the blast radius of compromise, and create unauthorized local modifications.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
83% confidence
Finding

The setup instructs the agent to write persistent values into config.json and maintain a setup-state file, creating cross-session state that can influence future runs. Session persistence is risky when not clearly bounded and user-approved because it can cause hidden behavior changes, retain sensitive operational metadata, and make unintended actions recur across sessions.

Content

Scanner excerpt · setup.md (reported line 39)May include surrounding context.

md
`~/.openclaw/config/skills/arxiv-to-zotero.setup.json`

## Write Back to Config

Write these values into `config.json` in the skill root directory:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The guide states that the script will automatically create a Zotero collection and place imports into it, but it does not warn users that their Zotero library organization will be modified. In a skill that performs external side effects, undisclosed automatic changes to a user's data organization are a real safety issue because they can alter or clutter the library without informed consent.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This instruction forces the final search_query to be in English and rewrites Chinese user input into English terms. That is a natural-language locale policy constraint without user opt-in, which can conflict with organizational language-choice requirements.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The instruction says the final arXiv query must be written in English, which imposes a language constraint. Although this may be technically motivated by arXiv query syntax, the file does not explicitly frame it as a justified system requirement or offer a user-facing language choice, so it qualifies as a natural-language locale policy concern.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

This is a manifest-style JSON file, so vague-trigger checks apply. The file defines operational parameters but provides no explicit trigger phrases, activation conditions, or exclusion conditions, leaving unclear when this skill should activate versus not activate.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/main.py (reported line 633)May include surrounding context.

python
def merge_record(base: PaperRecord, incoming: PaperRecord) -> None:
    for field_name in ["abstract", "year", "venue", "comments", "journal_ref", "doi", "arxiv_id", "url", "pdf_url"]:
        if not getattr(base, field_name) and getattr(incoming, field_name):
            setattr(base, field_name, getattr(incoming, field_name))
    if base.citation_count is None and incoming.citation_count is not None:
        base.citation_count = incoming.citation_count

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/main.py (reported line 634)May include surrounding context.

python
def merge_record(base: PaperRecord, incoming: PaperRecord) -> None:
    for field_name in ["abstract", "year", "venue", "comments", "journal_ref", "doi", "arxiv_id", "url", "pdf_url"]:
        if not getattr(base, field_name) and getattr(incoming, field_name):
            setattr(base, field_name, getattr(incoming, field_name))
    if base.citation_count is None and incoming.citation_count is not None:
        base.citation_count = incoming.citation_count
    base.source_names = sorted(set(base.source_names + incoming.source_names))

Static analysis

No suspicious patterns detected.