summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorPatrick J Volkerding <volkerdi@slackware.com>2022-02-01 08:27:47 +0000
committerEric Hameleers <alien@slackware.com>2022-02-01 17:59:49 +0100
commitba74260aeb31600cdee80934088739a8e9869f21 (patch)
tree74867bc39b75da76d60eff7792cef4e437237daa /source
parentbd42aca52dc7532946a93fb3354a8454c4adfe94 (diff)
downloadcurrent-ba74260aeb31600cdee80934088739a8e9869f21.tar.gz
Tue Feb 1 08:27:47 UTC 202220220201082747
kde/kate-21.12.1-x86_64-2.txz: Rebuilt. Fix missing validation of binaries executed via QProcess. Thanks to Heinz Wiesinger. For more information, see: https://kde.org/info/security/advisory-20220131-1.txt https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-23853 (* Security fix *)
Diffstat (limited to 'source')
-rwxr-xr-xsource/a/rpm2tgz/rpm2tgz.SlackBuild12
-rw-r--r--source/kde/kde/build/kate2
-rw-r--r--source/kde/kde/patch/kate.patch7
-rw-r--r--source/kde/kde/patch/kate/361dd43e42994829dbdb35e78fb7698d27cbb0e2.patch87
-rw-r--r--source/kde/kde/patch/kate/6fc3bf6e5bd540e842e32c4a959c2158c8573be5.patch71
-rw-r--r--source/kde/kde/patch/kate/7e08a58fb50d28ba96aedd5f5cd79a9479b4a0ad.patch918
-rw-r--r--source/kde/kde/patch/kate/92a9c65e30b4b63b8b116eb5c8dcb1e1a2d867bc.patch39
-rw-r--r--source/kde/kde/patch/kate/c5d66f3b70ae4778d6162564309aee95f643e7c9.patch124
8 files changed, 1254 insertions, 6 deletions
diff --git a/source/a/rpm2tgz/rpm2tgz.SlackBuild b/source/a/rpm2tgz/rpm2tgz.SlackBuild
index 00ebb5bc..7aae64cb 100755
--- a/source/a/rpm2tgz/rpm2tgz.SlackBuild
+++ b/source/a/rpm2tgz/rpm2tgz.SlackBuild
@@ -24,7 +24,7 @@ cd $(dirname $0) ; CWD=$(pwd)
PKGNAM=rpm2tgz
VERSION=1.2.2
-BUILD=${BUILD:-5}
+BUILD=${BUILD:-6}
# Automatically determine the architecture we're building on:
if [ -z "$ARCH" ]; then
@@ -78,10 +78,12 @@ cat $CWD/rpm2targz > $PKG/usr/bin/rpm2targz
zcat $CWD/patches/0007-Add-support-for-.txz-packages-and-rpm2txz-symlink.patch.gz | patch -p1 || exit 1
zcat $CWD/patches/0008-Avoid-none-values-in-slack-desc.patch.gz | patch -p1 || exit 1
zcat $CWD/patches/0009-Add-c-option-just-as-makepkg-c-y.patch.gz | patch -p1 || exit 1
- # Make sure that if someone created an RPM with absolute filenames that we
- # don't allow it to write all over the / directory when we're just trying
- # to extract it to make the .tgz:
- zcat $CWD/patches/0010-no-absolute-filenames-extracting-cpio.patch.gz | patch -p1 || exit 1
+ ## NO - cpio is stupid and strips the leading '/' from symlinks to absolute
+ ## paths with --no-absolute-filenames. :-/
+ ## Make sure that if someone created an RPM with absolute filenames that we
+ ## don't allow it to write all over the / directory when we're just trying
+ ## to extract it to make the .tgz:
+ #zcat $CWD/patches/0010-no-absolute-filenames-extracting-cpio.patch.gz | patch -p1 || exit 1
# Allow ignoring rpm2cpio error code. Some RPMs can be extracted, but
# throw an error anyway.
zcat $CWD/patches/0011-ignore-rpm2cpio-error-code.patch.gz | patch -p1 || exit 1
diff --git a/source/kde/kde/build/kate b/source/kde/kde/build/kate
index d00491fd..0cfbf088 100644
--- a/source/kde/kde/build/kate
+++ b/source/kde/kde/build/kate
@@ -1 +1 @@
-1
+2
diff --git a/source/kde/kde/patch/kate.patch b/source/kde/kde/patch/kate.patch
index 59df062a..df3032ed 100644
--- a/source/kde/kde/patch/kate.patch
+++ b/source/kde/kde/patch/kate.patch
@@ -1,3 +1,10 @@
# Allow Kate to be started by the root user; disallowing this is not
# a decision that a developer should make for the user, it is patronizing:
cat $CWD/patch/kate/kate_runasroot.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
+# Fix KTextEditor/Kate: Missing validation of binaries executed via QProcess (CVE-2022-23853)
+cat $CWD/patch/kate/361dd43e42994829dbdb35e78fb7698d27cbb0e2.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+cat $CWD/patch/kate/6fc3bf6e5bd540e842e32c4a959c2158c8573be5.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+cat $CWD/patch/kate/92a9c65e30b4b63b8b116eb5c8dcb1e1a2d867bc.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+cat $CWD/patch/kate/c5d66f3b70ae4778d6162564309aee95f643e7c9.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+cat $CWD/patch/kate/7e08a58fb50d28ba96aedd5f5cd79a9479b4a0ad.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
diff --git a/source/kde/kde/patch/kate/361dd43e42994829dbdb35e78fb7698d27cbb0e2.patch b/source/kde/kde/patch/kate/361dd43e42994829dbdb35e78fb7698d27cbb0e2.patch
new file mode 100644
index 00000000..4f7237aa
--- /dev/null
+++ b/source/kde/kde/patch/kate/361dd43e42994829dbdb35e78fb7698d27cbb0e2.patch
@@ -0,0 +1,87 @@
+From 361dd43e42994829dbdb35e78fb7698d27cbb0e2 Mon Sep 17 00:00:00 2001
+From: Mark Nauwelaerts <mark.nauwelaerts@gmail.com>
+Date: Mon, 13 Dec 2021 20:52:57 +0100
+Subject: [PATCH] lspclient: consider some additional server capabilities
+
+---
+ addons/lspclient/lspclientprotocol.h | 14 +++++++++++++-
+ addons/lspclient/lspclientserver.cpp | 9 ++++++++-
+ addons/lspclient/lspclientservermanager.cpp | 2 +-
+ 3 files changed, 22 insertions(+), 3 deletions(-)
+
+diff --git a/addons/lspclient/lspclientprotocol.h b/addons/lspclient/lspclientprotocol.h
+index 0fb7f4485..9de0ec511 100644
+--- a/addons/lspclient/lspclientprotocol.h
++++ b/addons/lspclient/lspclientprotocol.h
+@@ -21,6 +21,8 @@
+ #include <KTextEditor/Cursor>
+ #include <KTextEditor/Range>
+
++#include <optional>
++
+ // Following types roughly follow the types/interfaces as defined in LSP protocol spec
+ // although some deviation may arise where it has been deemed useful
+ // Moreover, to avoid introducing a custom 'optional' type, absence of an optional
+@@ -51,6 +53,16 @@ struct LSPResponseError {
+
+ enum class LSPDocumentSyncKind { None = 0, Full = 1, Incremental = 2 };
+
++struct LSPSaveOptions {
++ bool includeText = false;
++};
++
++// only used parts for now
++struct LSPTextDocumentSyncOptions {
++ LSPDocumentSyncKind change = LSPDocumentSyncKind::None;
++ std::optional<LSPSaveOptions> save;
++};
++
+ struct LSPCompletionOptions {
+ bool provider = false;
+ bool resolveProvider = false;
+@@ -81,7 +93,7 @@ struct LSPWorkspaceFoldersServerCapabilities {
+ };
+
+ struct LSPServerCapabilities {
+- LSPDocumentSyncKind textDocumentSync = LSPDocumentSyncKind::None;
++ LSPTextDocumentSyncOptions textDocumentSync;
+ bool hoverProvider = false;
+ LSPCompletionOptions completionProvider;
+ LSPSignatureHelpOptions signatureHelpProvider;
+diff --git a/addons/lspclient/lspclientserver.cpp b/addons/lspclient/lspclientserver.cpp
+index 8739d46c9..a7094fde2 100644
+--- a/addons/lspclient/lspclientserver.cpp
++++ b/addons/lspclient/lspclientserver.cpp
+@@ -344,8 +344,15 @@ static void from_json(LSPServerCapabilities &caps, const QJsonObject &json)
+ };
+
+ auto sync = json.value(QStringLiteral("textDocumentSync"));
+- caps.textDocumentSync = static_cast<LSPDocumentSyncKind>(
++ caps.textDocumentSync.change = static_cast<LSPDocumentSyncKind>(
+ (sync.isObject() ? sync.toObject().value(QStringLiteral("change")) : sync).toInt(static_cast<int>(LSPDocumentSyncKind::None)));
++ if (sync.isObject()) {
++ auto syncObject = sync.toObject();
++ auto save = syncObject.value(QStringLiteral("save"));
++ if (save.isObject() || save.toBool()) {
++ caps.textDocumentSync.save = {save.toObject().value(QStringLiteral("includeText")).toBool()};
++ }
++ }
+ caps.hoverProvider = toBoolOrObject(json.value(QStringLiteral("hoverProvider")));
+ from_json(caps.completionProvider, json.value(QStringLiteral("completionProvider")));
+ from_json(caps.signatureHelpProvider, json.value(QStringLiteral("signatureHelpProvider")));
+diff --git a/addons/lspclient/lspclientservermanager.cpp b/addons/lspclient/lspclientservermanager.cpp
+index 1fbcf928f..1e03801ea 100644
+--- a/addons/lspclient/lspclientservermanager.cpp
++++ b/addons/lspclient/lspclientservermanager.cpp
+@@ -931,7 +931,7 @@ private:
+ auto it = m_docs.find(doc);
+ if (it != m_docs.end() && it->server) {
+ const auto &caps = it->server->capabilities();
+- if (caps.textDocumentSync == LSPDocumentSyncKind::Incremental) {
++ if (caps.textDocumentSync.change == LSPDocumentSyncKind::Incremental) {
+ return &(*it);
+ }
+ }
+--
+GitLab
+
diff --git a/source/kde/kde/patch/kate/6fc3bf6e5bd540e842e32c4a959c2158c8573be5.patch b/source/kde/kde/patch/kate/6fc3bf6e5bd540e842e32c4a959c2158c8573be5.patch
new file mode 100644
index 00000000..cdbde70f
--- /dev/null
+++ b/source/kde/kde/patch/kate/6fc3bf6e5bd540e842e32c4a959c2158c8573be5.patch
@@ -0,0 +1,71 @@
+From 6fc3bf6e5bd540e842e32c4a959c2158c8573be5 Mon Sep 17 00:00:00 2001
+From: Mark Nauwelaerts <mark.nauwelaerts@gmail.com>
+Date: Mon, 13 Dec 2021 21:36:50 +0100
+Subject: [PATCH] lspclient: send didSave notification if so requested
+
+---
+ addons/lspclient/lspclientserver.cpp | 7 +++++--
+ addons/lspclient/lspclientservermanager.cpp | 15 +++++++++++++++
+ 2 files changed, 20 insertions(+), 2 deletions(-)
+
+diff --git a/addons/lspclient/lspclientserver.cpp b/addons/lspclient/lspclientserver.cpp
+index a7094fde2..9fb5844cd 100644
+--- a/addons/lspclient/lspclientserver.cpp
++++ b/addons/lspclient/lspclientserver.cpp
+@@ -1255,7 +1255,8 @@ private:
+ {QStringLiteral("documentSymbol"), QJsonObject{{QStringLiteral("hierarchicalDocumentSymbolSupport"), true}} },
+ {QStringLiteral("publishDiagnostics"), QJsonObject{{QStringLiteral("relatedInformation"), true}}},
+ {QStringLiteral("codeAction"), codeAction},
+- {QStringLiteral("semanticTokens"), semanticTokens}
++ {QStringLiteral("semanticTokens"), semanticTokens},
++ {QStringLiteral("synchronization"), QJsonObject{{QStringLiteral("didSave"), true}}},
+ },
+ },
+ {QStringLiteral("window"),
+@@ -1475,7 +1476,9 @@ public:
+ void didSave(const QUrl &document, const QString &text)
+ {
+ auto params = textDocumentParams(document);
+- params[QStringLiteral("text")] = text;
++ if (!text.isNull()) {
++ params[QStringLiteral("text")] = text;
++ }
+ send(init_request(QStringLiteral("textDocument/didSave"), params));
+ }
+
+diff --git a/addons/lspclient/lspclientservermanager.cpp b/addons/lspclient/lspclientservermanager.cpp
+index 1e03801ea..551926e23 100644
+--- a/addons/lspclient/lspclientservermanager.cpp
++++ b/addons/lspclient/lspclientservermanager.cpp
+@@ -833,6 +833,7 @@ private:
+ connect(doc, &KTextEditor::Document::aboutToClose, this, &self_type::untrack, Qt::UniqueConnection);
+ connect(doc, &KTextEditor::Document::destroyed, this, &self_type::untrack, Qt::UniqueConnection);
+ connect(doc, &KTextEditor::Document::textChanged, this, &self_type::onTextChanged, Qt::UniqueConnection);
++ connect(doc, &KTextEditor::Document::documentSavedOrUploaded, this, &self_type::onDocumentSaved, Qt::UniqueConnection);
+ // in case of incremental change
+ connect(doc, &KTextEditor::Document::textInserted, this, &self_type::onTextInserted, Qt::UniqueConnection);
+ connect(doc, &KTextEditor::Document::textRemoved, this, &self_type::onTextRemoved, Qt::UniqueConnection);
+@@ -976,6 +977,20 @@ private:
+ }
+ }
+
++ void onDocumentSaved(KTextEditor::Document *doc, bool saveAs)
++ {
++ if (!saveAs) {
++ auto it = m_docs.find(doc);
++ if (it != m_docs.end() && it->server) {
++ auto server = it->server;
++ const auto &saveOptions = server->capabilities().textDocumentSync.save;
++ if (saveOptions) {
++ server->didSave(doc->url(), saveOptions->includeText ? doc->text() : QString());
++ }
++ }
++ }
++ }
++
+ void onMessage(bool isLog, const LSPLogMessageParams &params)
+ {
+ // determine server description
+--
+GitLab
+
diff --git a/source/kde/kde/patch/kate/7e08a58fb50d28ba96aedd5f5cd79a9479b4a0ad.patch b/source/kde/kde/patch/kate/7e08a58fb50d28ba96aedd5f5cd79a9479b4a0ad.patch
new file mode 100644
index 00000000..456f3361
--- /dev/null
+++ b/source/kde/kde/patch/kate/7e08a58fb50d28ba96aedd5f5cd79a9479b4a0ad.patch
@@ -0,0 +1,918 @@
+From 7e08a58fb50d28ba96aedd5f5cd79a9479b4a0ad Mon Sep 17 00:00:00 2001
+From: Christoph Cullmann <cullmann@kde.org>
+Date: Mon, 24 Jan 2022 19:07:37 +0000
+Subject: [PATCH] improve QProcess handling
+
+ensure we take executables from PATH for execution instead possibly from current working directory
+or the working directory set for the QProcess
+---
+ addons/compiler-explorer/compiledbreader.cpp | 4 +-
+ addons/externaltools/katetoolrunner.cpp | 9 +++-
+ addons/gdbplugin/debugview.cpp | 17 +++++++-
+ addons/git-blame/commitfilesview.cpp | 17 +++++---
+ addons/git-blame/kategitblameplugin.cpp | 8 +++-
+ addons/kate-ctags/gotosymbolmodel.cpp | 15 +++++--
+ addons/project/comparebranchesview.cpp | 4 +-
+ addons/project/filehistorywidget.cpp | 14 +++++--
+ addons/project/git/gitutils.cpp | 41 +++++++++++++++----
+ addons/project/gitwidget.cpp | 19 +++++++--
+ addons/project/kateprojectindex.cpp | 9 +++-
+ .../kateprojectinfoviewcodeanalysis.cpp | 9 +++-
+ addons/project/kateprojectworker.cpp | 32 +++++++++++----
+ addons/project/stashdialog.cpp | 20 ++++-----
+ addons/project/stashdialog.h | 2 +-
+ addons/replicode/replicodeview.cpp | 9 ++++
+ addons/xmlcheck/plugin_katexmlcheck.cpp | 8 ++++
+ kate/katefileactions.cpp | 17 ++++----
+ kate/katefileactions.h | 4 +-
+ kate/katemwmodonhddialog.cpp | 6 ++-
+ kate/katemwmodonhddialog.h | 1 +
+ kate/kateviewspace.cpp | 7 +++-
+ shared/gitprocess.h | 16 +++++++-
+ 23 files changed, 217 insertions(+), 71 deletions(-)
+
+diff --git a/addons/compiler-explorer/compiledbreader.cpp b/addons/compiler-explorer/compiledbreader.cpp
+index 74e83638e..ab9ebc483 100644
+--- a/addons/compiler-explorer/compiledbreader.cpp
++++ b/addons/compiler-explorer/compiledbreader.cpp
+@@ -21,7 +21,9 @@ std::optional<QString> getDotGitPath(const QString &repo)
+ {
+ /* This call is intentionally blocking because we need git path for everything else */
+ QProcess git;
+- setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--absolute-git-dir")});
++ if (!setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--absolute-git-dir")})) {
++ return std::nullopt;
++ }
+ git.start(QProcess::ReadOnly);
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+ if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
+diff --git a/addons/externaltools/katetoolrunner.cpp b/addons/externaltools/katetoolrunner.cpp
+index 10a5d7226..e14940ad7 100644
+--- a/addons/externaltools/katetoolrunner.cpp
++++ b/addons/externaltools/katetoolrunner.cpp
+@@ -14,6 +14,7 @@
+ #include <KTextEditor/View>
+ #include <QFileInfo>
+ #include <QRegularExpression>
++#include <QStandardPaths>
+
+ KateToolRunner::KateToolRunner(std::unique_ptr<KateExternalTool> tool, KTextEditor::View *view, QObject *parent)
+ : QObject(parent)
+@@ -40,6 +41,12 @@ KateExternalTool *KateToolRunner::tool() const
+
+ void KateToolRunner::run()
+ {
++ // always only execute the tool from PATH
++ const auto fullExecutable = QStandardPaths::findExecutable(m_tool->executable);
++ if (fullExecutable.isEmpty()) {
++ return;
++ }
++
+ if (!m_tool->workingDir.isEmpty()) {
+ m_process->setWorkingDirectory(m_tool->workingDir);
+ } else if (m_view) {
+@@ -72,7 +79,7 @@ void KateToolRunner::run()
+ });
+
+ const QStringList args = KShell::splitArgs(m_tool->arguments);
+- m_process->start(m_tool->executable, args);
++ m_process->start(fullExecutable, args);
+ }
+
+ void KateToolRunner::waitForFinished()
+diff --git a/addons/gdbplugin/debugview.cpp b/addons/gdbplugin/debugview.cpp
+index 9505daa25..d8c868d7a 100644
+--- a/addons/gdbplugin/debugview.cpp
++++ b/addons/gdbplugin/debugview.cpp
+@@ -12,7 +12,9 @@
+ #include "debugview.h"
+
+ #include <QFile>
++#include <QFileInfo>
+ #include <QRegularExpression>
++#include <QStandardPaths>
+ #include <QTimer>
+
+ #include <KLocalizedString>
+@@ -48,7 +50,20 @@ void DebugView::runDebugger(const GDBTargetConf &conf, const QStringList &ioFifo
+ if (conf.executable.isEmpty()) {
+ return;
+ }
++
+ m_targetConf = conf;
++
++ // no chance if no debugger configured
++ if (m_targetConf.gdbCmd.isEmpty()) {
++ return;
++ }
++
++ // only run debugger from PATH or the absolute executable path we specified
++ const auto fullExecutable = QFileInfo(m_targetConf.gdbCmd).isAbsolute() ? m_targetConf.gdbCmd : QStandardPaths::findExecutable(m_targetConf.gdbCmd);
++ if (fullExecutable.isEmpty()) {
++ return;
++ }
++
+ if (ioFifos.size() == 3) {
+ m_ioPipeString = QStringLiteral("< %1 1> %2 2> %3").arg(ioFifos[0], ioFifos[1], ioFifos[2]);
+ }
+@@ -69,7 +84,7 @@ void DebugView::runDebugger(const GDBTargetConf &conf, const QStringList &ioFifo
+
+ connect(&m_debugProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &DebugView::slotDebugFinished);
+
+- m_debugProcess.start(m_targetConf.gdbCmd, QStringList());
++ m_debugProcess.start(fullExecutable, QStringList());
+
+ m_nextCommands << QStringLiteral("set pagination off");
+ m_state = ready;
+diff --git a/addons/git-blame/commitfilesview.cpp b/addons/git-blame/commitfilesview.cpp
+index 26e484a4a..667b423b2 100644
+--- a/addons/git-blame/commitfilesview.cpp
++++ b/addons/git-blame/commitfilesview.cpp
+@@ -263,7 +263,9 @@ static void createFileTree(QStandardItem *parent, const QString &basePath, const
+ static std::optional<QString> getGitCmdOutput(const QString &workDir, const QStringList &args)
+ {
+ QProcess git;
+- setupGitProcess(git, workDir, args);
++ if (!setupGitProcess(git, workDir, args)) {
++ return {};
++ }
+ git.start(QProcess::ReadOnly);
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+ if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
+@@ -365,9 +367,12 @@ void CommitDiffTreeView::openCommit(const QString &hash, const QString &filePath
+ m_commitHash = hash;
+
+ QProcess *git = new QProcess(this);
+- setupGitProcess(*git,
+- QFileInfo(filePath).absolutePath(),
+- {QStringLiteral("show"), hash, QStringLiteral("--numstat"), QStringLiteral("--pretty=oneline"), QStringLiteral("-z")});
++ if (!setupGitProcess(*git,
++ QFileInfo(filePath).absolutePath(),
++ {QStringLiteral("show"), hash, QStringLiteral("--numstat"), QStringLiteral("--pretty=oneline"), QStringLiteral("-z")})) {
++ delete git;
++ return;
++ }
+ connect(git, &QProcess::finished, this, [this, git, filePath](int e, QProcess::ExitStatus s) {
+ git->deleteLater();
+ if (e != 0 || s != QProcess::NormalExit) {
+@@ -440,7 +445,9 @@ void CommitDiffTreeView::showDiff(const QModelIndex &idx)
+ {
+ const QString file = idx.data(FileItem::Path).toString();
+ QProcess git;
+- setupGitProcess(git, m_gitDir, {QStringLiteral("show"), m_commitHash, QStringLiteral("--"), file});
++ if (!setupGitProcess(git, m_gitDir, {QStringLiteral("show"), m_commitHash, QStringLiteral("--"), file})) {
++ return;
++ }
+ git.start(QProcess::ReadOnly);
+
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+diff --git a/addons/git-blame/kategitblameplugin.cpp b/addons/git-blame/kategitblameplugin.cpp
+index d0354cc75..ae0f8c106 100644
+--- a/addons/git-blame/kategitblameplugin.cpp
++++ b/addons/git-blame/kategitblameplugin.cpp
+@@ -255,7 +255,9 @@ void KateGitBlamePluginView::startBlameProcess(const QUrl &url)
+ QDir dir{url.toLocalFile()};
+ dir.cdUp();
+
+- setupGitProcess(m_blameInfoProc, dir.absolutePath(), {QStringLiteral("blame"), QStringLiteral("-p"), QStringLiteral("./%1").arg(fileName)});
++ if (!setupGitProcess(m_blameInfoProc, dir.absolutePath(), {QStringLiteral("blame"), QStringLiteral("-p"), QStringLiteral("./%1").arg(fileName)})) {
++ return;
++ }
+ m_blameInfoProc.start(QIODevice::ReadOnly);
+ m_blameUrl = url;
+ }
+@@ -270,7 +272,9 @@ void KateGitBlamePluginView::startShowProcess(const QUrl &url, const QString &ha
+ QDir dir{url.toLocalFile()};
+ dir.cdUp();
+
+- setupGitProcess(m_showProc, dir.absolutePath(), {QStringLiteral("show"), hash, QStringLiteral("--numstat")});
++ if (!setupGitProcess(m_showProc, dir.absolutePath(), {QStringLiteral("show"), hash, QStringLiteral("--numstat")})) {
++ return;
++ }
+ m_showProc.start(QIODevice::ReadOnly);
+ }
+
+diff --git a/addons/kate-ctags/gotosymbolmodel.cpp b/addons/kate-ctags/gotosymbolmodel.cpp
+index 6c547e379..0c116090f 100644
+--- a/addons/kate-ctags/gotosymbolmodel.cpp
++++ b/addons/kate-ctags/gotosymbolmodel.cpp
+@@ -8,6 +8,7 @@
+ #include <KLocalizedString>
+ #include <QDebug>
+ #include <QProcess>
++#include <QStandardPaths>
+
+ GotoSymbolModel::GotoSymbolModel(QObject *parent)
+ : QAbstractTableModel(parent)
+@@ -58,16 +59,24 @@ void GotoSymbolModel::refresh(const QString &filePath)
+ m_rows.clear();
+ endResetModel();
+
++ // only use ctags from PATH
++ static const auto fullExecutablePath = QStandardPaths::findExecutable(QStringLiteral("ctags"));
++ if (fullExecutablePath.isEmpty()) {
++ beginResetModel();
++ m_rows.append(SymbolItem{i18n("CTags executable not found."), -1, QIcon()});
++ endResetModel();
++ return;
++ }
++
+ QProcess p;
+- p.start(QStringLiteral("ctags"), {QStringLiteral("-x"), QStringLiteral("--_xformat=%{name}%{signature}\t%{kind}\t%{line}"), filePath});
++ p.start(fullExecutablePath, {QStringLiteral("-x"), QStringLiteral("--_xformat=%{name}%{signature}\t%{kind}\t%{line}"), filePath});
+
+ QByteArray out;
+ if (p.waitForFinished()) {
+ out = p.readAllStandardOutput();
+ } else {
+- qWarning() << "Ctags failed";
+ beginResetModel();
+- m_rows.append(SymbolItem{i18n("CTags executable not found."), -1, QIcon()});
++ m_rows.append(SymbolItem{i18n("CTags executable failed to execute."), -1, QIcon()});
+ endResetModel();
+ return;
+ }
+diff --git a/addons/project/comparebranchesview.cpp b/addons/project/comparebranchesview.cpp
+index 48d1d2633..7cf585f66 100644
+--- a/addons/project/comparebranchesview.cpp
++++ b/addons/project/comparebranchesview.cpp
+@@ -158,7 +158,9 @@ void CompareBranchesView::showDiff(const QModelIndex &idx)
+ {
+ auto file = idx.data(Qt::UserRole).toString().remove(m_gitDir + QLatin1Char('/'));
+ QProcess git;
+- setupGitProcess(git, m_gitDir, {QStringLiteral("diff"), QStringLiteral("%1...%2").arg(m_fromBr).arg(m_toBr), QStringLiteral("--"), file});
++ if (!setupGitProcess(git, m_gitDir, {QStringLiteral("diff"), QStringLiteral("%1...%2").arg(m_fromBr).arg(m_toBr), QStringLiteral("--"), file})) {
++ return;
++ }
+ git.start(QProcess::ReadOnly);
+
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+diff --git a/addons/project/filehistorywidget.cpp b/addons/project/filehistorywidget.cpp
+index 626016a6b..14857e178 100644
+--- a/addons/project/filehistorywidget.cpp
++++ b/addons/project/filehistorywidget.cpp
+@@ -231,9 +231,12 @@ FileHistoryWidget::~FileHistoryWidget()
+ // git log --format=%H%n%aN%n%aE%n%at%n%ct%n%P%n%B --author-date-order
+ void FileHistoryWidget::getFileHistory(const QString &file)
+ {
+- setupGitProcess(m_git,
+- QFileInfo(file).absolutePath(),
+- {QStringLiteral("log"), QStringLiteral("--format=%H%n%aN%n%aE%n%at%n%ct%n%P%n%B"), QStringLiteral("-z"), file});
++ if (!setupGitProcess(m_git,
++ QFileInfo(file).absolutePath(),
++ {QStringLiteral("log"), QStringLiteral("--format=%H%n%aN%n%aE%n%at%n%ct%n%P%n%B"), QStringLiteral("-z"), file})) {
++ Q_EMIT errorMessage(i18n("Failed to get file history: git executable not found in PATH"), true);
++ return;
++ }
+
+ connect(&m_git, &QProcess::readyReadStandardOutput, this, [this] {
+ auto commits = parseCommits(m_git.readAllStandardOutput().split(0x00));
+@@ -258,7 +261,10 @@ void FileHistoryWidget::itemClicked(const QModelIndex &idx)
+
+ const auto commit = idx.data(CommitListModel::CommitRole).value<Commit>();
+
+- setupGitProcess(git, fi.absolutePath(), {QStringLiteral("show"), QString::fromUtf8(commit.hash), QStringLiteral("--"), m_file});
++ if (!setupGitProcess(git, fi.absolutePath(), {QStringLiteral("show"), QString::fromUtf8(commit.hash), QStringLiteral("--"), m_file})) {
++ return;
++ }
++
+ git.start(QProcess::ReadOnly);
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+ if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
+diff --git a/addons/project/git/gitutils.cpp b/addons/project/git/gitutils.cpp
+index ea8dd8823..8b494c16f 100644
+--- a/addons/project/git/gitutils.cpp
++++ b/addons/project/git/gitutils.cpp
+@@ -15,7 +15,10 @@
+ bool GitUtils::isGitRepo(const QString &repo)
+ {
+ QProcess git;
+- setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--is-inside-work-tree")});
++ if (!setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--is-inside-work-tree")})) {
++ return false;
++ }
++
+ git.start(QProcess::ReadOnly);
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+ return git.readAll().trimmed() == "true";
+@@ -27,7 +30,10 @@ std::optional<QString> GitUtils::getDotGitPath(const QString &repo)
+ {
+ /* This call is intentionally blocking because we need git path for everything else */
+ QProcess git;
+- setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--absolute-git-dir")});
++ if (!setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--absolute-git-dir")})) {
++ return std::nullopt;
++ }
++
+ git.start(QProcess::ReadOnly);
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+ if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
+@@ -57,7 +63,10 @@ QString GitUtils::getCurrentBranchName(const QString &repo)
+
+ for (int i = 0; i < 3; ++i) {
+ QProcess git;
+- setupGitProcess(git, repo, argsList[i]);
++ if (!setupGitProcess(git, repo, argsList[i])) {
++ return QString();
++ }
++
+ git.start(QProcess::ReadOnly);
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+ if (git.exitStatus() == QProcess::NormalExit && git.exitCode() == 0) {
+@@ -73,7 +82,10 @@ QString GitUtils::getCurrentBranchName(const QString &repo)
+ GitUtils::CheckoutResult GitUtils::checkoutBranch(const QString &repo, const QString &branch)
+ {
+ QProcess git;
+- setupGitProcess(git, repo, {QStringLiteral("checkout"), branch});
++ if (!setupGitProcess(git, repo, {QStringLiteral("checkout"), branch})) {
++ return CheckoutResult{};
++ }
++
+ git.start(QProcess::ReadOnly);
+ CheckoutResult res;
+ res.branch = branch;
+@@ -91,7 +103,11 @@ GitUtils::CheckoutResult GitUtils::checkoutNewBranch(const QString &repo, const
+ if (!fromBranch.isEmpty()) {
+ args.append(fromBranch);
+ }
+- setupGitProcess(git, repo, args);
++
++ if (!setupGitProcess(git, repo, args)) {
++ return CheckoutResult{};
++ }
++
+ git.start(QProcess::ReadOnly);
+ CheckoutResult res;
+ res.branch = newBranch;
+@@ -132,7 +148,10 @@ QVector<GitUtils::Branch> GitUtils::getAllBranchesAndTags(const QString &repo, R
+ args.append(QStringLiteral("--sort=-taggerdate"));
+ }
+
+- setupGitProcess(git, repo, args);
++ if (!setupGitProcess(git, repo, args)) {
++ return {};
++ }
++
+ git.start(QProcess::ReadOnly);
+ QVector<Branch> branches;
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+@@ -166,7 +185,10 @@ std::pair<QString, QString> GitUtils::getLastCommitMessage(const QString &repo)
+ {
+ // git log -1 --pretty=%B
+ QProcess git;
+- setupGitProcess(git, repo, {QStringLiteral("log"), QStringLiteral("-1"), QStringLiteral("--pretty=%B")});
++ if (!setupGitProcess(git, repo, {QStringLiteral("log"), QStringLiteral("-1"), QStringLiteral("--pretty=%B")})) {
++ return {};
++ }
++
+ git.start(QProcess::ReadOnly);
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+ if (git.exitCode() != 0 || git.exitStatus() != QProcess::NormalExit) {
+@@ -197,7 +219,10 @@ GitUtils::Result GitUtils::deleteBranches(const QStringList &branches, const QSt
+ args << branches;
+
+ QProcess git;
+- setupGitProcess(git, repo, args);
++ if (!setupGitProcess(git, repo, args)) {
++ return {};
++ }
++
+ git.start(QProcess::ReadOnly);
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+ QString out = QString::fromLatin1(git.readAllStandardError()) + QString::fromLatin1(git.readAllStandardOutput());
+diff --git a/addons/project/gitwidget.cpp b/addons/project/gitwidget.cpp
+index 2b19781c0..77499dad8 100644
+--- a/addons/project/gitwidget.cpp
++++ b/addons/project/gitwidget.cpp
+@@ -514,8 +514,9 @@ void GitWidget::launchExternalDiffTool(const QString &file, bool staged)
+ args.append(file);
+
+ QProcess git;
+- setupGitProcess(git, m_gitPath, args);
+- git.startDetached();
++ if (setupGitProcess(git, m_gitPath, args)) {
++ git.startDetached();
++ }
+ }
+
+ void GitWidget::commitChanges(const QString &msg, const QString &desc, bool signOff, bool amend)
+@@ -745,7 +746,12 @@ void GitWidget::branchCompareFiles(const QString &from, const QString &to)
+ auto args = QStringList{QStringLiteral("diff"), QStringLiteral("%1...%2").arg(from).arg(to), QStringLiteral("--name-status")};
+
+ QProcess git;
+- setupGitProcess(git, m_gitPath, args);
++
++ // early out if we can't find git
++ if (!setupGitProcess(git, m_gitPath, args)) {
++ return;
++ }
++
+ git.start(QProcess::ReadOnly);
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+ if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
+@@ -767,7 +773,12 @@ void GitWidget::branchCompareFiles(const QString &from, const QString &to)
+
+ // get --num-stat
+ args = QStringList{QStringLiteral("diff"), QStringLiteral("%1...%2").arg(from).arg(to), QStringLiteral("--numstat"), QStringLiteral("-z")};
+- setupGitProcess(git, m_gitPath, args);
++
++ // early out if we can't find git
++ if (!setupGitProcess(git, m_gitPath, args)) {
++ return;
++ }
++
+ git.start(QProcess::ReadOnly);
+ if (git.waitForStarted() && git.waitForFinished(-1)) {
+ if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
+diff --git a/addons/project/kateprojectindex.cpp b/addons/project/kateprojectindex.cpp
+index a7d9ec9c1..9fc5b64cb 100644
+--- a/addons/project/kateprojectindex.cpp
++++ b/addons/project/kateprojectindex.cpp
+@@ -9,6 +9,7 @@
+
+ #include <QDir>
+ #include <QProcess>
++#include <QStandardPaths>
+
+ /**
+ * include ctags reading
+@@ -73,6 +74,12 @@ void KateProjectIndex::loadCtags(const QStringList &files, const QVariantMap &ct
+ */
+ m_ctagsIndexFile->close();
+
++ // only use ctags from PATH
++ static const auto fullExecutablePath = QStandardPaths::findExecutable(QStringLiteral("ctags"));
++ if (fullExecutablePath.isEmpty()) {
++ return;
++ }
++
+ /**
+ * try to run ctags for all files in this project
+ * output to our ctags index file
+@@ -85,7 +92,7 @@ void KateProjectIndex::loadCtags(const QStringList &files, const QVariantMap &ct
+ for (const QVariant &optVariant : opts) {
+ args << optVariant.toString();
+ }
+- ctags.start(QStringLiteral("ctags"), args);
++ ctags.start(fullExecutablePath, args);
+ if (!ctags.waitForStarted()) {
+ return;
+ }
+diff --git a/addons/project/kateprojectinfoviewcodeanalysis.cpp b/addons/project/kateprojectinfoviewcodeanalysis.cpp
+index 21cd26a84..23b82c45e 100644
+--- a/addons/project/kateprojectinfoviewcodeanalysis.cpp
++++ b/addons/project/kateprojectinfoviewcodeanalysis.cpp
+@@ -13,6 +13,7 @@
+
+ #include <QFileInfo>
+ #include <QHBoxLayout>
++#include <QStandardPaths>
+ #include <QToolTip>
+ #include <QVBoxLayout>
+
+@@ -134,14 +135,18 @@ void KateProjectInfoViewCodeAnalysis::slotStartStopClicked()
+ connect(m_analyzer, &QProcess::readyRead, this, &KateProjectInfoViewCodeAnalysis::slotReadyRead);
+ connect(m_analyzer, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &KateProjectInfoViewCodeAnalysis::finished);
+
+- m_analyzer->start(m_analysisTool->path(), m_analysisTool->arguments());
++ // ensure we only run the code analyzer from PATH
++ const QString fullExecutable = QStandardPaths::findExecutable(m_analysisTool->path());
++ if (!fullExecutable.isEmpty()) {
++ m_analyzer->start(fullExecutable, m_analysisTool->arguments());
++ }
+
+ if (m_messageWidget) {
+ delete m_messageWidget;
+ m_messageWidget = nullptr;
+ }
+
+- if (!m_analyzer->waitForStarted()) {
++ if (fullExecutable.isEmpty() || !m_analyzer->waitForStarted()) {
+ m_messageWidget = new KMessageWidget(this);
+ m_messageWidget->setCloseButtonVisible(true);
+ m_messageWidget->setMessageType(KMessageWidget::Warning);
+diff --git a/addons/project/kateprojectworker.cpp b/addons/project/kateprojectworker.cpp
+index d1979d1ec..831dae89b 100644
+--- a/addons/project/kateprojectworker.cpp
++++ b/addons/project/kateprojectworker.cpp
+@@ -18,6 +18,7 @@
+ #include <QRegularExpression>
+ #include <QSet>
+ #include <QSettings>
++#include <QStandardPaths>
+ #include <QThread>
+ #include <QTime>
+ #include <QtConcurrent>
+@@ -442,10 +443,12 @@ QVector<QString> KateProjectWorker::filesFromGit(const QDir &dir, bool recursive
+
+ QVector<QString> KateProjectWorker::gitFiles(const QDir &dir, bool recursive, const QStringList &args)
+ {
++ QVector<QString> files;
+ QProcess git;
+- setupGitProcess(git, dir.absolutePath(), args);
++ if (!setupGitProcess(git, dir.absolutePath(), args)) {
++ return files;
++ }
+ git.start(QProcess::ReadOnly);
+- QVector<QString> files;
+ if (!git.waitForStarted() || !git.waitForFinished(-1)) {
+ return files;
+ }
+@@ -466,13 +469,18 @@ QVector<QString> KateProjectWorker::gitFiles(const QDir &dir, bool recursive, co
+
+ QVector<QString> KateProjectWorker::filesFromMercurial(const QDir &dir, bool recursive)
+ {
++ // only use version control from PATH
+ QVector<QString> files;
++ static const auto fullExecutablePath = QStandardPaths::findExecutable(QStringLiteral("hg"));
++ if (fullExecutablePath.isEmpty()) {
++ return files;
++ }
+
+ QProcess hg;
+ hg.setWorkingDirectory(dir.absolutePath());
+ QStringList args;
+ args << QStringLiteral("manifest") << QStringLiteral(".");
+- hg.start(QStringLiteral("hg"), args, QProcess::ReadOnly);
++ hg.start(fullExecutablePath, args, QProcess::ReadOnly);
+ if (!hg.waitForStarted() || !hg.waitForFinished(-1)) {
+ return files;
+ }
+@@ -493,7 +501,12 @@ QVector<QString> KateProjectWorker::filesFromMercurial(const QDir &dir, bool rec
+
+ QVector<QString> KateProjectWorker::filesFromSubversion(const QDir &dir, bool recursive)
+ {
++ // only use version control from PATH
+ QVector<QString> files;
++ static const auto fullExecutablePath = QStandardPaths::findExecutable(QStringLiteral("svn"));
++ if (fullExecutablePath.isEmpty()) {
++ return files;
++ }
+
+ QProcess svn;
+ svn.setWorkingDirectory(dir.absolutePath());
+@@ -504,7 +517,7 @@ QVector<QString> KateProjectWorker::filesFromSubversion(const QDir &dir, bool re
+ } else {
+ args << QStringLiteral("--depth=files");
+ }
+- svn.start(QStringLiteral("svn"), args, QProcess::ReadOnly);
++ svn.start(fullExecutablePath, args, QProcess::ReadOnly);
+ if (!svn.waitForStarted() || !svn.waitForFinished(-1)) {
+ return files;
+ }
+@@ -555,18 +568,21 @@ QVector<QString> KateProjectWorker::filesFromSubversion(const QDir &dir, bool re
+
+ QVector<QString> KateProjectWorker::filesFromDarcs(const QDir &dir, bool recursive)
+ {
++ // only use version control from PATH
+ QVector<QString> files;
++ static const auto fullExecutablePath = QStandardPaths::findExecutable(QStringLiteral("darcs"));
++ if (fullExecutablePath.isEmpty()) {
++ return files;
++ }
+
+- const QString cmd = QStringLiteral("darcs");
+ QString root;
+-
+ {
+ QProcess darcs;
+ darcs.setWorkingDirectory(dir.absolutePath());
+ QStringList args;
+ args << QStringLiteral("list") << QStringLiteral("repo");
+
+- darcs.start(cmd, args, QProcess::ReadOnly);
++ darcs.start(fullExecutablePath, args, QProcess::ReadOnly);
+
+ if (!darcs.waitForStarted() || !darcs.waitForFinished(-1)) {
+ return files;
+@@ -590,7 +606,7 @@ QVector<QString> KateProjectWorker::filesFromDarcs(const QDir &dir, bool recursi
+ darcs.setWorkingDirectory(dir.absolutePath());
+ args << QStringLiteral("list") << QStringLiteral("files") << QStringLiteral("--no-directories") << QStringLiteral("--pending");
+
+- darcs.start(cmd, args, QProcess::ReadOnly);
++ darcs.start(fullExecutablePath, args, QProcess::ReadOnly);
+
+ if (!darcs.waitForStarted() || !darcs.waitForFinished(-1)) {
+ return files;
+diff --git a/addons/project/stashdialog.cpp b/addons/project/stashdialog.cpp
+index c623182a8..bddedf709 100644
+--- a/addons/project/stashdialog.cpp
++++ b/addons/project/stashdialog.cpp
+@@ -32,6 +32,8 @@
+
+ #include <kfts_fuzzy_match.h>
+
++#include <gitprocess.h>
++
+ constexpr int StashIndexRole = Qt::UserRole + 2;
+
+ class StashFilterModel final : public QSortFilterProxyModel
+@@ -218,11 +220,10 @@ void StashDialog::slotReturnPressed()
+ hide();
+ }
+
+-QProcess *StashDialog::gitp()
++QProcess *StashDialog::gitp(const QStringList &arguments)
+ {
+ auto git = new QProcess(this);
+- git->setProgram(QStringLiteral("git"));
+- git->setWorkingDirectory(m_gitPath);
++ setupGitProcess(*git, m_gitPath, arguments);
+ return git;
+ }
+
+@@ -242,7 +243,7 @@ void StashDialog::stash(bool keepIndex, bool includeUntracked)
+ args.append(m_lineEdit.text());
+ }
+
+- auto git = gitp();
++ auto git = gitp(args);
+ connect(git, &QProcess::finished, this, [this, git](int exitCode, QProcess::ExitStatus es) {
+ if (es != QProcess::NormalExit || exitCode != 0) {
+ qWarning() << git->errorString();
+@@ -253,14 +254,12 @@ void StashDialog::stash(bool keepIndex, bool includeUntracked)
+ Q_EMIT done();
+ git->deleteLater();
+ });
+- git->setArguments(args);
+ git->start(QProcess::ReadOnly);
+ }
+
+ void StashDialog::getStashList()
+ {
+- auto git = gitp();
+- git->setArguments({QStringLiteral("stash"), QStringLiteral("list")});
++ auto git = gitp({QStringLiteral("stash"), QStringLiteral("list")});
+ git->start(QProcess::ReadOnly);
+
+ QList<QByteArray> stashList;
+@@ -293,11 +292,11 @@ void StashDialog::getStashList()
+
+ void StashDialog::popStash(const QByteArray &index, const QString &command)
+ {
+- auto git = gitp();
+ QStringList args{QStringLiteral("stash"), command};
+ if (!index.isEmpty()) {
+ args.append(QString::fromUtf8(index));
+ }
++ auto git = gitp(args);
+
+ connect(git, &QProcess::finished, this, [this, command, git](int exitCode, QProcess::ExitStatus es) {
+ if (es != QProcess::NormalExit || exitCode != 0) {
+@@ -320,7 +319,6 @@ void StashDialog::popStash(const QByteArray &index, const QString &command)
+ Q_EMIT done();
+ git->deleteLater();
+ });
+- git->setArguments(args);
+ git->start(QProcess::ReadOnly);
+ }
+
+@@ -339,9 +337,8 @@ void StashDialog::showStash(const QByteArray &index)
+ if (index.isEmpty()) {
+ return;
+ }
+- auto git = gitp();
+
+- QStringList args{QStringLiteral("stash"), QStringLiteral("show"), QStringLiteral("-p"), QString::fromUtf8(index)};
++ auto git = gitp({QStringLiteral("stash"), QStringLiteral("show"), QStringLiteral("-p"), QString::fromUtf8(index)});
+
+ connect(git, &QProcess::finished, this, [this, git](int exitCode, QProcess::ExitStatus es) {
+ if (es != QProcess::NormalExit || exitCode != 0) {
+@@ -353,6 +350,5 @@ void StashDialog::showStash(const QByteArray &index)
+ git->deleteLater();
+ });
+
+- git->setArguments(args);
+ git->start(QProcess::ReadOnly);
+ }
+diff --git a/addons/project/stashdialog.h b/addons/project/stashdialog.h
+index a18d42ab9..417690757 100644
+--- a/addons/project/stashdialog.h
++++ b/addons/project/stashdialog.h
+@@ -56,7 +56,7 @@ protected Q_SLOTS:
+ void slotReturnPressed() override;
+
+ private:
+- QProcess *gitp();
++ QProcess *gitp(const QStringList &arguments);
+ void stash(bool keepIndex, bool includeUntracked);
+ void getStashList();
+ void popStash(const QByteArray &index, const QString &command = QStringLiteral("pop"));
+diff --git a/addons/replicode/replicodeview.cpp b/addons/replicode/replicodeview.cpp
+index 0199f46ce..7f70ee1ea 100644
+--- a/addons/replicode/replicodeview.cpp
++++ b/addons/replicode/replicodeview.cpp
+@@ -8,7 +8,9 @@
+
+ #include "replicodeconfig.h"
+ #include "replicodesettings.h"
++
+ #include <QPushButton>
++#include <QStandardPaths>
+ #include <QTemporaryFile>
+ #include <QtGlobal>
+
+@@ -116,7 +118,14 @@ void ReplicodeView::runReplicode()
+ }
+
+ KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("Replicode"));
++
+ QString executorPath = config.readEntry<QString>("replicodePath", QString());
++
++ // ensure we only call replicode from PATH if not given as absolute path already
++ if (!executorPath.isEmpty() && !QFileInfo(executorPath).isAbsolute()) {
++ executorPath = QStandardPaths::findExecutable(executorPath);
++ }
++
+ if (executorPath.isEmpty()) {
+ QMessageBox::warning(m_mainWindow->window(),
+ i18nc("@title:window", "Replicode Executable Not Found"),
+diff --git a/addons/xmlcheck/plugin_katexmlcheck.cpp b/addons/xmlcheck/plugin_katexmlcheck.cpp
+index f1d52f3a7..3971550cd 100644
+--- a/addons/xmlcheck/plugin_katexmlcheck.cpp
++++ b/addons/xmlcheck/plugin_katexmlcheck.cpp
+@@ -304,10 +304,18 @@ bool PluginKateXMLCheckView::slotValidate()
+ s << kv->document()->text();
+ s.flush();
+
++ // ensure we only execute xmllint from PATH or application package
+ QString exe = QStandardPaths::findExecutable(QStringLiteral("xmllint"));
+ if (exe.isEmpty()) {
+ exe = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, QStringLiteral("xmllint"));
+ }
++ if (exe.isEmpty()) {
++ KMessageBox::error(nullptr,
++ i18n("<b>Error:</b> Failed to find xmllint. Please make "
++ "sure that xmllint is installed. It is part of libxml2."));
++ return false;
++ }
++
+ // qDebug() << "exe=" <<exe;
+ // // use catalogs for KDE docbook:
+ // if( ! getenv("XML_CATALOG_FILES") ) {
+diff --git a/kate/katefileactions.cpp b/kate/katefileactions.cpp
+index c56c7e1c8..09a23686c 100644
+--- a/kate/katefileactions.cpp
++++ b/kate/katefileactions.cpp
+@@ -23,6 +23,7 @@
+ #include <QDebug>
+ #include <QInputDialog>
+ #include <QProcess>
++#include <QStandardPaths>
+ #include <QUrl>
+
+ void KateFileActions::copyFilePathToClipboard(KTextEditor::Document *doc)
+@@ -137,17 +138,13 @@ void KateFileActions::deleteDocumentFile(QWidget *parent, KTextEditor::Document
+ }
+ }
+
+-QStringList KateFileActions::supportedDiffTools()
++QVector<std::pair<QString, QString>> KateFileActions::supportedDiffTools()
+ {
+- // LATER: check for program existence and set some boolean value accordingly
+- // Can this be even done in an easy way when we don't use the absolute path to the executable?
+- // See https://stackoverflow.com/questions/42444055/how-to-check-if-a-program-exists-in-path-using-qt
+-
+- QStringList resultList;
+- resultList.push_back(QStringLiteral("kdiff3"));
+- resultList.push_back(QStringLiteral("kompare"));
+- resultList.push_back(QStringLiteral("meld"));
+-
++ // query once if the tools are there in the path and store that
++ // we will disable the actions for the tools not found
++ static QVector<std::pair<QString, QString>> resultList{{QStringLiteral("kdiff3"), QStandardPaths::findExecutable(QStringLiteral("kdiff3"))},
++ {QStringLiteral("kompare"), QStandardPaths::findExecutable(QStringLiteral("kompare"))},
++ {QStringLiteral("meld"), QStandardPaths::findExecutable(QStringLiteral("meld"))}};
+ return resultList;
+ }
+
+diff --git a/kate/katefileactions.h b/kate/katefileactions.h
+index 524d81097..77cc5b0bf 100644
+--- a/kate/katefileactions.h
++++ b/kate/katefileactions.h
+@@ -51,9 +51,9 @@ void openFilePropertiesDialog(KTextEditor::Document *document);
+ void deleteDocumentFile(QWidget *parent, KTextEditor::Document *document);
+
+ /**
+- * @returns a list of supported diff tools (names of the executables)
++ * @returns a list of supported diff tools (names of the executables + paths to them, empty if not found in PATH)
+ */
+-QStringList supportedDiffTools();
++QVector<std::pair<QString, QString>> supportedDiffTools();
+
+ /**
+ * Runs an external program to compare the underlying files of two given documents.
+diff --git a/kate/katemwmodonhddialog.cpp b/kate/katemwmodonhddialog.cpp
+index e0041d858..d7c79e4d4 100644
+--- a/kate/katemwmodonhddialog.cpp
++++ b/kate/katemwmodonhddialog.cpp
+@@ -22,6 +22,7 @@
+ #include <QHeaderView>
+ #include <QLabel>
+ #include <QPushButton>
++#include <QStandardPaths>
+ #include <QStyle>
+ #include <QTemporaryFile>
+ #include <QTextStream>
+@@ -52,6 +53,7 @@ public:
+
+ KateMwModOnHdDialog::KateMwModOnHdDialog(DocVector docs, QWidget *parent, const char *name)
+ : QDialog(parent)
++ , m_fullDiffPath(QStandardPaths::findExecutable(QStringLiteral("diff")))
+ , m_proc(nullptr)
+ , m_diffFile(nullptr)
+ , m_blockAddDocument(false)
+@@ -108,6 +110,7 @@ KateMwModOnHdDialog::KateMwModOnHdDialog(DocVector docs, QWidget *parent, const
+ "file for the selected document, and shows the difference with the "
+ "default application. Requires diff(1)."));
+ hb->addWidget(btnDiff);
++ btnDiff->setEnabled(!m_fullDiffPath.isEmpty());
+ connect(btnDiff, &QPushButton::clicked, this, &KateMwModOnHdDialog::slotDiff);
+
+ // Dialog buttons
+@@ -288,9 +291,10 @@ void KateMwModOnHdDialog::slotDiff()
+ m_diffFile->open();
+
+ // Start a KProcess that creates a diff
++ // We use the full path to don't launch some random "diff" in current working directory
+ m_proc = new KProcess(this);
+ m_proc->setOutputChannelMode(KProcess::MergedChannels);
+- *m_proc << QStringLiteral("diff") << QStringLiteral("-ub") << QStringLiteral("-") << doc->url().toLocalFile();
++ *m_proc << m_fullDiffPath << QStringLiteral("-ub") << QStringLiteral("-") << doc->url().toLocalFile();
+ connect(m_proc, &KProcess::readyRead, this, &KateMwModOnHdDialog::slotDataAvailable);
+ connect(m_proc, static_cast<void (KProcess::*)(int, QProcess::ExitStatus)>(&KProcess::finished), this, &KateMwModOnHdDialog::slotPDone);
+
+diff --git a/kate/katemwmodonhddialog.h b/kate/katemwmodonhddialog.h
+index 11c09eab7..6fa245726 100644
+--- a/kate/katemwmodonhddialog.h
++++ b/kate/katemwmodonhddialog.h
+@@ -51,6 +51,7 @@ private:
+ class QTreeWidget *twDocuments;
+ class QDialogButtonBox *dlgButtons;
+ class QPushButton *btnDiff;
++ QString m_fullDiffPath;
+ KProcess *m_proc;
+ QTemporaryFile *m_diffFile;
+ QStringList m_stateTexts;
+diff --git a/kate/kateviewspace.cpp b/kate/kateviewspace.cpp
+index dba2fb973..af3bb8d34 100644
+--- a/kate/kateviewspace.cpp
++++ b/kate/kateviewspace.cpp
+@@ -678,8 +678,11 @@ void KateViewSpace::showContextMenu(int idx, const QPoint &globalPos)
+
+ if (mCompareWithActive->isEnabled()) {
+ for (auto &&diffTool : KateFileActions::supportedDiffTools()) {
+- QAction *compareAction = mCompareWithActive->addAction(diffTool);
+- compareAction->setData(diffTool);
++ QAction *compareAction = mCompareWithActive->addAction(diffTool.first);
++
++ // we use the full path to safely execute the tool, disable action if no full path => tool not found
++ compareAction->setData(diffTool.second);
++ compareAction->setEnabled(!diffTool.second.isEmpty());
+ }
+ }
+
+diff --git a/shared/gitprocess.h b/shared/gitprocess.h
+index 47b98b696..b0d79fac6 100644
+--- a/shared/gitprocess.h
++++ b/shared/gitprocess.h
+@@ -7,6 +7,7 @@
+ #pragma once
+
+ #include <QProcess>
++#include <QStandardPaths>
+
+ /**
+ * small helper function to setup a QProcess based "git" command.
+@@ -17,10 +18,20 @@
+ * @param process process to setup for git
+ * @param workingDirectory working directory to use for process
+ * @param arguments arguments to pass to git
++ * @return could set setup the process or did that fail, e.g. because the git executable is not available?
+ */
+-inline void setupGitProcess(QProcess &process, const QString &workingDirectory, const QStringList &arguments)
++inline bool setupGitProcess(QProcess &process, const QString &workingDirectory, const QStringList &arguments)
+ {
+- process.setProgram(QStringLiteral("git"));
++ // only use git from PATH
++ static const auto gitExecutable = QStandardPaths::findExecutable(QStringLiteral("git"));
++ if (gitExecutable.isEmpty()) {
++ // ensure we have no valid QProcess setup
++ process.setProgram(QString());
++ return false;
++ }
++
++ // setup program and arguments, ensure we do run git in the right working directory
++ process.setProgram(gitExecutable);
+ process.setWorkingDirectory(workingDirectory);
+ process.setArguments(arguments);
+
+@@ -37,4 +48,5 @@ inline void setupGitProcess(QProcess &process, const QString &workingDirectory,
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ env.insert(QStringLiteral("GIT_OPTIONAL_LOCKS"), QStringLiteral("0"));
+ process.setProcessEnvironment(env);
++ return true;
+ }
+--
+GitLab
+
diff --git a/source/kde/kde/patch/kate/92a9c65e30b4b63b8b116eb5c8dcb1e1a2d867bc.patch b/source/kde/kde/patch/kate/92a9c65e30b4b63b8b116eb5c8dcb1e1a2d867bc.patch
new file mode 100644
index 00000000..6900a46c
--- /dev/null
+++ b/source/kde/kde/patch/kate/92a9c65e30b4b63b8b116eb5c8dcb1e1a2d867bc.patch
@@ -0,0 +1,39 @@
+From 92a9c65e30b4b63b8b116eb5c8dcb1e1a2d867bc Mon Sep 17 00:00:00 2001
+From: Waqar Ahmed <waqar.17a@gmail.com>
+Date: Sun, 16 Jan 2022 18:39:50 +0500
+Subject: [PATCH] step down warning level when LSP not found
+
+Currently it gives an error which results in the widget popping up
+everytime you open a file for which you don't have LSP. However, one may
+have intentionally not installed the LSP for a language.
+
+BUG: 448549
+---
+ addons/lspclient/lspclientservermanager.cpp | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/addons/lspclient/lspclientservermanager.cpp b/addons/lspclient/lspclientservermanager.cpp
+index 551926e23..24e3f275b 100644
+--- a/addons/lspclient/lspclientservermanager.cpp
++++ b/addons/lspclient/lspclientservermanager.cpp
+@@ -737,13 +737,13 @@ private:
+ server.reset(new LSPClientServer(cmdline, root, realLangId, serverConfig.value(QStringLiteral("initializationOptions")), folders));
+ connect(server.data(), &LSPClientServer::stateChanged, this, &self_type::onStateChanged, Qt::UniqueConnection);
+ if (!server->start()) {
+- QString errorMessage = i18n("Failed to start server: %1", cmdline.join(QLatin1Char(' ')));
++ QString message = i18n("Failed to start server: %1", cmdline.join(QLatin1Char(' ')));
+ const auto url = serverConfig.value(QStringLiteral("url")).toString();
+ if (!url.isEmpty()) {
+- errorMessage += QStringLiteral("\n") + i18n("Please check your PATH for the binary");
+- errorMessage += QStringLiteral("\n") + i18n("See also %1 for installation or details", url);
++ message += QStringLiteral("\n") + i18n("Please check your PATH for the binary");
++ message += QStringLiteral("\n") + i18n("See also %1 for installation or details", url);
+ }
+- showMessage(errorMessage, KTextEditor::Message::Error);
++ showMessage(message, KTextEditor::Message::Warning);
+ } else {
+ showMessage(i18n("Started server %2: %1", cmdline.join(QLatin1Char(' ')), serverDescription(server.data())),
+ KTextEditor::Message::Positive);
+--
+GitLab
+
diff --git a/source/kde/kde/patch/kate/c5d66f3b70ae4778d6162564309aee95f643e7c9.patch b/source/kde/kde/patch/kate/c5d66f3b70ae4778d6162564309aee95f643e7c9.patch
new file mode 100644
index 00000000..cc3f058d
--- /dev/null
+++ b/source/kde/kde/patch/kate/c5d66f3b70ae4778d6162564309aee95f643e7c9.patch
@@ -0,0 +1,124 @@
+From c5d66f3b70ae4778d6162564309aee95f643e7c9 Mon Sep 17 00:00:00 2001
+From: Christoph Cullmann <cullmann@kde.org>
+Date: Thu, 20 Jan 2022 21:00:09 +0100
+Subject: [PATCH] avoid that we execute LSP binaries from cwd
+
+QProcess will just use current working directory as
+fallback
+
+that allows to execute un-wanted binaries by accident
+---
+ addons/lspclient/lspclientservermanager.cpp | 87 ++++++++++++---------
+ 1 file changed, 51 insertions(+), 36 deletions(-)
+
+diff --git a/addons/lspclient/lspclientservermanager.cpp b/addons/lspclient/lspclientservermanager.cpp
+index 24e3f275b..e78b4aa2d 100644
+--- a/addons/lspclient/lspclientservermanager.cpp
++++ b/addons/lspclient/lspclientservermanager.cpp
+@@ -707,52 +707,67 @@ private:
+ }
+
+ if (cmdline.length() > 0) {
++ // ensure we always only take the server executable from the PATH or user defined paths
++ // QProcess will take the executable even just from current working directory without this => BAD
++ auto cmd = QStandardPaths::findExecutable(cmdline[0]);
++
+ // optionally search in supplied path(s)
+- auto vpath = serverConfig.value(QStringLiteral("path")).toArray();
+- if (vpath.size() > 0) {
+- auto cmd = QStandardPaths::findExecutable(cmdline[0]);
+- if (cmd.isEmpty()) {
+- // collect and expand in case home dir or other (environment) variable reference is used
+- QStringList path;
+- for (const auto &e : vpath) {
+- auto p = e.toString();
+- editor->expandText(p, view, p);
+- path.push_back(p);
+- }
+- cmd = QStandardPaths::findExecutable(cmdline[0], path);
+- if (!cmd.isEmpty()) {
+- cmdline[0] = cmd;
+- }
++ const auto vpath = serverConfig.value(QStringLiteral("path")).toArray();
++ if (cmd.isEmpty() && !vpath.isEmpty()) {
++ // collect and expand in case home dir or other (environment) variable reference is used
++ QStringList path;
++ for (const auto &e : vpath) {
++ auto p = e.toString();
++ editor->expandText(p, view, p);
++ path.push_back(p);
+ }
++ cmd = QStandardPaths::findExecutable(cmdline[0], path);
+ }
+- // an empty list is always passed here (or null)
+- // the initial list is provided/updated using notification after start
+- // since that is what a server is more aware of
+- // and should support if it declares workspace folder capable
+- // (as opposed to the new initialization property)
+- LSPClientServer::FoldersType folders;
+- if (useWorkspace) {
+- folders = QList<LSPWorkspaceFolder>();
+- }
+- server.reset(new LSPClientServer(cmdline, root, realLangId, serverConfig.value(QStringLiteral("initializationOptions")), folders));
+- connect(server.data(), &LSPClientServer::stateChanged, this, &self_type::onStateChanged, Qt::UniqueConnection);
+- if (!server->start()) {
+- QString message = i18n("Failed to start server: %1", cmdline.join(QLatin1Char(' ')));
++
++ // we can only start the stuff if we did find the binary in the paths
++ if (!cmd.isEmpty()) {
++ // use full path to avoid security issues
++ cmdline[0] = cmd;
++
++ // an empty list is always passed here (or null)
++ // the initial list is provided/updated using notification after start
++ // since that is what a server is more aware of
++ // and should support if it declares workspace folder capable
++ // (as opposed to the new initialization property)
++ LSPClientServer::FoldersType folders;
++ if (useWorkspace) {
++ folders = QList<LSPWorkspaceFolder>();
++ }
++ server.reset(new LSPClientServer(cmdline, root, realLangId, serverConfig.value(QStringLiteral("initializationOptions")), folders));
++ connect(server.data(), &LSPClientServer::stateChanged, this, &self_type::onStateChanged, Qt::UniqueConnection);
++ if (!server->start()) {
++ QString message = i18n("Failed to start server: %1", cmdline.join(QLatin1Char(' ')));
++ const auto url = serverConfig.value(QStringLiteral("url")).toString();
++ if (!url.isEmpty()) {
++ message += QStringLiteral("\n") + i18n("Please check your PATH for the binary");
++ message += QStringLiteral("\n") + i18n("See also %1 for installation or details", url);
++ }
++ showMessage(message, KTextEditor::Message::Warning);
++ } else {
++ showMessage(i18n("Started server %2: %1", cmdline.join(QLatin1Char(' ')), serverDescription(server.data())),
++ KTextEditor::Message::Positive);
++ using namespace std::placeholders;
++ server->connect(server.data(), &LSPClientServer::logMessage, this, std::bind(&self_type::onMessage, this, true, _1));
++ server->connect(server.data(), &LSPClientServer::showMessage, this, std::bind(&self_type::onMessage, this, false, _1));
++ server->connect(server.data(), &LSPClientServer::workDoneProgress, this, &self_type::onWorkDoneProgress);
++ server->connect(server.data(), &LSPClientServer::workspaceFolders, this, &self_type::onWorkspaceFolders, Qt::UniqueConnection);
++ }
++ } else {
++ // we didn't find the server binary at all!
++ QString message = i18n("Failed to find server binary: %1", cmdline[0]);
+ const auto url = serverConfig.value(QStringLiteral("url")).toString();
+ if (!url.isEmpty()) {
+ message += QStringLiteral("\n") + i18n("Please check your PATH for the binary");
+ message += QStringLiteral("\n") + i18n("See also %1 for installation or details", url);
+ }
+ showMessage(message, KTextEditor::Message::Warning);
+- } else {
+- showMessage(i18n("Started server %2: %1", cmdline.join(QLatin1Char(' ')), serverDescription(server.data())),
+- KTextEditor::Message::Positive);
+- using namespace std::placeholders;
+- server->connect(server.data(), &LSPClientServer::logMessage, this, std::bind(&self_type::onMessage, this, true, _1));
+- server->connect(server.data(), &LSPClientServer::showMessage, this, std::bind(&self_type::onMessage, this, false, _1));
+- server->connect(server.data(), &LSPClientServer::workDoneProgress, this, &self_type::onWorkDoneProgress);
+- server->connect(server.data(), &LSPClientServer::workspaceFolders, this, &self_type::onWorkspaceFolders, Qt::UniqueConnection);
+ }
++
+ serverinfo.settings = serverConfig.value(QStringLiteral("settings"));
+ serverinfo.started = QTime::currentTime();
+ serverinfo.url = serverConfig.value(QStringLiteral("url")).toString();
+--
+GitLab
+