Back to skill

Security audit

Arxiv Research Secure

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent arXiv research helper, but it has review-worthy file safety and persistence issues around unsanitized paper IDs and agent-memory logging.

Review this skill before installing. It appears intended for legitimate arXiv research, but use it only in a workspace where unintended PDF or JSON writes would not matter, avoid untrusted paper IDs or notes, and be aware that research metadata and notes may persist in an agent memory path.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • 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
  • 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/arxiv_client.py:113
Finding

Path Traversal Through Unvalidated Paper Identifiers

Content
View full analysis
Optional[dict]: """Get cached paper metadata.""" cache_file = METADATA_DIR / f"{paper_id}.json" if cache_file.exists(): with open(cache_file, 'r') as f: return json.load(f) return None ``` ```python def fetch_paper_pdf(paper_id: str) -> Tuple[bool, str]: """Download paper PDF.""" ensure_cache_dirs() # Clean paper ID clean_id = paper_id.replace("arXiv:", "").strip() # Check cache pdf_path = PAPERS_DIR / f"{clean_id}.pdf" if pdf_path.exists(): return True, str(pdf_path) ``` ### Technical Analysis The `paper_id` argument originates from the command line and is inserted directly into paths under `METADATA_DIR` and `PAPERS_DIR`. Removing the optional `arXiv:` prefix and surrounding whitespace does not prevent path separators, absolute paths, or `..` traversal components. The query validator is not applied to paper IDs used by the `fetch` and `log` commands. Consequently, a value such as `../../target` causes Python's `pathlib` path composition to resolve outside the intended cache directory. The metadata path always receives a `.json` suffix and the PDF path always receives a `.pdf` suffix, which limits—but does not eliminate—the files that can be targeted. No canonical path containment check is performed before reading or writing. ### Attack Path 1. An attacker gains the ability to invoke the skill or influence the paper ID supplied to its CLI. 2. The attacker supplies an identifier containing traversal components, such as `../../some/path/target`. 3. `get_cached_metadata()` constructs a path outside `METADATA_DIR`, ending in `.json`. 4. If that file exists and contains JSON compatible with `ArxivPaper`, the ...[truncated 809 chars]
Remediation
View remediation
str: clean_id = value.removeprefix("arXiv:").strip() if not ARXIV_ID_RE.fullmatch(clean_id): raise ValueError("Invalid ArXiv paper ID") return clean_id ``` 3. Resolve every generated path and verify that it remains beneath the expected directory: ```python def contained_path(base: Path, filename: str) -> Path: base = base.resolve() candidate = (base / filename).resolve() if candidate.parent != base: raise ValueError("Path escapes the configured storage directory") return candidate ``` 4. Apply the same validation consistently to cache reads, metadata writes, cache checks, and PDF downloads. 5. Open newly downloaded files safely and avoid silently overwriting existing files outside the intended lifecycle. 6. Add tests for `../`, absolute paths, backslashes, encoded traversal strings, legacy IDs containing slashes, and identifiers with version suffixes. ]]>

T02 · Agent Memory Poisoning

Error
Location
scripts/arxiv_client.py:281
Finding

Persistent Agent Memory Poisoning Through Unsanitized Research Log Content

Content
View full analysis
Tuple[bool, str]: """Log paper to research journal.""" ensure_cache_dirs() timestamp = datetime.now().strftime("%Y-%m-%d") paper_id = paper.paper_id_clean entry = f""" ## [{timestamp}] {paper.title} - **arXiv ID**: `{paper_id}` - **Authors**: {', '.join(paper.authors[:3])}{' et al.' if len(paper.authors) > 3 else ''} - **Published**: {paper.published[:10]} - **Categories**: {', '.join(paper.categories[:3])} - **Summary**: {paper.summary[:300]}... - **Notes**: {notes} - **PDF**: {'✅ Downloaded' if is_paper_cached(paper_id) else '❌ Not downloaded'} --- """ try: RESEARCH_LOG.parent.mkdir(parents=True, exist_ok=True) with open(RESEARCH_LOG, 'a', encoding='utf-8') as f: f.write(entry) ``` ### Technical Analysis The application appends remote ArXiv metadata and user-provided notes directly to a Markdown file located under the Agent workspace's `memory` directory. These values are not escaped, normalized, or separated from trusted instructions. Untrusted values include: - Paper titles, authors, summaries, categories, and identifiers received from the remote API. - The `notes` command-line argument. - Cached metadata loaded from local JSON files. An attacker can place Markdown headings, separators, links, or instruction-like text in these fields. Because the content is persistent and stored in an Agent memory location, a later session may load it as contextual memory and treat malicious text as instructions rather than inert research data. The summary length limit does not prevent prompt injection, and the `notes` field has no corresponding l ...[truncated 1404 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/arxiv_client.py:82
Finding

Unbounded Network Responses Permit Memory and Disk Resource Exhaustion

Content
View full analysis
Tuple[bool, bytes, str]: """Safely fetch URL with validation.""" try: parsed = urllib.parse.urlparse(url) if parsed.hostname not in ALLOWED_HOSTS: return False, b"", f"FORBIDDEN_HOST: {parsed.hostname} not in allowlist" req = urllib.request.Request( url, headers={ "User-Agent": "ArXivResearchSecure/1.0 (compatible; research tool)" } ) with urllib.request.urlopen(req, timeout=timeout) as response: data = response.read() return True, data, f"OK: {len(data)} bytes" ``` ```python # Download pdf_url = f"{ARXIV_PDF_BASE}/{clean_id}.pdf" success, data, msg = safe_urlopen(pdf_url, timeout=60) if not success: return False, msg # Save try: with open(pdf_path, 'wb') as f: f.write(data) return True, str(pdf_path) ``` ### Technical Analysis `response.read()` reads the entire HTTP response into memory without imposing a maximum byte count. The configured timeout restricts waiting behavior but does not limit the total response size. After buffering the complete response, the PDF flow writes all received bytes to disk. The implementation does not: - Enforce per-response or per-file size limits. - Check `Content-Length` before downloading. - Stream data through a bounded loop. - Enforce a total cache quota. - Validate the response content type. - Verify the PDF file signature. - Clean up partial files because it does not use a temporary, atomic download process. An oversized or unexpected response from an allowed endpoint can therefore cons ...[truncated 1123 chars]
Remediation
View remediation
MAX_PDF_BYTES: raise ValueError("Response exceeds the configured size limit") output.write(chunk) ``` ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (8)

Tainted flow: 'cache_file' from os.environ.get (line 124, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/arxiv_client.py (reported line 125)May include surrounding context.

python
"""Cache paper metadata."""
    ensure_cache_dirs()
    cache_file = METADATA_DIR / f"{paper.paper_id_clean}.json"
    with open(cache_file, 'w') as f:
        json.dump(paper.to_dict(), f, indent=2)

def is_paper_cached(paper_id: str) -> bool:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The fetch operation downloads remote content and stores it persistently on disk without any user-facing disclosure of where data is written or that the action has side effects. In an agent-skill context, hidden persistence is more dangerous because users may expect a search helper, not silent local file creation and disk consumption.

Content

No source excerpt is available for this finding.

Tainted flow: 'pdf_path' from os.environ.get (line 260, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
91% confidence
Finding

The file path uses paper_id-derived clean_id directly in the filename with no sanitization, so a value containing path separators such as '../' can escape PAPERS_DIR and cause arbitrary file writes within the permissions of the process. Although the downloaded content must come from arxiv.org due to host allowlisting, an attacker can still use this to overwrite local files with remote PDF data or create files in unintended locations.

Content

Scanner excerpt · scripts/arxiv_client.py (reported line 273)May include surrounding context.

python
# Save
    try:
        with open(pdf_path, 'wb') as f:
            f.write(data)
        return True, str(pdf_path)
    except Exception as e:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The log operation appends research metadata and free-form notes to a persistent markdown file without prominently warning the user that potentially sensitive notes will be retained. In a research assistant skill, saved notes can include proprietary topics, internal hypotheses, or confidential references, so silent persistence materially increases privacy and data-retention risk.

Content

No source excerpt is available for this finding.

Tainted flow: 'RESEARCH_LOG' from os.environ.get (line 26, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/arxiv_client.py (reported line 303)May include surrounding context.

python
try:
        RESEARCH_LOG.parent.mkdir(parents=True, exist_ok=True)
        with open(RESEARCH_LOG, 'a', encoding='utf-8') as f:
            f.write(entry)
        return True, f"Logged to {RESEARCH_LOG}"
    except Exception as e:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The export function writes accumulated research-log data to a JSON file without warning that a new portable copy of prior activity is being created. This increases the chance of unintended disclosure because exported structured files are easier to exfiltrate, sync, or ingest into other tools than a local internal cache/log.

Content

No source excerpt is available for this finding.

Tainted flow: 'export_path' from os.environ.get (line 369, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/arxiv_client.py (reported line 370)May include surrounding context.

python
}
    
    export_path = WORKSPACE / "arxiv_research_export.json"
    with open(export_path, 'w') as f:
        json.dump(export_data, f, indent=2)
    
    return True, str(export_path)

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The skill advertises automatic download, logging, export, and cache management behaviors that write data to the local workspace, but it does not clearly warn the user about those side effects at the point of use. In an agent setting, this can lead to unintended persistence of PDFs, metadata, summaries, and research logs, which may expose sensitive queries or consume storage without explicit user awareness.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.