diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts
index f974db170..b749770f4 100644
--- a/share/translations/keepassxc_en.ts
+++ b/share/translations/keepassxc_en.ts
@@ -3095,6 +3095,34 @@ Would you like to correct it?
Failed to decrypt SSH key, ensure password is correct.
+
+ Site
+
+
+
+ Allowed
+
+
+
+ Yes
+
+
+
+ No
+
+
+
+ Deny
+
+
+
+ Allow
+
+
+
+ Are you sure you want to remove this site?
+
+
EditEntryWidgetAdvanced
@@ -3302,6 +3330,22 @@ Would you like to correct it?
Do not send this entry to the browser for HTTP Auth dialogs. If enabled, HTTP Auth dialogs will not show this entry for selection.
+
+ Site Permissions
+
+
+
+ Allow or deny site
+
+
+
+ Allow
+
+
+
+ Remove selected site
+
+
EditEntryWidgetHistory
diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp
index 1d2d010e4..b3049d10f 100644
--- a/src/gui/entry/EditEntryWidget.cpp
+++ b/src/gui/entry/EditEntryWidget.cpp
@@ -47,7 +47,11 @@
#endif
#ifdef WITH_XC_BROWSER
#include "EntryURLModel.h"
+#include "browser/BrowserMessageBuilder.h"
#include "browser/BrowserService.h"
+#include
+#include
+#include
#endif
#include "gui/Clipboard.h"
#include "gui/EditWidgetIcons.h"
@@ -335,6 +339,18 @@ void EditEntryWidget::setupBrowser()
connect(m_additionalURLsDataModel,
SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&)),
SLOT(updateCurrentAttribute()));
+ connect(m_browserUi->sitePermissionsTable->selectionModel(),
+ SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
+ this,
+ SLOT(sitePermissionSelectionChanged()));
+ connect(m_browserUi->sitePermissionsTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(editSite()));
+ connect(m_browserUi->sitePermissionsTable,
+ SIGNAL(itemChanged(QTableWidgetItem*)),
+ this,
+ SLOT(editSiteFinished(QTableWidgetItem*)));
+ connect(m_browserUi->allowDenyButton, SIGNAL(clicked()), SLOT(allowDenyChangedForSite()));
+ connect(m_browserUi->removeSiteButton, SIGNAL(clicked()), SLOT(removeCurrentSite()));
+ connect(m_browserUi->editSiteButton, SIGNAL(clicked()), SLOT(editSite()));
// clang-format on
}
}
@@ -342,6 +358,7 @@ void EditEntryWidget::setupBrowser()
void EditEntryWidget::updateBrowserModified()
{
m_browserSettingsChanged = true;
+ setModified(true);
}
void EditEntryWidget::updateBrowser()
@@ -376,6 +393,27 @@ void EditEntryWidget::updateBrowser()
if (m_browserUi->notHttpAuthCheckbox->isEnabled()) {
changeValue(BrowserService::OPTION_NOT_HTTP_AUTH, m_browserUi->notHttpAuthCheckbox->isChecked());
}
+
+ // Update site permissions
+ if (!m_sitePermissions.isEmpty()) {
+ QJsonArray allowedSites;
+ QJsonArray deniedSites;
+
+ for (const auto& site : m_sitePermissions) {
+ if (site.second) {
+ allowedSites.append(site.first);
+ } else {
+ deniedSites.append(site.first);
+ }
+ }
+
+ QJsonObject sitePermissions;
+ sitePermissions["Allow"] = allowedSites;
+ sitePermissions["Deny"] = deniedSites;
+ sitePermissions["Realm"] = QString();
+ const auto sitePermissionsStr = QString(QJsonDocument(sitePermissions).toJson(QJsonDocument::Compact));
+ m_customData->set(BrowserService::KEEPASSXCBROWSER_NAME, sitePermissionsStr);
+ }
}
void EditEntryWidget::insertURL()
@@ -459,6 +497,153 @@ void EditEntryWidget::entryURLEdited(const QString& url)
{
m_additionalURLsDataModel->setEntryUrl(url);
}
+
+void EditEntryWidget::initializeSitePermissionsTable()
+{
+ if (!m_customData->hasKey(BrowserService::KEEPASSXCBROWSER_NAME)) {
+ return;
+ }
+
+ // Get JSON from custom data
+ const auto sitePermissions = m_customData->value(BrowserService::KEEPASSXCBROWSER_NAME);
+ const auto sitePermissionsJson = browserMessageBuilder()->getJsonObject(sitePermissions.toUtf8());
+ if (!sitePermissionsJson["Allow"].isArray() || !sitePermissionsJson["Deny"].isArray()) {
+ return;
+ }
+
+ m_sitePermissions.clear();
+
+ // Parse allowed URLs
+ const auto allowedUrls = sitePermissionsJson["Allow"].toArray();
+ for (const auto& allowedUrl : allowedUrls) {
+ if (!allowedUrl.isString()) {
+ continue;
+ }
+ m_sitePermissions.append(qMakePair(allowedUrl.toString(), true));
+ }
+
+ // Parse denied URLs
+ const auto deniedUrls = sitePermissionsJson["Deny"].toArray();
+ for (const auto& deniedUrl : deniedUrls) {
+ if (!deniedUrl.isString()) {
+ continue;
+ }
+ m_sitePermissions.append(qMakePair(deniedUrl.toString(), false));
+ }
+
+ m_browserUi->sitePermissionsTable->setColumnCount(2);
+ m_browserUi->sitePermissionsTable->setSelectionMode(QAbstractItemView::SingleSelection);
+ m_browserUi->sitePermissionsTable->setHorizontalHeaderLabels({tr("Site"), tr("Allowed")});
+ m_browserUi->sitePermissionsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+ m_browserUi->sitePermissionsTable->setColumnWidth(1, 100);
+ m_browserUi->sitePermissionsTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
+
+ updateSitePermissionsTable();
+}
+
+void EditEntryWidget::updateSitePermissionsTable()
+{
+ m_browserUi->sitePermissionsTable->setRowCount(m_sitePermissions.size());
+
+ int row = 0;
+ for (const auto& sitePermission : m_sitePermissions) {
+ const auto urlItem = new QTableWidgetItem(sitePermission.first);
+ const auto allowDenyItem = new QTableWidgetItem(sitePermission.second ? tr("Yes") : tr("No"));
+
+ // Allow edit on site only
+ urlItem->setData(Qt::UserRole, row);
+ urlItem->setFlags(urlItem->flags() | Qt::ItemIsEditable);
+ allowDenyItem->setData(Qt::UserRole, row);
+ allowDenyItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
+ m_browserUi->sitePermissionsTable->setItem(row, 0, urlItem);
+ m_browserUi->sitePermissionsTable->setItem(row, 1, allowDenyItem);
+ m_browserUi->sitePermissionsTable->item(row, 1)->setTextAlignment(Qt::AlignCenter | Qt::AlignHCenter);
+ ++row;
+ }
+}
+
+void EditEntryWidget::sitePermissionSelectionChanged()
+{
+ const auto currentItem = m_browserUi->sitePermissionsTable->currentIndex();
+ if (!currentItem.isValid()) {
+ m_browserUi->allowDenyButton->setEnabled(false);
+ m_browserUi->removeSiteButton->setEnabled(false);
+ m_browserUi->editSiteButton->setEnabled(false);
+ return;
+ }
+
+ const auto currentSite = m_sitePermissions.at(currentItem.row());
+ m_browserUi->allowDenyButton->setText(currentSite.second ? tr("Deny") : tr("Allow"));
+ m_browserUi->allowDenyButton->setEnabled(true);
+ m_browserUi->removeSiteButton->setEnabled(true);
+ m_browserUi->editSiteButton->setEnabled(true);
+}
+
+void EditEntryWidget::allowDenyChangedForSite()
+{
+ const auto currentItem = m_browserUi->sitePermissionsTable->currentIndex();
+ if (!currentItem.isValid()) {
+ return;
+ }
+
+ const auto currentSite = m_sitePermissions.at(currentItem.row());
+ const auto newValue = !currentSite.second;
+
+ m_sitePermissions.replace(currentItem.row(), qMakePair(currentSite.first, newValue));
+ m_browserUi->allowDenyButton->setText(newValue ? tr("Deny") : tr("Allow"));
+
+ updateSitePermissionsTable();
+ updateBrowserModified();
+}
+
+void EditEntryWidget::removeCurrentSite()
+{
+ const auto index = m_browserUi->sitePermissionsTable->currentIndex();
+ if (index.isValid()) {
+ const auto result = MessageBox::question(this,
+ tr("Confirm Removal"),
+ tr("Are you sure you want to remove this site?"),
+ MessageBox::Remove | MessageBox::Cancel,
+ MessageBox::Cancel);
+ if (result != MessageBox::Remove) {
+ return;
+ }
+
+ m_sitePermissions.removeAt(index.row());
+ if (m_sitePermissions.isEmpty()) {
+ m_browserUi->allowDenyButton->setEnabled(false);
+ m_browserUi->removeSiteButton->setEnabled(false);
+ m_browserUi->editSiteButton->setEnabled(false);
+ }
+
+ updateSitePermissionsTable();
+ updateBrowserModified();
+ }
+}
+
+void EditEntryWidget::editSite()
+{
+ const auto currentItem = m_browserUi->sitePermissionsTable->currentItem();
+ if (!currentItem) {
+ return;
+ }
+
+ m_browserUi->sitePermissionsTable->editItem(currentItem);
+}
+
+void EditEntryWidget::editSiteFinished(QTableWidgetItem* item)
+{
+ if (item->column() == 0 && item->row() <= m_sitePermissions.size()) {
+ const auto currentSite = m_sitePermissions.at(item->row());
+ if (currentSite.first != item->text()) {
+ m_sitePermissions.replace(item->row(), qMakePair(item->text(), currentSite.second));
+
+ updateSitePermissionsTable();
+ updateBrowserModified();
+ }
+ }
+}
#endif
void EditEntryWidget::setupProperties()
@@ -556,6 +741,8 @@ void EditEntryWidget::setupEntryUpdate()
connect(m_browserUi->addURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->removeURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
connect(m_browserUi->editURLButton, SIGNAL(toggled(bool)), SLOT(setModified()));
+ connect(m_browserUi->allowDenyButton, SIGNAL(toggled(bool)), SLOT(setModified()));
+ connect(m_browserUi->removeSiteButton, SIGNAL(toggled(bool)), SLOT(setModified()));
}
#endif
}
@@ -1145,6 +1332,7 @@ void EditEntryWidget::setForms(Entry* entry, bool restore)
}
}
+ initializeSitePermissionsTable();
setPageHidden(m_browserWidget, !config()->get(Config::Browser_Enabled).toBool());
#endif
diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h
index 3fce4d56d..644d2cd14 100644
--- a/src/gui/entry/EditEntryWidget.h
+++ b/src/gui/entry/EditEntryWidget.h
@@ -1,6 +1,6 @@
/*
+ * Copyright (C) 2026 KeePassXC Team
* Copyright (C) 2010 Felix Geyer
- * Copyright (C) 2021 KeePassXC Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -44,6 +44,7 @@ class QMenu;
class QScrollArea;
class QSortFilterProxyModel;
class QStringListModel;
+class QTableWidgetItem;
#ifdef WITH_XC_SSHAGENT
#include "sshagent/KeeAgentSettings.h"
class OpenSSHKey;
@@ -148,6 +149,11 @@ private slots:
void editCurrentURL();
void updateCurrentURL();
void entryURLEdited(const QString& url);
+ void sitePermissionSelectionChanged();
+ void allowDenyChangedForSite();
+ void removeCurrentSite();
+ void editSite();
+ void editSiteFinished(QTableWidgetItem* item);
#endif
private:
@@ -157,6 +163,8 @@ private:
void setupAutoType();
#ifdef WITH_XC_BROWSER
void setupBrowser();
+ void initializeSitePermissionsTable();
+ void updateSitePermissionsTable();
#endif
#ifdef WITH_XC_SSHAGENT
void setupSSHAgent();
@@ -207,6 +215,7 @@ private:
bool m_browserSettingsChanged;
QWidget* const m_browserWidget;
EntryURLModel* const m_additionalURLsDataModel;
+ QList> m_sitePermissions;
#endif
EditWidgetProperties* const m_editWidgetProperties;
QWidget* const m_historyWidget;
diff --git a/src/gui/entry/EditEntryWidgetBrowser.ui b/src/gui/entry/EditEntryWidgetBrowser.ui
index 7b4d5016b..b62d976fc 100644
--- a/src/gui/entry/EditEntryWidgetBrowser.ui
+++ b/src/gui/entry/EditEntryWidgetBrowser.ui
@@ -6,8 +6,8 @@
0
0
- 374
- 348
+ 532
+ 469
@@ -155,6 +155,84 @@
+ -
+
+
+ Site Permissions
+
+
+
-
+
+
+ Site Permissions
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ QAbstractItemView::SelectRows
+
+
+ false
+
+
+
+ -
+
+
-
+
+
+ false
+
+
+ Allow or deny site
+
+
+ Allow
+
+
+
+ -
+
+
+ false
+
+
+ Remove selected site
+
+
+ Remove
+
+
+
+ -
+
+
+ false
+
+
+ Edit
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+