CVE-2026-17613: Cross-Team File Takeover in Penpot via import-binfile
| CVE | CVE-2026-17613 |
|---|---|
| Advisory | GHSA-8qqw-wm58-45v7 |
| CERT/CC | VU#241166 |
| Severity | Critical CVSS v3.1 base score 9.9 — 10.0 on instances with open self-registration |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H |
| Weaknesses | CWE-639 — Authorization Bypass Through User-Controlled Key • CWE-862 — Missing Authorization |
| Product | Penpot (open-source design and prototyping platform, Kaleidos) |
| Affected | Penpot from v1.20 onward — self-hosted Community Edition and Penpot Cloud |
| Fixed in | No fix available Unpatched at time of publication |
| Reported by | Louis Sanchez — Voke Cyber |
| Coordination | Reported to the vendor 2026-05-28; CERT/CC coordinated (VU#241166) after 21 days of no response. |
No patch available
At the time of publication there is no fixed release. The flaw was re-verified as live at develop HEAD on 2026-07-27. Operators of self-hosted Penpot should apply the interim mitigations below and treat every account on the instance as having write access to every file on it.
Summary
Penpot's file-import API (the import-binfile RPC command) accepts an optional file-id parameter meaning "import into this existing file instead of creating a new one." The handler validates that the caller can edit their own project-id, which they obviously can, but it never checks that the supplied file-id belongs to them. Any authenticated user can therefore point that parameter at any file on the entire instance and overwrite it. Worse, the in-place import also re-parents the file into the attacker's own project, so the attacker becomes its legitimate owner in the eyes of every other endpoint. One request destroys a victim's design, transfers ownership, and opens it up to be read, exported, and re-shared. On instances with open self-registration — the default — the attacker needs no prior relationship to the target at all.
Status at publication
This advisory is being published without a vendor fix. The report went to Penpot on 2026-05-28 and to CERT/CC on 2026-06-18 after three weeks of silence. CERT/CC opened case VU#241166 and CVE-2026-17613 was assigned on 2026-07-06. As of today the code is still vulnerable at develop HEAD and no patched release exists.
Because there is no patch, this advisory describes the flaw and the shape of the request that triggers it — which is what operators need in order to assess their exposure and apply the interim mitigations below. The drop-in exploit script and the payload recipe are being held back until a fixed release ships.
Background: what Penpot is and why the blast radius matters
Penpot is a widely used open-source design and prototyping platform, the open alternative to tools like Figma. It runs both as a hosted service and self-hosted by tens of thousands of teams. Users organize their work into files, inside projects, inside teams, and the whole point of that structure is that one team's designs are private to that team.
Design files are not low-value data. A single Penpot file can hold an unreleased product's entire interface, brand assets, marketing material ahead of launch, or mockups containing real customer names and internal identifiers in placeholder text. Agencies and consultancies commonly use one Penpot instance with a team per client, and rely on team separation to keep those clients confidential from one another. This flaw dissolves that boundary from any account on the instance.
Root cause: an unchecked caller-supplied file identifier
Importing a Penpot file goes through the import-binfile RPC command. When it is called with a file-id, it performs an "in-place" import, overwriting an existing file rather than creating a new one.
The handler validates edit permission on the caller's project-id — a project the caller obviously owns, since they supplied it. The problem is that the caller-supplied file-id is bound straight through and used without any matching permission check.
// import-binfile handler (simplified) // The caller's own project is checked... check-edition-permissions! (project-id) // ...but the caller-supplied file-id is used as-is. // No check-edition-permissions! on file-id. No ownership predicate.
The detail that makes this one interesting is that the very same source file gets it right on the other side. The export-binfile command validates read permission on its file-id before handing anything back. The permission function exists, it is already imported, and it is used correctly a few dozen lines away. The import path simply omits the call.
From there the in-place import fetches the target file by id alone, with no ownership predicate on the query, enables overwrite mode, writes the attacker's content into the victim's file row, and sets the file's project to the attacker's project. The final database update is keyed only on the attacker-supplied file id. The net effect of one request is that the victim's file now contains attacker data and belongs to the attacker's team.
Why the re-parenting is the important half
That last step is what turns this from vandalism into takeover. Most write-side IDOR bugs stop at "the attacker corrupted your data." This one also rewrites the row's owning project, and because Penpot resolves file permissions by walking from the file to its project to its team, the attacker becomes the legitimate owner in the eyes of every other endpoint. The file disappears from the victim's project and appears in the attacker's, where it can be opened, exported, duplicated, and shared. The victim cannot even reach the file to attempt a restore.
A related issue in the same spirit
The report also bundled a lower-severity issue with the same root pattern: the real-time WebSocket channel lets a client subscribe to a file or team topic using a client-supplied id with no permission check, allowing live exfiltration of collaborative edits and shared-library changes for any file whose id the attacker knows. It shares the underlying mistake — trusting a client-supplied identifier without re-checking it — and one disciplined patch closes both.
Exploitation
Prerequisite: any authenticated Penpot account with its own project. On a default install, an attacker can simply self-register.
File identifiers are random and not guessable, so the natural objection is that this is hard to aim. It is not. File ids leak through completely normal use, and two of the routes need no mistake from the victim at all:
- Public share links. When a designer clicks Share, Penpot produces a URL with the file id sitting in it in plaintext. People send those to clients and coworkers constantly. Whoever receives the link already has what the attack needs.
- Shared-library enumeration. Any user who has ever linked a shared design-system library can read that library file's id from a normal library-listing call. No error, no trace — and this is the route that reaches an entire organization's design system rather than one file.
- Workspace and viewer URLs. These embed the file id, so it appears in every screenshot, support ticket, Slack paste, issue screencap, and screen share.
With a target file id in hand, the attack is a single import call: project-id set to a project the attacker owns, file-id set to the victim's file, and a valid single-file Penpot archive as the payload.
POST /api/rpc/command/import-binfile Cookie: <attacker session> Content-Type: multipart/form-data project-id = <attacker's own project> // checked file-id = <victim's file> // NOT checked file = <valid .penpot archive>
The server overwrites the victim's file with the attacker's content and moves the file into the attacker's project. Afterward the file has vanished from the victim's project and appears in the attacker's, where it can be opened, exported, duplicated, and shared.
A note on attack complexity
This is worth dwelling on for anyone who scores their own findings. During review, the exploitability of the file id came up as a possible reason to rate attack complexity as High. The distinction that settled it: High complexity is meant for conditions the attacker cannot arrange on their own. Receiving a share link and listing shared libraries are ordinary actions any account can take unilaterally, so the complexity stays Low.
Impact
- Data destruction — the victim's file contents are overwritten with attacker data.
- Persistent takeover — the file is re-parented to the attacker's team, so the attacker owns it and can read, edit, export, and share the victim's design going forward.
- Library poisoning — if the hijacked file was a shared design library, every file across every other team that syncs from it starts pulling attacker-controlled colors, components, and typography, quietly poisoning downstream designs.
- Cross-team scope — the attacker has no authorization on the victim's team; the action moves a resource entirely out of that security boundary, which is what carries the severity to Critical.
There is no cross-team trust required and, on a default install, no prior relationship to the victim at all.
CVSS vector rationale
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H — base score 9.9
- AV:N — The Penpot API is reachable over the network.
- AC:L — A single request. Obtaining a file id is an ordinary unilateral action, not a condition beyond the attacker's control.
- PR:L — Any authenticated account with its own project. On instances with open self-registration this becomes PR:N and the score rises to 10.0.
- UI:N — No victim interaction. The attacker drives the exploit entirely from their own session.
- S:C (Scope Changed) — A compromise scoped to the attacker's own team affects resources owned by other teams on the instance.
- C:H — After re-parenting, the attacker can read and export the victim's full design file.
- I:H — The victim's file contents are overwritten with attacker-chosen data, and shared libraries propagate that content downstream.
- A:H — The original design is destroyed and the victim loses all access to the file, with no in-product recovery path.
Remediation and interim mitigations
There is no fixed release to upgrade to yet, so until one ships:
- Disable open self-registration. This does not fix the bug, but it removes the sign-up-and-attack path and takes the score from 10.0 down to 9.9, since an attacker then needs an account someone gave them.
- Treat every account on the instance as having write access to every file on it. If your deployment relies on team separation for client confidentiality, that assumption does not currently hold. Consider separate instances for genuinely sensitive client work.
- Audit for prior exploitation. Look for file rows whose owning project changed without a corresponding user action, and for edits written by a profile that is not a member of the file's team. An in-place import does not leave a normal change row behind, so a file whose project lineage no longer matches the accounts that historically edited it is the signal to chase.
- Keep your own backups. Because the victim loses access to the file entirely, in-product recovery is not available. Regular export-binfile snapshots of important files, stored outside Penpot, are the practical fallback.
The fix
The correct code-level fix is a permission check that already exists elsewhere in the codebase. When an in-place import is requested, require edit permission on the supplied file-id, not just on the caller's project. Then, separately, reject any in-place import that would move the file into a different project than the one it currently belongs to.
The second assertion matters more than it looks. It blocks the re-parenting leg on its own, so even if a future change loosens the per-file check, the bug cannot come back as a takeover.
Disclosure timeline
- 2026-05-27 — Found during a source review of the Penpot backend and confirmed against develop HEAD.
- 2026-05-28 — Reported privately through GitHub's advisory process (GHSA-8qqw-wm58-45v7) and to the vendor's published security contact.
- 2026-06-18 — No vendor response after 21 days. Confirmed no silent patch had landed. Reported to CERT/CC for coordination.
- 2026-06-29 — CERT/CC raised the attack-complexity question; answered the same day.
- 2026-07-06 — CVE-2026-17613 assigned, CERT/CC case VU#241166 opened.
- 2026-07-27 — Re-verified still vulnerable at develop HEAD; proof-of-concept package provided to CERT/CC.
- 2026-08-04 — Public disclosure. No vendor fix available.
References
- CVE record: CVE-2026-17613
- CERT/CC vulnerability note: VU#241166
- GitHub Security Advisory: GHSA-8qqw-wm58-45v7
- Penpot project: penpot.app • github.com/penpot/penpot
- Blog post (story and disclosure narrative): The Import That Took the File With It: CVE-2026-17613 in Penpot
Found and reported by Louis Sanchez, Founder & Principal Security Consultant at Voke Cyber (OSCP, OSWA, CISSP, CCSK). Prior advisories: CVE-2026-16751 (Ente), CVE-2026-16624 (Cal.com), CVE-2026-15630 (Casdoor), CVE-2026-39878 (Chamilo LMS), CVE-2026-48742 (Coolify), CVE-2026-35198 (HeyForm), CVE-2026-48507 (Snipe-IT), and CVE-2026-42318 (GLPI).
We find the bugs tools miss
A parameter that is optional, undocumented, and unchecked does not show up in a dependency scan. It surfaces when someone reads the handler and asks which of these caller-supplied ids anyone actually verified. That is how we approach web application and API testing for clients across the Charlotte, NC area and nationwide.
Get a Quote