OS command injection (RCE) in CherryTree's custom web-link action

With the custom web-link action enabled, CherryTree substitutes a note's link URL into a shell command and runs it via std::system with no escaping, so a link in a shared or synced document executes arbitrary commands when clicked. Independently discovered and reported to the maintainer; fixed in commit 0a0638651. Tracked publicly as CVE-2026-118692.

Severity
High
CVSS
7.8 (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)
Published
2026
Tags
CWE-78, command-injection, rce, coordinated-disclosure, desktop, cherrytree

Overview

CherryTree is a popular open-source (GTK/C++) hierarchical note-taking application whose documents (.ctd / .ctb / .ctz) are routinely shared, synced, and imported. When a user clicks a web link in a note and has the Custom web-link action enabled, CherryTree built a shell command by substituting the note’s link URL into the user’s command template and ran it through std::system with no shell escaping. A note whose web-link URL contained shell metacharacters - delivered inside a shared, synced, or imported document - therefore executed arbitrary commands as the user the moment that link was clicked.

Product CherryTree (desktop, GTK/C++)
Class OS command injection (CWE-78) -> remote code execution
Affected through 1.7.2 (tested on 1.2.0 as shipped by Debian/Kali)
Fixed in commit 0a0638651 (“fixed command injection with custom link actions”), 2026-10-31
Precondition Custom web-link action enabled (weblink_custom_on, default off)
Vector Attacker-controlled web-link URL in a shared / synced / imported document
Severity High - CVSS:3.1 7.8 (AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H; AV:N via a synced document pushes it higher)
Identifier CVE-2026-118692 (see Disclosure for attribution)

Root cause

src/ct/ct_actions_others.cc, CtActions::link_clicked (v1.2.0, around lines 330-337):

if (CtLinkType::Webs == link_entry.type) {
    Glib::ustring clean_weblink = str::replace(link_entry.webs, "amp;", "");   // only "amp;" removed
    if (_pCtConfig->weblinkCustomOn) {
        std::string cmd = fmt::sprintf(_pCtConfig->weblinkCustomAct, clean_weblink.raw()); // "firefox %s &"
        int retr = std::system(cmd.c_str());                                   // URL -> shell, no escaping
    } else {
        fs::open_weblink(clean_weblink);                                       // default path = argv-safe
    }
}

CtMiscUtil::get_link_entry_from_property strips only control characters (below 0x20) from the URL, so every shell metacharacter (; & $ ( ) “ ’ | >) passes through. The URL is taken as split(” “)[1](so it must contain no spaces, trivially worked around with&, ;, or ${IFS}) and must keep an http(s)://scheme prefix. Everything after that reachesstd::system` verbatim.

Preconditions

  1. Custom web-link action enabled - weblink_custom_on=true under [editor] (default false; default template weblink_custom_act = firefox %s &). This is a non-default setting, which is why the base score uses local attack vector; it is a feature a power user turns on.
  2. A note with an attacker-chosen web-link URL containing shell metacharacters, inside a shared, synced, or imported .ctd / .ctb / .ctz.
  3. The user clicks the link.

Proof of concept

Reproduced end to end on v1.2.0. With Custom web-link action enabled, open a document containing a web link whose URL is:

http://x&id>/tmp/CT_RCE_PROOF;echo

Clicking it runs:

std::system("firefox http://x&id>/tmp/CT_RCE_PROOF;echo &")

firefox is backgrounded and id runs as the user, writing /tmp/CT_RCE_PROOF with the current uid/gid. Any command (curl ... | sh, a reverse shell) works the same way.

Same-class variants

The file-link and folder-link custom actions (filelink_custom_act / folderlink_custom_act, via fs::open_filepath / open_folderpath) follow the same fmt::sprintf + std::system pattern and are injectable the same way when their custom actions are enabled.

Suggested fix

Do not build a shell string. Run the user’s program with the URL as a separate argument (for example Glib::spawn_async with an argv vector), never fmt::sprintf + std::system; or at minimum Glib::shell_quote() the URL before substitution. Apply the same to the file-link and folder-link custom actions.

Disclosure

This finding has an unusual multi-party timeline, so I want to record it accurately.

  • 2026-10-23 - I independently discovered the CtActions::link_clicked command injection and reported it privately to the maintainer, Giuseppe Penone (giuspen), by email under coordinated disclosure, with a full write-up and an end-to-end proof of concept. I deferred requesting a CVE, intending to coordinate it after a fix.
  • 2026-10-28 - Another researcher, Mohammed Afnaan Ahmed, reported the same class of issue publicly (CherryTree issue #2945) and was assigned CVE-2026-118692 for the command injection. He also reported a distinct path-traversal / local-file-disclosure bug in CtStorageXmlHelper::_create_image_from_xml, which was assigned CVE-2026-118693.
  • 2026-10-31 - Giuseppe Penone fixed the command injection in commit 0a0638651 (“fixed command injection with custom link actions”), crediting both reporters (@r3vpwnx, @Afnaan-Ahmed, #2945).

So CVE-2026-118692 is credited to Mohammed Afnaan Ahmed, who reported it publicly and requested the identifier; I am credited in the upstream fix commit for the independent report. I am publishing this write-up as a record of the independent discovery and the technical analysis, not as a claim on the CVE. The practical lesson I took from it: on a confirmed, novel finding, request or reserve the identifier at report time rather than deferring it, because an identifier is assigned to whoever requests it first, not to whoever reported first.

For CherryTree users

Update to a build that includes commit 0a0638651. If you use the Custom web-link action (or the file-link / folder-link custom actions), be aware that, before this fix, clicking a link in a document you did not author could run commands; until you are on a fixed build, do not enable those custom actions for documents obtained by share, sync, or import from sources you do not control.


Researcher: Dilanka Kaushal Hewage (r3vpwnx). Found as part of a wider review of command injection in desktop note applications.