PT-2026-59531 · Pypi · Pptagent

Published

2026-07-13

·

Updated

2026-07-13

CVSS v3.1

4.6

Medium

VectorAV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:L

Summary

The save generated slides MCP tool accepts a pptx path argument and writes the generated PPTX file to that path without any workspace restriction or path validation:
python
# pptagent/mcp server.py:288-300
async def save generated slides(pptx path: str):
  """Save the generated slides to a PowerPoint file.

  Args:
    pptx path: The path to save the PowerPoint file
  """
  pptx = Path(pptx path)
  assert len(self.slides), (
    "No slides generated, please call `generate slide` first"
  )
  pptx.parent.mkdir(parents=True, exist ok=True)  # ← creates arbitrary directories
  self.empty prs.save(pptx path)          # ← writes to arbitrary path
The call to pptx.parent.mkdir(parents=True, exist ok=True) creates any intermediate directories, and self.empty prs.save(pptx path) writes a valid PPTX binary (ZIP archive) to the specified path. No is relative to(workspace) check is performed — contrast with download file in deeppresenter/tools/search.py:290, which correctly enforces workspace confinement.
The server changes directory to WORKSPACE (if set) on startup, so relative paths land in the workspace. Absolute paths, however, reach any filesystem location accessible to the server process.

Impact

The concrete attack scenarios include
  1. Cron persistence (root-running server): pptx path = "/etc/cron.d/backdoor" → writes a PPTX ZIP to a path the cron daemon reads; if the ZIP header is misinterpreted, this may corrupt cron or be exploitable depending on parser behaviour.
  2. Dot-file overwrite: pptx path = "/home/user/.bashrc" → overwrites shell init file with a binary blob containing arbitrary content in the PPTX's embedded comments/custom properties.
  3. Directory traversal from workspace: pptx path = "../../.ssh/known hosts.pptx" → escapes workspace entirely.
  4. Denial of service: pptx path = "/dev/sda" writes to a raw device.

Remediation

The potential fix is something like:
python
async def save generated slides(pptx path: str):
  workspace = Path(os.getcwd()).resolve()
  target = Path(pptx path).resolve()
  if not target.is relative to(workspace):
    raise ValueError(f"Access denied: path outside workspace: {target}")
  target.parent.mkdir(parents=True, exist ok=True)
  self.empty prs.save(str(target))

Fix

Found an issue in the description? Have something to add? Feel free to write us 👾

Related Identifiers

PYSEC-2026-2894

Affected Products

Pptagent