Back to skill

Security audit

ArXiv Batch Reporter

Security checks for vulnerabilities and agentic risk

Overview

This skill is a coherent arXiv report builder, but its scripts allow report inputs and outputs to escape the intended run directory if arguments are misused or influenced.

Install only for trusted local arXiv workflows. Do not pass template, summary-file, or output-file values from untrusted input, and run it in a workspace where accidental file reads or overwrites outside the run directory would not expose secrets or damage important files.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/collect_summaries_bundle.py:111
Finding

Unrestricted Local File Read Through User-Controlled Path Arguments

Content
View full analysis

Vulnerability Details

File Location: scripts/collect_summaries_bundle.py:25-33, 111-116; scripts/render_collection_report.py:35-47, 146-150, 189-195
Vulnerability Type: Path traversal and arbitrary local file read
Risk Level: Medium

Vulnerable Code

python
# scripts/collect_summaries_bundle.py
parser.add_argument(
    "--summary-file-name",
    default="summary.md",
    help="Per-paper summary file name.",
)
python
# scripts/collect_summaries_bundle.py
summary_path = child / summary_file_name
if not meta_json.exists() and not meta_md.exists():
    continue

metadata = read_json(meta_json) if meta_json.exists() else {}
if not metadata:
    metadata = parse_metadata_md(meta_md)

summary_text = summary_path.read_text() if summary_path.exists() else ""
python
# scripts/render_collection_report.py
parser.add_argument(
    "--template-file",
    default="collection_report_template.md",
    help="Template markdown file name or path. Default: collection_report_template.md",
)
parser.add_argument(
    "--summary-file-name",
    default="summary.md",
    help="Per-paper summary file name. Default: summary.md",
)
python
# scripts/render_collection_report.py
def resolve_template_path(base_dir: Path, template_file: str) -> Path:
    candidate = Path(template_file).expanduser()
    if candidate.is_absolute():
        return candidate
    return base_dir / candidate
python
# scripts/render_collection_report.py
template_path = resolve_template_path(base_dir, args.template_file)
if not template_path.exists():
    print(f"[ERROR] template file not found: {template_path}")
    return 1

lang_code = normalize_language(args.language)
template_text = template_path.read_text()
python
# scripts/render_collection_report.py
summary_path = paper_dir / args.summary_file_name
if not summary_path.exists():
    unresolved.append({"line": str(line_no), "arxiv_id": maybe_id, "reason": f"missing {args.summary_file_name}"})
    o
...[truncated 2031 chars]
Remediation
View remediation

Remediation Suggestions

  • Restrict --summary-file-name to a plain basename by rejecting absolute paths, path separators, . and .. components.
  • Resolve every input path and verify that it remains under its authorized root before reading:
python
def confined_path(root: Path, value: str) -> Path:
    root = root.resolve()
    candidate = (root / value).resolve()
    try:
        candidate.relative_to(root)
    except ValueError as exc:
        raise ValueError(f"Path escapes authorized directory: {value}") from exc
    return candidate
  • Use the individual paper directory as the authorized root for summary files.
  • Use base_dir as the authorized root for templates unless external templates are an explicit requirement.
  • If external templates are required, place that behavior behind a clearly documented opt-in flag and validate the approved external directory.
  • Validate that input paths are regular files rather than directories, devices, or unexpected symbolic-link targets.
  • Return an error before reading any path that fails containment validation.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/render_collection_report.py:213
Finding

Arbitrary File Overwrite Through Unrestricted Output Paths

Content
View full analysis

Vulnerability Details

File Location: scripts/collect_summaries_bundle.py:19-23, 383-384; scripts/render_collection_report.py:39-43, 213-215
Vulnerability Type: Path traversal and arbitrary file overwrite
Risk Level: Medium

Vulnerable Code

python
# scripts/collect_summaries_bundle.py
parser.add_argument(
    "--output-file",
    default="summaries_bundle.md",
    help="Output bundle markdown file under base dir.",
)
python
# scripts/collect_summaries_bundle.py
output_path = base_dir / args.output_file
output_path.write_text(bundle_text)
python
# scripts/render_collection_report.py
parser.add_argument(
    "--output-file",
    default="collection_report.md",
    help="Rendered report file name under base dir. Default: collection_report.md",
)
python
# scripts/render_collection_report.py
output_path = (base_dir / args.output_file).resolve()
rendered = "\n".join(output_lines).rstrip() + "\n"
output_path.write_text(rendered)

Technical Analysis

Both scripts state that output files are written under base-dir, but neither enforces that security boundary. When an absolute path is joined with a Path, the absolute component supersedes the preceding base path. Relative values containing ../ can likewise escape base-dir.

The renderer calls .resolve(), but canonicalization alone does not provide confinement; the resulting path is not checked with relative_to(base_dir) or an equivalent containment test. Path.write_text() then opens the target for writing and truncates an existing file without confirmation.

Exploitation requires control over the --output-file argument and write permission to the selected target. The written content is generated Markdown, so the finding is an arbitrary-location overwrite rather than arbitrary-content code execution.

Attack Path

  1. An attacker controls or influences --output-file.
  2. The attacker supplies an absolute destination or a relative path containing ...[truncated 838 chars]
Remediation
View remediation

Remediation Suggestions

  • Reject absolute output paths and any path containing parent-directory traversal.
  • Canonicalize base_dir and the proposed destination, then enforce containment before writing:
python
base_dir = base_dir.resolve()
output_path = (base_dir / args.output_file).resolve()

try:
    output_path.relative_to(base_dir)
except ValueError:
    print("[ERROR] output path must remain inside base directory")
    return 1
  • Restrict output arguments to plain filenames if subdirectories are unnecessary.
  • Reject symbolic-link destinations that resolve outside the authorized directory.
  • Refuse to overwrite an existing file by default; require an explicit --force option.
  • Write to a temporary file inside base-dir, flush and synchronize it as appropriate, and atomically replace the destination only after validation succeeds.
  • Apply the same validated helper consistently in both scripts.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description says this skill performs the second step of a two-step pipeline: taking a model-written report template and injecting each paper's Brief Conclusion and abs URL by matching arXiv-id placeholders. The actual code does something materially different. It does not read any existing template, does not search for placeholders, and does not perform substitution. Instead, it enumerates paper subdirectories, reads metadata.json/metadata.md and summary.md, extracts a brief conclusion section, and composes a new markdown 'bundle' intended as input for later model synthesis. It also includes full summaries, metadata fields, task meta information, and formatting normalization. No abs URL handling appears in the code. Therefore the actual behavior does not accurately match the declared purpose.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The normalization logic maps a small set of Chinese identifiers to 'zh' and silently forces every other input to English. This creates a natural-language locale constraint without user opt-in or clear support for other requested languages, which can violate language-choice policy expectations.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest says this skill's role is the second step of final report generation: inject each paper's Brief Conclusion and abs URL into a model-written template using arXiv-id placeholders. Instead, this code synthesizes a new markdown bundle from metadata, full summaries, and task instructions for a model, which is a materially different operation from placeholder substitution.

Content

No source excerpt is available for this finding.

Ssd 1

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The script concatenates untrusted paper summary content directly into a bundle that also contains explicit instructions for a downstream model. Because summaries are treated as plain text but will be interpreted in the same prompt context as trusted instructions, a malicious or poisoned summary can inject competing directives, manipulate the model's report, suppress papers, leak prior context, or otherwise subvert the final synthesis.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest narrowly describes injecting each paper's Brief Conclusion and abs URL from summary.md by arXiv-id placeholders. Here the script appends the entire summary.md contents for every paper, which expands the skill from targeted field injection to bulk content aggregation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The normalization logic maps a small set of Chinese variants to "zh" and silently coerces every other language value to English. This is a natural-language policy concern because it forces a specific output language without explicit user opt-in or clear support for broader locale choices.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The script writes the rendered report to the output path before checking whether unresolved placeholders should cause failure or whether zero placeholders were replaced. This can leave a partially rendered or invalid report on disk even when the command exits with an error, which is dangerous in automated pipelines because downstream steps or users may consume the stale/incorrect artifact as if generation succeeded.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The documentation states that the default is English when the language parameter is omitted. This is a natural-language locale policy concern because it imposes a specific language unless the user manually overrides it, rather than clearly requiring explicit language choice by default.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

Line L80 states 'Follow the language requested by workflow parameter (default English)'. This imposes a specific default language choice in natural-language instructions, which can conflict with a language/locale policy requiring user choice or explicit opt-in.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.