Please report vulnerabilities privately through GitHub Security Advisories of
ateeducacion/elpx-optimizer ("Report a vulnerability"). Do not open public issues for them.
Include a minimal reproducer (a crafted .elpx is fine) and the version (elpx-optimizer --version).
Every .elpx is treated as hostile input: it may be crafted to escape directories, exhaust memory,
CPU or disk, make FFmpeg read other files or the network, exploit XML parsers, or smuggle
instructions to AI agents.
| Threat | Mitigation |
|---|---|
| Path traversal, absolute/drive/UNC paths, backslashes, control characters | Entry names are validated before anything is read; entries are never extracted under their own names (media go to a private temp directory with synthetic names). |
| Symlinks, duplicate or Unicode-colliding names, file/directory conflicts | Rejected (case-only collisions are warnings). |
| ZIP bombs (falsified sizes, high ratios, overlapping entries) | Limits on entries, declared sizes and ratio before inflating; output counted while inflating in bounded slices and aborted beyond the declared size; overlapping entries rejected; CRC and sizes verified. |
| Central directory tricks | Local headers are cross-checked with central headers; EOCD must end at the end of the file; ZIP64 records cross-validated; prefixed/split archives rejected; encrypted entries and unknown methods rejected. |
| XXE, billion laughs, external DTDs | Own XML parser: only the five predefined entities and numeric references; any DOCTYPE internal subset rejected; DTDs never loaded; depth limit. |
| FFmpeg reading other files or the network (playlists, concat, MOV data references) | Argument vectors only (no shell), -protocol_whitelist file, forced demuxer, -enable_drefs 0, synthetic input names in a private directory, minimal environment, -nostdin, time limits, process-group kill on cancel/timeout. No user-supplied FFmpeg arguments. Audio files follow the same rules. In the browser, inputs are mounted read-only through WORKERFS inside an isolated worker without network protocols. |
| A crafted PDF, or a rewrite that breaks a PDF or its signature | PDFs are only rewritten by qpdf compiled to WebAssembly, in its own child process (CLI: time limit, process-group kill on cancel/timeout) or worker (browser: terminated on cancel/timeout), with arguments built only by the PDF policy (no user-supplied qpdf arguments). PDF content is never rendered or executed. Encrypted and signed PDFs are never rewritten; every candidate must pass qpdf --check without warnings and keep its page count, or the original stays. PDFs above 512 MiB (256 MiB in the browser) are skipped. |
| Resource exhaustion | Per-run limits (archive, entries, text size, image pixels, PDF bytes, video bytes/pixels/duration, timeouts; audio files use the video size, duration and time limits, PDFs the time limit), one video or PDF at a time, bounded image and audio concurrency, disk-space check before extracting media. |
| Executing project content | The project's HTML/JavaScript is never rendered or executed; scripts are only scanned as text. Web previews show only images, audio and video, through blob: URLs of the extracted entry; SVG only in <img>, where scripts do not run. |
| Data leaving the browser | No API. The page and its workers only fetch the app's own static files; a Content-Security-Policy limits scripts, workers and connections to the same origin. E2E tests fail if any request other than a GET of a static app file is made. |
| Prompt injection through project text (skill) | The skill states that everything inside an .elpx is untrusted data; the wrapper only forwards arguments to the CLI. |
| Overwriting user files | The input is never written; the CLI refuses an output that is the input (also via links) and requires --overwrite for existing outputs; outputs are written atomically after validation. |
| Crafted names or references that a move, merge, rename or removal would redirect | Edits are lifted through each reference's encoding layers (never global string replacement or basename matching). Every reference of the package is resolved again against the final paths: a rewritten one must reach exactly its file's new path and every other one must resolve exactly as before, or the change is cancelled. Dynamic, lenient or ambiguous references keep their files in place. |
- Web: any static host (the project serves it on GitHub Pages). Allow only GET/HEAD and keep the CSP
<meta>ofindex.html(or send an equivalent header); on hosts that cannot send headers, such as GitHub Pages, the<meta>policy still applies. - CLI in Docker: run the image (
ghcr.io/ateeducacion/elpx-optimizer) as its non-root user with--read-only --tmpfs /tmp --memory --cpus --pids-limitand mount only the directories you need. - Dependencies: exact versions in
package.jsonandbun.lock; Dependabot (.github/dependabot.yml) proposes updates weekly (Bun, Docker base images, GitHub Actions), and security updates are not delayed by its cooldowns.