Skip to content

Security: ToolsEnabled/toolsenabled-slides

SECURITY.md

Security

ToolsEnabled Slides runs on loopback for one local user. Human controls are not available as MCP tools and require the dashboard session. Open the one-time URL printed at launch within ten minutes. Its secret is a URL fragment, which is never sent in an HTTP request or Referer header. The page removes that fragment and exchanges the secret once by a same-Origin JSON POST. It stores the returned token only in sessionStorage, scoped to the exact origin including its port, and sends it in the X-Slides-Session header for human controls. An independent HttpOnly, SameSite=Strict, host-only cookie is an additional factor; that cookie alone never authorizes a control. No GET returns a controller or session token. Restarting the server invalidates both values. Keep the launch URL private.

A process running as the same user can still act as the user. The dashboard session prevents casual HTTP invocation; it does not isolate hostile programs with access to the user's files, browser or process output. Host and Origin validation and JSON content checks remain in effect.

MCP clients may edit within the workspace, hold their own locks, leave notes and export a new workspace copy. Pause and content protections apply. Import and export paths are confined to the workspace; existing files are not overwritten. State lives in the private .slides-state directory. The server does not send presentations to a model provider.

Report security issues privately to the package maintainer with a minimal reproduction and no private presentation content.

Import parsing limits XML parts to 2 MiB each and 64 MiB total, 50,000 elements and 50,000 attributes per part (2,000,000 elements and attributes per package), depth 128, and a 200:1 ratio for parts over 1 MiB. Must-pass scale fixtures cover 200 designed slides and 200 slides including 180 freeforms with 60 vertices on one slide. Slides’ own render output does not use import volume limits; it retains the depth, DTD, active-content and image-format checks. DTDs, duplicate parts and unsupported ZIP methods are refused. Imported raster canvases are limited to 25 million pixels, GIF/APNG metadata to 10,000 frames, and total first-frame decoding to 100 million pixels per package; generated output is not subject to that import cap. Every part passes one byte-content gate, including fonts and thumbnails: bounded XML, validated PNG/JPEG/GIF, recognized font headers, or structured Windows DEVMODE/Mac plist printer records. Embedded printer plists receive XML caps; only their fixed standard Apple external plist declaration is allowed, without loading a DTD. XML cannot stand in for a font or printer part. Unsupported binaries (including BMP/TIFF/WebP) are refused; image relationships and drawing blips must resolve to validated raster parts. Validation decodes only the first image frame, scans GIF blocks and PNG frame dimensions without decoding later frames, and refuses PNG msOG embedded animation. GIF extension payloads are limited to 1 MiB; comments and later frames are removed from the bytes passed to the pixel decoder. Incomplete later GIF frames are preserved. Private import/render copies carry versioned content-hash receipts; unchanged inputs and image bytes reuse that validation. Changed bytes or missing receipts require fresh validation. Original raster bytes and animation are preserved. Python import/render helpers enforce a 768 MiB address-space ceiling on POSIX and process-memory Job Object ceiling on Windows. Import/export jobs are serialized; background rendering already uses a single running job.

Structural import caps are refusal thresholds, not a guarantee that every deck below them fits in memory. XML trees, decoded pixels and library objects can exhaust the Python helper’s 768 MiB limit sooner; a deck with 58 MiB of XML has reached that ceiling while staying within every structural cap. Such a job fails closed. Reduce image resolution or split a very large deck if import or rendering fails under the memory limit.

Board messages from agents share a 128 KiB pool within the 512 KiB board budget. Old unpinned agent messages are evicted to leave room for human notes, pins and tasks. Only the authenticated dashboard marks human provenance; author strings and request body claims confer no priority. Human notes and pins are never evicted automatically. Human-authored data can still fill the overall budget and require manual cleanup.

Embedded workbook packages, including standard Excel-backed charts, are intentionally refused. The import error directs the user to replace these charts with raster images before import. This restriction does not assert that ordinary workbook-backed charts contain malicious code.

The import pixel caps do not apply to Slides-generated output, so large output images can reach LibreOffice. LibreOffice runs with a fresh private profile and a timeout, but it is not covered by the Python helper’s memory ceiling or an OS filesystem sandbox. It can therefore consume substantially more memory before the timeout. This preserves high-resolution output; reduce output image dimensions when memory use is a concern.

Original raster byte preservation includes EXIF/GPS, XMP, ICC profiles and trailing bytes. Import validation does not scrub private image metadata; PPTX exports retain those bytes. If that metadata must be removed before sharing, strip it explicitly from the source images and replace them before export. Preserving these bytes avoids silent fidelity and animation changes.

Before PDF or PNG conversion, Slides copies the already validated deck into a private staging directory and binds preprocessing to that copy’s SHA-256. Animated GIF parts become content-addressed first-frame PNG parts with matching relationships and content types. APNG animation chunks and PNG msOG chunks are removed without pixel decoding. Signatures, rather than filenames, select projection in every part. Later frames are never decoded by the preprocessing helper, which shares the Python job queue and 768 MiB ceiling. Every GIF decoded by projection is checked against the 25 MP image and 100 MP per-pass decode caps. Static render images and package XML are not subjected to import volume caps. Projected PNGs are cached by source hash and policy version under private state (128 entries, 64 MiB); output hashes and static PNG structure are checked on reuse. Original single-frame GIFs and other media stay unchanged. The projection is used only as Office input and discarded after conversion. Original files, private base media, model state and PPTX exports retain their animation bytes.

There aren't any published security advisories