From 12a623f7d7e9a628b62c1ceac6dbb69d946da4a1 Mon Sep 17 00:00:00 2001 From: Siddhant Shekhar Date: Sat, 1 Nov 2025 19:55:10 +0530 Subject: [PATCH] Sanitize username to prevent single-instance detection failure (#12559) --------- Co-authored-by: Jonathan White --- src/core/Tools.cpp | 23 +++++++++++++++++++++++ src/core/Tools.h | 1 + src/gui/Application.cpp | 23 ++++++++++++----------- tests/TestTools.cpp | 23 +++++++++++++++++++++++ tests/TestTools.h | 2 ++ 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 1d0bb1ba0..4ac9edfc0 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -426,6 +426,29 @@ namespace Tools return filename.trimmed(); } + QString cleanUsername() + { +#if defined(Q_OS_WIN) + QString userName = qgetenv("USERNAME"); + if (userName.isEmpty()) { + userName = qgetenv("USER"); + } +#else + QString userName = qgetenv("USER"); + if (userName.isEmpty()) { + userName = qgetenv("USERNAME"); + } +#endif + // Sanitize username for file safety + userName = userName.trimmed(); + // Replace <>:"/\|?* with _ + userName.replace(QRegularExpression(R"([<>:\"\/\\|?*])"), "_"); + // Remove trailing dots and spaces + userName.replace(QRegularExpression(R"([.\s]+$)"), ""); + + return userName; + } + QVariantMap qo2qvm(const QObject* object, const QStringList& ignoredProperties) { QVariantMap result; diff --git a/src/core/Tools.h b/src/core/Tools.h index ee5ef612e..a9a645846 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -48,6 +48,7 @@ namespace Tools QString envSubstitute(const QString& filepath, QProcessEnvironment environment = QProcessEnvironment::systemEnvironment()); QString cleanFilename(QString filename); + QString cleanUsername(); template QSet asSet(const QList& a) { diff --git a/src/gui/Application.cpp b/src/gui/Application.cpp index b0923bbb5..4609d58fc 100644 --- a/src/gui/Application.cpp +++ b/src/gui/Application.cpp @@ -20,6 +20,7 @@ #include "Application.h" #include "core/Bootstrap.h" +#include "core/Tools.h" #include "gui/MainWindow.h" #include "gui/MessageBox.h" #include "gui/osutils/OSUtils.h" @@ -31,6 +32,7 @@ #include #include #include +#include #include #include @@ -63,20 +65,19 @@ Application::Application(int& argc, char** argv) registerUnixSignals(); #endif - QString userName = qgetenv("USER"); - if (userName.isEmpty()) { - userName = qgetenv("USERNAME"); - } - QString identifier = "keepassxc"; - if (!userName.isEmpty()) { - identifier += "-" + userName; + // Build identifier + auto identifier = QStringLiteral("keepassxc"); + auto username = Tools::cleanUsername(); + if (!username.isEmpty()) { + identifier += QChar('-') + username; } #ifdef QT_DEBUG - // In DEBUG mode don't interfere with Release instances - identifier += "-DEBUG"; + // In DEBUG mode don’t interfere with Release instances + identifier += QStringLiteral("-DEBUG"); #endif - QString lockName = identifier + ".lock"; - m_socketName = identifier + ".socket"; + + QString lockName = identifier + QStringLiteral(".lock"); + m_socketName = identifier + QStringLiteral(".socket"); // According to documentation we should use RuntimeLocation on *nixes, but even Qt doesn't respect // this and creates sockets in TempLocation, so let's be consistent. diff --git a/tests/TestTools.cpp b/tests/TestTools.cpp index a373e1523..5e8217842 100644 --- a/tests/TestTools.cpp +++ b/tests/TestTools.cpp @@ -428,3 +428,26 @@ void TestTools::testIsTextMimeType() QVERIFY(!Tools::isTextMimeType(noText)); } } + +// Test sanitization logic for Tools::cleanUsername +void TestTools::testCleanUsername() +{ + // Test vars + QFETCH(QString, input); + QFETCH(QString, expected); + + qputenv("USER", input.toUtf8()); + qputenv("USERNAME", input.toUtf8()); + QCOMPARE(Tools::cleanUsername(), expected); +} + +void TestTools::testCleanUsername_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expected"); + + QTest::newRow("Leading and trailing spaces") << " user " << "user"; + QTest::newRow("Special characters") << R"(user<>:"/\|?*name)" << "user_________name"; + QTest::newRow("Trailing dots and spaces") << "username... " << "username"; + QTest::newRow("Combination of issues") << R"( user<>:"/\|?*name... )" << "user_________name"; +} diff --git a/tests/TestTools.h b/tests/TestTools.h index 728849bd3..dbf74ef41 100644 --- a/tests/TestTools.h +++ b/tests/TestTools.h @@ -41,6 +41,8 @@ private slots: void testGetMimeType(); void testGetMimeTypeByFileInfo(); void testIsTextMimeType(); + void testCleanUsername(); + void testCleanUsername_data(); }; #endif // KEEPASSX_TESTTOOLS_H