Skip to content

Security: jimman0I/prune

SECURITY.md

Security

Prune uninstalls programs, deletes files, writes to the registry and asks Windows for administrator rights. A bug in it costs more than a bug in most desktop apps, so this page says where to send one and what is worth sending.

Reporting a vulnerability

Use GitHub's private reporting, not a public issue: Report a vulnerability. That opens a private thread visible only to you and the maintainer, so nothing is disclosed while it is still exploitable.

If private reporting is unavailable to you for any reason, open a public issue saying only that you have found a security problem and asking for a private channel. Do not include the details.

Please include, if you have them:

  • What an attacker gains — read a file they could not read, delete one they could not delete, run code, get administrator.
  • Whether it needs the attacker to already be running code on the machine as that user. If it does, say so; it changes the severity a great deal (see below).
  • The steps, and the Prune version from Settings.

What to expect

Prune is written by one person. There is no security team and no on-call rotation, so an honest answer rather than a flattering one: expect a first reply within about a week. If a report is valid and I can fix it, a release follows as soon as it is ready and the advisory is published with it. If I cannot fix it, or decide not to, I will say which and why rather than leaving the thread to go quiet.

Credit in the advisory and the changelog if you want it, and not if you do not.

Supported versions

The latest release only. Prune is a single-developer project with no long-term support branches; a fix ships in the next version, and older ones are not patched.

What is in scope

These are the parts where a bug does real damage. All of them are things Prune does on purpose, so the vulnerability would be in the guard rather than in the capability.

  • The local API. The backend listens on 127.0.0.1:3101, and loopback is not the protection it sounds like: every web page the user has open can reach it too. A localOnly guard checks the Host header before the body is parsed, and CORS reflects rather than wildcards. Anything that gets a request past that guard is in scope — it would let any site the user is browsing list their installed programs, toggle startup entries, run a Deep Clean, or empty the quarantine and destroy every undo the app holds.
  • Path handling in the cleaner and the quarantine. Deep Clean and the leftover sweep both delete by rule, and every deletion is guarded by an exclusion list that no rule may override (lib/cleanGuards.js). A path that escapes those guards, or a quarantine restore that writes outside the directory it was captured from, is in scope.
  • Command construction. Uninstalling runs the program's own registered uninstall command — or the quiet variant the vendor published beside it — and several features shell out to powershell.exe and reg.exe. An injection that turns registry-derived or user-supplied text into an extra command is in scope.
  • Store app removal. A package name is interpolated into a Remove-AppxPackage script after being checked against a strict character class, and packages Windows marks NonRemovable are refused before the command is built. Anything that gets a crafted name past that check, or removes a package Windows protects, is in scope.
  • Elevation. Some operations raise a UAC prompt. Anything that gets more done under that elevation than the prompt described, or that elevates without one, is in scope.
  • Quarantine integrity. Removed files are moved and registry keys exported so a bad match can be undone. Anything that makes a restore write the wrong thing, or to the wrong place, is in scope.

What is out of scope

Not because these do not matter, but because they are known, deliberate, or not something a report can change:

  • The unsigned installer and the SmartScreen warning. Prune ships without a code-signing certificate, on purpose, and the README explains what that warning means and how to verify the download instead. This is a documented limitation, not a finding.
  • Anything that assumes the attacker already runs code as that user. If they do, they can delete the same files Prune can, without Prune. A report needs to show a gain over what the attacker already has.
  • Antivirus flagging the binary. Expected for an unsigned program that deletes files in bulk and spawns PowerShell. Tell me anyway — it is useful — but as an issue, not a vulnerability.
  • Dependency advisories with no path to exploitation here. A CVE in a package is a starting point, not a finding. Say how it is reached through Prune.
  • Findings from automated scanners, pasted unread. Anything that cannot say what an attacker gains will be closed.

What Prune does not do

Stated because it removes a whole category of question, and because it is verifiable rather than a promise: nothing Prune does leaves the machine unless you turn on the update check. There is no telemetry, no crash reporting and no analytics.

The only HTTP in the app is the window talking to its own backend on 127.0.0.1:3101, with one opt-in exception: Settings → Check for updates, off by default — the installer asks too, on a fresh install, with the box unticked. When it is on, the backend asks https://api.github.com/repos/jimman0I/prune/releases/latest at most once a day, sending a User-Agent and nothing else. The link it shows is built from the checked version number, never taken from GitHub's reply, and the endpoint that opens it takes no address from its caller.

Installing an update is a separate step, and it is the user's. When the check has found a newer release, a button appears at the bottom of the side bar; only clicking it — or turning on Install updates automatically, also off by default — makes the Electron shell's electron-updater download the installer from that GitHub release, check it against the SHA-512 in the release's latest.yml, and run it. The window can only name the version it showed: where the update comes from is fixed in the build, and any version other than the one shown is refused (electron/updater.cjs).

What that trusts, plainly: the GitHub release. Prune is unsigned, so nothing checks who built the new installer — the SHA-512 proves the file matches latest.yml, and both come from the same release. That is the same trust as downloading it from the release page by hand. Every file on a release carries a build attestation you can check yourself; see the README.

Grep for it: backend/src/services/updateCheck.js and electron/updater.cjs (through electron-updater) are the only code that opens a connection to a host that is not loopback, and neither does anything while the update check is off. The sole other non-loopback URL in the Electron shell is http://localhost:5174, the Vite dev server, used only when running from source.

There aren't any published security advisories