Skip to content

Implements Global URL Scheme Overrides - #13544

Open
Shkarlatov wants to merge 16 commits into
keepassxreboot:developfrom
Shkarlatov:feature/url_overrides
Open

Shkarlatov wants to merge 16 commits into
keepassxreboot:developfrom
Shkarlatov:feature/url_overrides

Conversation

@Shkarlatov

Copy link
Copy Markdown

Implements Global URL Scheme Overrides, similar to KeePass 2.x's "URL Overrides" feature (see #10887). Adds a new Settings page where users can map a URL scheme (e.g. ssh, kdbx, ftp) to an external cmd:// command template. When an entry's URL matches an enabled rule's scheme, KeePassXC runs that command (with the existing confirmation dialog) instead of opening the URL in the browser.

Implementation notes

  • The feature lives in a new optional subproject, src/urloverride/ (static library urloverride), gated by a new KPXC_FEATURE_URLOVERRIDE CMake option (default ON, disabled under KPXC_MINIMAL) — same pattern as KPXC_FEATURE_BROWSER/SSHAGENT/FDOSECRETS.
  • DatabaseWidget::openUrlForEntry() checks the configured rules for the entry's URL scheme before falling back to the existing cmd:///kdbx:///default-browser handling. A new "URL Overrides" page is registered in the Settings dialog.
  • Scheme matching is a literal, case-insensitive comparison against the URL's own scheme (QUrl::scheme()) — not a regular expression — so ssh never accidentally matches sshfs. The first enabled rule with a non-empty command wins; a rule with an empty command is skipped rather than blocking a lower-priority rule for the same scheme.
  • A disabled example rule (ssh → cmd://ssh {USERNAME}@{URL:HOST}) is seeded once on first use, so the feature and its placeholder syntax are discoverable in the settings page without doing anything until a user explicitly enables it.
  • Saving changes to the rule table shows a confirmation dialog first, since these rules can cause arbitrary commands to run when a URL is opened.
  • On Windows, launching a cmd:// command now detects console-subsystem targets (e.g. ssh.exe) via their PE header and forces a new console for them specifically, so they get a visible window and can prompt for passwords/host-key confirmations interactively — while GUI targets (e.g. a browser) are left unaffected.

Screenshots

Not included — built and tested in a headless CI-style environment, no screen capture available for this session. UI surface added is a single new "URL Overrides" page under Settings: a 3-column table (Enabled / URL Scheme / Command) with Add / Remove / Move Up / Move Down buttons and an explanatory label.

Testing strategy

  • New testurloverride unit test target (guiless, built only when KPXC_FEATURE_URLOVERRIDE is on, same as testbrowser/testsshagent), covering:
    • rule list persistence (order, disabled entries, overwriting with a shorter/empty list, distinguishing "never configured" from "explicitly saved as empty")
    • the one-time seeded example rule, and that it never matches on its own (disabled)
    • scheme normalization (ff:// / ff: / ff all normalize to ff — confirming it's not treated as a regex)
    • matching logic: literal case-insensitive comparison, first-match-wins ordering, empty scheme/command being skipped (not blocking lower-priority rules), URLs with no scheme never matching
    • special characters (&, <, >, ", ') in a command surviving a save/load round-trip
  • Manually tested end-to-end on Windows: adding a rule for ssh pointing at C:\Windows\System32\OpenSSH\ssh.exe, confirming the execute-command dialog appears, and confirming a real visible console window opens (verified the Windows console-allocation behavior specifically, since that requires real process/console state and isn't meaningfully unit-testable).
  • The settings page's widget logic (table CRUD, save-confirmation gating) is not covered by an automated GUI test — consistent with the rest of the settings pages in this codebase (ShortcutSettingsPage, BrowserSettingsPage), none of which have dedicated GUI tests either.

Introduces the CMake option for an optional global URL scheme override
feature, following the existing KPXC_FEATURE_BROWSER/SSHAGENT/FDOSECRETS
pattern: option declaration, KPXC_MINIMAL opt-out, feature summary, and
the config-keepassx.h define. No consumer yet.
New optional static library (src/urloverride, built only when
KPXC_FEATURE_URLOVERRIDE is on) that maps a URL scheme (e.g. "ssh",
"kdbx") to an external "cmd://" command template, plus a settings page
to manage the rule table (enable, scheme, command, reordering, with a
confirmation prompt before saving changes).

- UrlOverride::getRules/setRules persist the rule list as an XML string
  under a single new Config key (UrlOverride_Rules), the same way
  KeeShare stores its structured settings - no other change to
  core/Config, and no second QSettings instance on the config file.
- UrlOverride::findCommand does a literal, case-insensitive match of a
  rule's scheme against the URL's own scheme (not a regex), first
  enabled match with a non-empty command wins.
- A disabled example rule ("ssh" -> "cmd://ssh {USERNAME}@{URL:HOST}")
  is seeded once, on first-ever use, purely for discoverability.
- UrlOverride::executeCommand launches the resolved command; on
  Windows it detects console-subsystem targets (e.g. ssh.exe) via the
  PE header and forces a new console so they get a visible window,
  without doing so for GUI targets like a browser.
DatabaseWidget::openUrlForEntry() now checks UrlOverride::findCommand()
for the entry's URL scheme before falling back to the existing
cmd://, kdbx:// and default-browser handling, and the existing cmd://
launch path now goes through UrlOverride::executeCommand() to pick up
the Windows console-visibility fix. Both are guarded by
KPXC_FEATURE_URLOVERRIDE with an unchanged fallback when the feature
is built out.

MainWindow registers the new "URL Overrides" settings page alongside
the existing Shortcuts page.
New guiless testurloverride target (built only when
KPXC_FEATURE_URLOVERRIDE is on, like testbrowser/testsshagent), covering:
- rule list round-trip through Config, including order, disabled
  entries, and overwriting with a shorter/empty list
- the one-time disabled example rule seeded on first use
- scheme normalization (not a regex: "ff://" stores as "ff")
- literal case-insensitive scheme matching, first-match-wins ordering,
  empty scheme/command being skipped rather than blocking lower rules,
  and URLs with no scheme never matching
- XML special characters in a command surviving the save/load
  round-trip
@droidmonkey

droidmonkey commented Jul 24, 2026 •

Copy link
Copy Markdown
Member

This should not be gated behind a build flag. It should be directly incorporated into the application settings.

Was this generated by AI (I assume so given the writeup and headless development).

Did you test this yourself (as a human)? Why no screenshots?

@Shkarlatov

Copy link
Copy Markdown
Author

Yes, I mostly used AI, but I was in complete control of it.

That feature was the only thing that kept me using Keepass2.

изображение изображение

@Shkarlatov

Copy link
Copy Markdown
Author
clideo_editor_18f556db45e34a5a97276f6a5fc98462

@droidmonkey

Copy link
Copy Markdown
Member

Thank you

@varjolintu varjolintu added the pr: ai-assisted Pull request contains significant contributions by generative AI label Jul 25, 2026
Comment thread src/urloverride/UrlOverride.cpp Resolved
…-form input

Extracts the URI scheme grammar (ALPHA *(ALPHA / DIGIT / "+" / "-" / ".")
per RFC 3986) from a string instead of guessing which surrounding
characters to strip. Handles messy input (stray leading characters,
doubled-up separators, trailing garbage that isn't ":"/"/") that a
naive trim-based approach would get wrong.
UrlOverride::normalizeScheme() duplicated logic that belongs in
UrlTools, a general-purpose URL utility namespace used elsewhere in
the app. Drop the plugin's own copy and call UrlTools::normalizeScheme()
instead; the plugin's own tests now only cover that setRules() applies
it before persisting, the extraction grammar itself is covered by
TestUrlTools.
@Shkarlatov

Copy link
Copy Markdown
Author

Linux: no automatic terminal handling

Unlike Windows, we intentionally do not attempt to detect or wrap console-subsystem targets on Linux. ELF binaries have no PE-style subsystem flag, and there's no OS-level "hidden console" to reveal — QProcess::startDetached() simply inherits the parent's stdin/stdout/stderr, which for a normally-launched desktop app are typically not a real TTY. As a result, interactive commands (e.g. ssh prompting for a password or host-key confirmation) will generally fail fast rather than work, the same underlying issue as on Windows, just surfacing differently (no window vs. no TTY).

If a rule needs an interactive terminal on Linux, the recommended workaround is to specify a terminal emulator explicitly in the command, e.g.:

cmd://xterm -e ssh {USERNAME}@{URL:HOST}

We're deliberately not auto-detecting a terminal emulator for now (no single portable way to do this across distros/DEs). Once xdg-terminal-exec (part of the XDG spec) is more broadly available, that would be the natural, portal-friendly way to implement this properly instead of guessing which terminal emulator is installed.

isConsoleSubsystemExecutable() was checked against the PATH-resolved
program, but QProcess was then started with the original, unresolved
name, so the console-subsystem check and the launched binary could
diverge.
Add bounds checks that reject malformed or truncated executables
before trusting offsets/sizes taken from the file itself: e_lfanew
is validated against the file size before seeking to it, and
SizeOfOptionalHeader is checked against both the declared and the
actual IMAGE_OPTIONAL_HEADER32/64 size before reading it. Prevents
misreading Subsystem out of a corrupt or truncated PE.
Add a link to the Entry Placeholders documentation section instead of
listing placeholder examples inline.
@StaticBR

Copy link
Copy Markdown

I would really like to see this feature.
Thanks @Shkarlatov for the work.
Is there a way I can assist to get this PR to be ready for a merge?

…ides

# Conflicts:
#	src/gui/DatabaseWidget.cpp
@StaticBR

Copy link
Copy Markdown

Thanks @Shkarlatov for resolving the merge conflicts!

@varjolintu anything i can support you with to get merge ready?

@varjolintu

Copy link
Copy Markdown
Member

Thanks @Shkarlatov for resolving the merge conflicts!

@varjolintu anything i can support you with to get merge ready?

No. We will review pull request when we have time. Pinging us doesn't speed it up, unless it's some high priority fix.

@droidmonkey

droidmonkey commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Moving to draft until this is fixed:

This should not be gated behind a build flag. It should be directly incorporated into the application settings.

@droidmonkey
droidmonkey marked this pull request as draft October 14, 2026 03:11
Address review feedback: URL scheme overrides are no longer gated
behind a build flag and are part of the application settings.

* Remove the KPXC_FEATURE_URLOVERRIDE option and the urloverride
  library; UrlOverride now lives in the gui sources
* Replace the separate URL Overrides settings page with a
  "URL Overrides" tab in General settings
* Ignore rules whose command does not start with cmd://, since such a
  command would be opened as a regular URL instead of being run
* Shorten the explanation above the rules table
* Use the project's license header in the UrlOverride sources
@Shkarlatov

Shkarlatov commented Oct 14, 2026 •

Copy link
Copy Markdown
Author

Thanks for the review! The feature is no longer gated behind a build flag and is now part of the application settings.

Changes

  • Removed the KPXC_FEATURE_URLOVERRIDE CMake option (including the KPXC_MINIMAL handling, INSTALL.md and config-keepassx.h) and the separate urloverride static library. UrlOverride now lives in src/gui/ and is always built.
  • DatabaseWidget and MainWindow have no #ifdefs left. The diff of MainWindow.cpp against develop is now empty.
  • The rules editor is no longer a separate settings page. It is now a URL Overrides tab in Settings → General, next to "Basic Settings" and "Auto-Type", and is loaded and saved together with the other general settings in ApplicationSettingsWidget.
изображение

Other fixes

  • Rules whose command does not start with cmd:// are now ignored. Before, such a rule would replace the entry URL and the command would be opened in the browser as a regular URL instead of being run. Covered by a new case in TestUrlOverride.
  • Shortened the explanation above the rules table to two lines. It still links to the Entry Placeholders documentation.
  • keepassxc_en.ts updated with lupdate.

Note: UrlOverride::executeCommand() is used for every cmd:// URL, not only for override rules. On Windows, console programs started from regular cmd:// entries now also get their own console window, so interactive tools like ssh work there as well.

Testing

  • Windows (MSVC, Qt 6.8.3) and Linux (Ubuntu 22.04, GCC 14, Qt 6.2.4) builds pass.
  • testurloverride and testurltools pass on both platforms. TestGui::testSettingsDefaultTabOrder passes with the new tab.
  • Manually tested the new settings tab and opening ssh:// entries through an override rule.

@Shkarlatov
Shkarlatov marked this pull request as ready for review October 14, 2026 08:18
@droidmonkey droidmonkey added this to the v2.9.0 milestone Oct 14, 2026
@droidmonkey droidmonkey added user interface ux pr: new feature Pull request adds a new feature labels Oct 14, 2026
@Shkarlatov

Copy link
Copy Markdown
Author

CI note: the macOS failure (Release [MacOS] #9282) is not related to this PR. The macOS agent has a newer Homebrew Qt that removed the QTEST_DISABLE_KEYPAD_NAVIGATION macro, so tests/gui/TestGui.cpp fails to compile on develop as well.

This is already fixed in fb90668 ("Additional fixes for macOS and modern Qt6") on fix/compat-signal-warnings, and macOS builds pass on that branch. Once it is merged into develop, I'll merge develop into this branch and re-run CI.

…ides

# Conflicts:
#	src/gui/ApplicationSettingsWidget.cpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr: ai-assisted Pull request contains significant contributions by generative AI pr: new feature Pull request adds a new feature user interface ux

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants