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
- Custom web-link action enabled -
weblink_custom_on=trueunder[editor](default false; default templateweblink_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. - A note with an attacker-chosen web-link URL containing shell metacharacters, inside a shared,
synced, or imported
.ctd/.ctb/.ctz. - 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_clickedcommand 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.