Omahub
← All plugins
A

OmaqBT

by Aweiward

OmaqBT: qBittorrent in the Omarchy bar, with a quick popup and a full window for search, RSS and settings.

Security review

Potentially dangerous behavior detected · 35 findings

Deterministic scan — not a security guarantee

High
Risk level
High
Analyzed commit
3b31e13
Scanned
21 hours ago
  • high destructive_filesystem …/tests/tst_client_rss_rules.qml:1263

    Destructive operation on the root filesystem or a block device.

    rm -rf /) \"q\" -- --flag\n("
  • high destructive_filesystem …/tests/tst_service_rss.qml:40

    Destructive operation on the root filesystem or a block device.

    rm -rf /) \"q\" -- --flag"
  • high destructive_filesystem …/tests/tst_service_search.qml:61

    Destructive operation on the root filesystem or a block device.

    rm -rf /", "all")
  • high destructive_filesystem …/tests/tst_service_search.qml:65

    Destructive operation on the root filesystem or a block device.

    rm -rf /", "--category", "all"])
  • high persistence qbt:1169

    Bundles a systemd unit file.

    [Unit]
  • Downloads or connects to an external HTTP(S) host.

    ftp://example.org/jackett.py", "ok": false, "message": "Plugin URLs must start with https://.", "why": "another scheme"},
  • medium external_hosts tests/test_rss_rules.py:106

    Downloads or connects to an external HTTP(S) host.

    curl", "https://www.debian.org/security/2026/dsa-6001",
  • medium sudo qbt:1183

    Command runs with sudo, elevating the process beyond the plugin environment.

    sudo cannot prompt. pkexec opens the
  • Augments a command with octal/hex escape sequences.

    \0expect`",
  • Augments a command with octal/hex escape sequences.

    \xffword", "Use valid UTF-8 text.", env=env)
  • Augments a command with octal/hex escape sequences.

    \x80b", b"\xf4\x90\x80\x80", b"\xc0\xaf", b"\xed\xbf\xbf"):
  • Augments a command with octal/hex escape sequences.

    \x00c"):
  • Augments a command with octal/hex escape sequences.

    \0x", secret("nl") + "\n", secret("long") * 80, secret("bad") + "\udcff"):
  • Augments a command with octal/hex escape sequences.

    \x01b"), msg);
  • Augments a command with octal/hex escape sequences.

    \0useRegex`",
  • Augments a command with octal/hex escape sequences.

    \0feedUrl`",
  • Augments a command with octal/hex escape sequences.

    \0enable`",
  • Augments a command with octal/hex escape sequences.

    \0to`",
  • Augments a command with octal/hex escape sequences.

    \0junk").returncode, 0)
  • low obfuscation tests/actions.sh:830

    Augments a command with octal/hex escape sequences.

    \x7fb"], "No control characters in a name."),
  • low obfuscation tests/actions.sh:1187

    Augments a command with octal/hex escape sequences.

    \x1bsuch"], "That tag doesn't exist."),
  • low obfuscation tests/test_rss.py:177

    Augments a command with octal/hex escape sequences.

    \xffian").stdout.decode(), S["nameControl"])
  • low obfuscation tests/test_rss.py:374

    Augments a command with octal/hex escape sequences.

    \0junk").returncode, 0)
  • Augments a command with octal/hex escape sequences.

    \xffjackett.py")
  • Augments a command with octal/hex escape sequences.

    \xffian").stdout.decode(), "Use a search without control characters.")
  • Augments a command with octal/hex escape sequences.

    \0useRegex`. `key` is `mustContain` or `mustNotContain` (the `regex` kind with `useRegex`), `episodeFilter` (`episode`), `ignoreDays` (`ignoreDays`) or `savePath` (`savePath`); `useRegex` is `true` or
  • Augments a command with octal/hex escape sequences.

    \0feedUrl`. `name` goes through the `ruleName` rule ("Enter a rule name.", "Rule names can't contain control characters."); a name `rss/rules` already has prints "There's already a rule called <name>.
  • Augments a command with octal/hex escape sequences.

    \0enable`:
  • Augments a command with octal/hex escape sequences.

    \0to`. `to` goes through the `ruleName` rule; the window skips an unchanged name without calling qbt (qbt answers `{"ok": true, "name": to}` without a request for it too). `from` missing prints "That 
  • Augments a command with octal/hex escape sequences.

    \0b\0` is three, the last empty);
  • Augments a command with octal/hex escape sequences.

    \0guid`. It reads `rss/items?withData=true` and prints `{"text": "...", "truncated": false}`: that article's `description` through the `articleText` rule. A missing feed or article prints "That articl
  • Augments a command with octal/hex escape sequences.

    \0path`, where `path` is the new feed's full path. It checks `url` with the `feedUrl` rule and the new (last) segment of `path` with the `name` rule (the parent folder is taken as `rss/items` gave it)
  • Augments a command with octal/hex escape sequences.

    \0to`. The window skips an unchanged rename (the same name) without calling qbt. `to` must be in the same folder as `from` (moveItem is used for renames only); otherwise the usage line. It POSTs `rss/
  • Augments a command with octal/hex escape sequences.

    \0expect`. `guid` is `""` for a whole feed or folder, and `path` is `""` for everything (Unread and All). `expect` is the unread count the window's confirm named (a whole number; `0` with a `guid`).
  • Augments a command with octal/hex escape sequences.

    \0link`. It applies the `hasTorrent` rule ("This article has no torrent link." or "That link isn't http, https or magnet."), then POSTs `torrents/add` with `urls=torrentURL` and prints `{"ok": true, "

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
3b31e13
Reviewed
21 hours ago

The plugin is a legitimate qBittorrent client for the Omarchy bar. The high-risk deterministic findings are all in test files, fixtures, and documentation (e.g., `rm -rf /` in test snippets, external hosts in test data), not in the plugin's runtime code. The plugin's actual behavior—installing qbittorrent-nox via pkexec, writing a systemd user service, and modifying qBittorrent config—is clearly documented and user-initiated.

  • The plugin installs a system package (qbittorrent-nox) via pkexec and writes a systemd user service, which requires elevated privileges and modifies system state, but this is explicitly disclosed and triggered by user action.
  • The plugin modifies qBittorrent's configuration file (WebUI keys, API key) and may restart the daemon, which is documented and necessary for its function.
  • The deterministic scan flagged high-risk items in test files and documentation; these are not part of the executable plugin code and should not be considered actual threats.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/Aweiward/omaqbt --enable
System #bar
<p align="center"><img src="assets/logo/omaqbt-knockout.svg" width="96" alt="OmaqBT logo"></p>

OmaqBT

A themed Omarchy Quattro bar widget for qBittorrent. The mark shows live ↓/↑ speeds while anything is transferring, and a desktop notification fires when a download finishes. Left-click the mark for a quick popup: watch live transfers, add a magnet or a .torrent file, start or stop, remove a torrent, and set file priorities. For everything else, open the window, a full view with the torrent table, Search, RSS and Settings. Right-click starts or stops everything. The official Qt app stays as an escape hatch.

OmaqBT talks to qbittorrent-nox on your existing ~/.config/qBittorrent library through the local Web API. It does not talk to any host other than 127.0.0.1.

License: MIT.

OmaqBT on the Omarchy bar

OmaqBT torrent detail and actions

Install

omarchy plugin add https://github.com/Aweiward/omaqbt.git --enable

If qbittorrent-nox is missing, open the widget and click Install qBittorrent-nox. That runs pkexec omarchy pkg add qbittorrent-nox (Arch extra, polkit password prompt) and then starts the user service omaqbt-nox.service. It will not remove desktop qbittorrent if you already have it.

Close the Qt qBittorrent window before starting the daemon. Stop the daemon before opening the Qt app. They share one profile and must not run at the same time.

Connect Mullvad before you start the daemon if you want traffic bound to the VPN. The bind only happens when wg0-mullvad is already up. If nox starts with Mullvad down, it stays unbound until the next start.

omarchy bar move aweiward.omaqbt --section right

Usage

The popup

  • Left click: open or close the popup
  • Right click: start or stop all torrents
  • Middle click: refresh
  • Esc: close the popup

Under the header sits an Open the window row with a w hint. w, Enter on the row, or a click closes the popup and opens the window (or brings it up if it is already open).

While a torrent is downloading or seeding, compact ↓/↑ speeds appear next to the bar mark (horizontal bars only; hover for exact rates). When a download reaches 100% between two polls, a desktop notification fires through notify-send. Already-finished torrents never re-notify, including on shell restart.

If Mullvad (or QBT_BIND_IFACE) is up but the running daemon is not bound to it, the mark shows the warning badge and the popup offers Restart daemon to bind. Restarting writes the bind keys and brings the daemon back on the tunnel.

Clicking a magnet: link in a browser opens the popup, or the window when it is open (after the browser’s own “open xdg-open?” prompt, if any). The torrent is added so metadata can load, then stopped. The confirm row shows the name (and size when known). Enter starts it. Esc cancels and deletes it. Paste, y, and drag-drop are unchanged.

List keys: j/k move, Enter opens the torrent's detail, Space start/stop, o open the save folder, x remove (keep files, no confirm), X delete files (asks first), t start/stop all, s cycle sort (default → speed → eta → added), z turtle mode, a/p/c/* filter, / magnet field, y add clipboard magnet, r refresh. On detail: y copy magnet, m move, e recheck. While a browser magnet is waiting, Enter starts it and Esc cancels it (unless the paste field is focused).

The field takes a magnet, a .torrent URL, or a local .torrent path (/…, ~/…, or file://…). Once it holds something addable, a Save to… field and an Add stopped row appear: Enter adds and starts, Add stopped adds without starting, and the save path overrides qBittorrent’s default when filled. Dropping a .torrent file or magnet link onto the open popup adds it too. From a terminal, qbt add also accepts --category <name>.

Typing anything that is not addable filters the list by name; Esc clears the filter first, then closes.

File view keys: j/k move, Enter cycle priority, Space start/stop, Backspace, Left or h back. On the files section x skips the file under the cursor. Elsewhere in the detail view x removes the torrent (keep files) and X deletes its files (asks first).

On a torrent’s detail view, Copy magnet (y) writes the magnet to the clipboard. Move to… (m) opens a path field prefilled with the current save folder; Enter moves the files there (qBittorrent setLocation), Esc cancels the field. Force recheck (e) starts a hash check immediately. Those three keys do nothing on the list (y there still adds from the clipboard).

Turtle mode (z, or the row in the list) toggles qBittorrent’s alternative speed limits; the header shows “turtle” while it is on. Configure the alternative rates themselves in qBittorrent.

On a torrent’s detail view, size, ratio, seeds/peers, the added date, and the save path sit under the title, followed by Open folder, Copy magnet, Move to…, Force recheck, Remove, keep files and Delete files. Below the file list controls, clickable rows cycle the per-torrent download/upload limit (∞ → 8M → 4M → 1M → 256K), toggle sequential download, and cycle the seed ratio limit (global → 1.0 → 2.0 → none). Open folder (o) opens the save path in your file manager. Remove takes it out of the list and leaves the download on disk. Delete asks first, then removes the torrent and its files.

The window

The window is a separate, full-size view. Open it from the popup (the Open the window row or w), or from a terminal:

omarchy-shell shell toggle aweiward.omaqbt

That command toggles, so run it again to close the window.

Keybind. To open it from the keyboard, add this line to ~/.config/hypr/bindings.conf:

bindd = SUPER SHIFT, Q, OmaqBT window, exec, omarchy-shell shell toggle aweiward.omaqbt

Pick another key if Super+Shift+Q is taken.

The window shows one view at a time: the torrents (the default), Settings, Search and RSS. From the torrents:

  • F opens Search
  • N opens RSS
  • , opens Settings
  • : opens the command palette
  • ? opens help

Inside RSS, R opens Rules, the auto-download rules.

? lists each view's keys. Esc steps back out of a view.

Configure

The only plugin setting is refreshIntervalSec (default 5) on the widget entry in ~/.config/omarchy/shell.json.

OmaqBT polls through qbt-serve, a small long-lived Python helper (standard library only, no build step) that the shell starts once and shares across every bar, including one bar per monitor. It keeps one WebUI session and asks qBittorrent only for changes. If it can't run, the widget falls back to polling with qbt status. refreshIntervalSec sets its poll rate, and it polls every 250 ms while a browser magnet is waiting.

The polling runs as a plugin service, so while the plugin is enabled it keeps polling, handling browser magnets, and sending finish notifications even if the widget isn't placed on the bar. Disable the plugin to stop it.

Starting the daemon writes these keys under [Preferences] in ~/.config/qBittorrent/qBittorrent.conf if you click Install or Start daemon:

WebUI\Enabled=true
WebUI\Address=127.0.0.1
WebUI\LocalHostAuth=true
WebUI\AuthSubnetWhitelistEnabled=false
WebUI\APIKey=<generated if missing>
WebUI\Port=<existing port, or 8080>

OmaqBT signs in to the Web API with qBittorrent's API key. It reads the key from qBittorrent.conf, keeps that file readable only by you, and never puts the key on a command line, so other accounts on your machine can't use the Web API without it. Older OmaqBT versions turned localhost login off. If your daemon was set up that way, OmaqBT secures it automatically: it writes the keys above and restarts the daemon once. If qBittorrent ever refuses the key, the popup shows Restart daemon. Other tools that relied on the old localhost bypass (scripts, *arr apps) now need the API key or a Web UI login. Tracker URLs, magnets and download links, which can carry a private tracker's passkey, stay off command lines too: the widget hands them over on stdin. There are two exceptions. A magnet your browser opens reaches qbt magnet-inbox on its command line for the moment it takes to save it to the inbox. And when you download a search result through its plugin, qBittorrent itself runs the plugin's downloader (nova2dl.py) with the link on its command line, as it does from its own interface.

The browser Web UI at http://127.0.0.1:<port> now asks for a username and password. If you haven't set a password, qbittorrent-nox prints a temporary one each time it starts; read it with journalctl --user -u omaqbt-nox. Sign in with it, then set your own under Tools → Options → Web UI.

If wg0-mullvad is present (or QBT_BIND_IFACE is set), starting the daemon also writes under [BitTorrent]:

Session\Interface=<iface>
Session\InterfaceName=<iface>
Session\InterfaceAddress=

That binds the tunnel interface, not a single relay IP, so a Mullvad city change does not stall announces.

Nothing else in that file is rewritten by the plugin. The window's views write to qBittorrent when you change something there: preferences in Settings, feeds and rules in RSS, categories, speed limits, and search plugins. qBittorrent saves those changes itself. The plugin never stores a Web UI password.

Remove

omarchy plugin remove aweiward.omaqbt

Restore the Qt magnet handler before removing the plugin:

qbt magnet-uninstall-handler

qbt here is the helper in the plugin checkout. That points magnet: back at org.qbittorrent.qBittorrent.desktop when the Qt app is installed, and removes ~/.local/share/applications/omaqbt-magnet.desktop.

If the plugin is already gone:

xdg-mime default org.qbittorrent.qBittorrent.desktop x-scheme-handler/magnet

That disables the widget and deletes the plugin checkout. It does not uninstall qbittorrent-nox, stop omaqbt-nox.service, delete torrents, or revert the Web UI keys above, so localhost login stays on and the API key stays in qBittorrent.conf. It also does not restore the magnet handler unless you ran uninstall first.

To stop the daemon yourself:

systemctl --user stop omaqbt-nox.service

Requirements

  • Omarchy 4 (Quattro) / omarchy-shell
  • qbittorrent-nox 5.2+ (installed from the popup if missing)
  • On PATH for the helper: curl, jq, python3
  • notify-send (libnotify) for completion notifications; without it they are skipped silently
  • xdg-open (xdg-utils) for Open folder
  • pkexec only when installing the package from the popup (no TTY for sudo)
  • systemctl --user for omaqbt-nox.service

What this plugin does on your system

  • Runs qbt from the plugin folder. That helper is the only process that talks HTTP, and only to 127.0.0.1.
  • Installs the Arch extra package qbittorrent-nox through omarchy pkg add when you click Install. Privilege is pkexec, not a sudoers rule.
  • Writes ~/.config/systemd/user/omaqbt-nox.service and enables it as your user.
  • Writes the localhost Web UI keys listed under Configure, including an API key when there isn't one. It stops the daemon first so qBittorrent does not overwrite those keys on exit.
  • If wg0-mullvad is up, also writes the [BitTorrent] interface keys so qBittorrent binds the tunnel, not a single relay IP.
  • Stores sync state in $XDG_RUNTIME_DIR/omaqbt/ (private, mode 700). If that variable is unset it falls back to a uid-scoped /tmp/omaqbt-<uid>, created with umask 077, and refuses to write through a symlink or a directory it does not own.
  • Sends a desktop notification through notify-send when a download completes, and when a browser magnet arrives but the popup could not open.
  • On widget load, writes ~/.local/share/applications/omaqbt-magnet.desktop and claims x-scheme-handler/magnet. Chromium may still ask to open xdg-open; that is a browser prompt, not a bug in this plugin.
  • Stores pending browser magnets in ${XDG_STATE_HOME:-$HOME/.local/state}/omaqbt/ (not the runtime rid dir).
  • Writes the qBittorrent preferences you change in the window's Settings view, and its RSS feeds and rules. Once you turn a rule on, and auto-download on, RSS can add torrents by itself.
  • Apart from the RSS rules you turned on, and the one restart that secures a daemon set up by an older OmaqBT, it does not add torrents, delete files, or start the daemon unless you click or press the matching control, or click a magnet: link that this handler claimed.

Dev

npm test
npm run test:qml
python3 -m unittest discover -s tests -p 'test_*.py'
tests/actions.sh
tests/api-contract.sh
tests/magnet-handler.sh
tests/serve-parity.sh
npm run lint:qml
node tools/gen-settings-schema.js --check
omarchy plugin validate .

npm run test:qml turns off Qt's incremental garbage collector (QV4_GC_TIMELIMIT=0), because a Qt 6.11 bug could leave a newly created QML object empty.

tests/api-contract.sh talks to a fixture HTTP server. It does not start qbittorrent-nox, add a real torrent, or delete files on disk.