Repository navigation
Releases
v1.5.0
Compatibility
| Axis | Version |
|---|---|
pluginApi |
1.0.0 (unchanged) |
| Capabilities | all 1.0.0, unchanged |
| Manifest schema | + optional sync.paths (additive) |
bundleFormat |
1.0.0 (unchanged) |
| Vault schema | 5 (was 4) |
| Vault envelope | 1 (unchanged) |
| Audit schema | 1 (unchanged) |
syncProtocol |
1.0.0 (new) |
| Sync crypto suite | 1 (new) |
| Sync record schemas | 1 for every record type (new) |
Sync state schema (data/sync/state.db) |
1 (new) |
Server store schema (xqsyncd.db) |
2 (new; a database from a pre-release build at 1 is copied aside as xqsyncd.db.v1.bak and migrated on first start) |
BREAKING
Vault schema 4 → 5. The first unlock after the upgrade copies the vault aside as vault.age.v4.bak and rewrites it as schema 5, which records the team vault each synced item belongs to. xQuakShell 1.4 and older refuse a schema 5 vault rather than open it and drop that field; to go back, restore the backup. Nothing about keys or passwords changes in the upgrade, and no passphrase is asked for.
Added
Sync between your devices, through a server you run. Settings → Sync connects a vault to xqsyncd, the self-hosted sync server that ships with this release for Windows and Linux (admin guide). Everything is encrypted and signed on the device; the server cannot read a host name, a password or a folder name, cannot alter a record, and cannot roll a vault back or add a device without every device noticing. Signing in is one screen: paste the connect link the server prints (https://host:8443/#fp=…) and the application checks the server's fingerprint itself, or enter the address and compare the fingerprint it shows with the one its operator gave you. Fingerprints, the device list, the sync password and Shred now sit under Advanced. A second device signs in with the sync password; every other device announces it and can remove it, which replaces the account keys. Removing a device you do not recognise can replace the sync password in the same step, so whoever typed it to add that device cannot sign it in again; left empty, the device is still removed at once and the password can be changed afterwards. Edits made offline on two devices merge field by field; the same credential edited on both becomes a conflict you resolve, and the sync indicator turns red and says so until you do. A change from another device that would weaken this one — turning off plugin signature checks, loosening the lockout, replacing or deleting a known host key, a peer identity or a key — waits for this device's master password, in a team vault as in the personal one. Sync is off until you turn it on, and needs a master password of at least 12 characters; a new sync password needs at least 12 too.
Teams. A team vault shares connections with other accounts on the same server. You invite a person after comparing their fingerprint over another channel, and give them the admin, editor or viewer role. Nobody has to read the fingerprint aloud: each person copies their contact code (xqs-member:name#fp=…) from the Teams page and sends it in a chat or a mail, and pasting it into the invite field — or into a member's row, to verify an existing member — makes the application compare it with the key the server gives for that account. A code that does not match leaves nothing to invite. Being invited is not joining: the invited person sees the invitation on their Teams page with the inviter's fingerprint, and nothing from the team reaches their connections until they accept it; declining leaves the team. A team accepted on one device is joined on all of them. Credentials are not shared: each member binds their own key or password to a team connection, and a member who changes a shared connection's address suspends everyone else's binding to it until they confirm. Removing a member moves the team to new keys, so nothing written afterwards opens for them. Each team appears in the connection tree under its own row, below your personal connections; create a connection or folder there to keep it in the team, or move one in and out from the tree's menu, the editor's "Stored in" field, or by dragging it onto another vault. A move into a team links your own key or password for you, and a move back takes that link with it as the connection's credential. A viewer sees the team's connections and can use them, but cannot add, change or move them.
The sync server as a container image. Every release also publishes xqsyncd to ghcr.io/teoritty/xqsyncd for linux/amd64 and linux/arm64, tagged with the version, its minor and major lines and latest; a pre-release gets its own version only. The image is the static server binary on a distroless base, running unprivileged with its data on a volume. xqsyncd serve --init initialises an empty data directory before serving and leaves an initialised one untouched, so a container needs one command; a Compose file and the backup routine for a containerised server are in the admin guide.
Trash and Shred now. Deleted synced items stay in a trash for the vault's retention window (30 days by default) and can be restored. After that they are purged from the server, and within one and a half more windows the keys that covered them are destroyed on every device. Shred now does all of it at once.
Plugins from your other devices. A plugin installed on one device is offered on the others — never installed by itself. Installing the offer runs the usual download, checksum, signature and consent steps and then checks that the build is exactly the one your other device installed; a release replaced behind the same tag is refused unless you accept it as a new version. A plugin may declare files of its own (sync.paths in plugin.json) that follow you between devices; they are written only while the plugin is stopped, and the Sync page shows what waits for a restart.
Search across terminals. Ctrl+Shift+F turns the sidebar into a search view, the way an editor's search replaces its file tree: a query box with match case, whole word and regular expression switches, the terminals to search, and every match grouped by terminal underneath. Enter and Shift+Enter walk the matches, bringing each one's terminal to the front with the match scrolled into view and selected; Escape hands the keyboard back to the terminal and brings the connection tree back exactly as it was. Every match is tinted inside the terminals too, the current one brighter. The search covers the whole scrollback, finds a word even where the terminal wrapped it onto the next line, and follows new output while the panel is open. It works in SSH sessions, local terminals and plugin terminals alike. It starts on the terminal you were in, and opening it with text selected searches for that text.
The binding is not Ctrl+F, because inside a terminal Ctrl+F already belongs to the program running there — moving the cursor in bash and zsh, paging down in vim, less and man. It can be rebound in Settings → Hotkeys, to Ctrl+F as well for anyone who prefers it.
Typing into several terminals at once. Alt+M outlines every open terminal in red and puts a number on it; click a terminal or press its number to choose it — it turns green — and press Enter. From then on whatever you type into one of the green terminals reaches all of them, pastes included, and the green outline stays on them so it is always clear what a keystroke will touch. Typing into a terminal outside the group reaches that terminal alone. A terminal hidden behind another tab is chosen by its tab, which carries the same number. In the picker, A chooses all and N none; Escape leaves everything as it was. Alt+M again changes the group. Alt+Shift+M ends it — Escape cannot, because it belongs to whatever runs in the terminal — as do Stop in the top bar and Enter in the picker with fewer than two chosen; the rest of the time Alt+Shift+M reaches the terminal as usual. A terminal that closes leaves the group, and a group down to one ends on its own. On SSH sessions each command is recorded in the audit log of every server it reached.
All three shortcuts are listed on the start screen with the others, and can be changed in Settings → Hotkeys, where a clash with another shortcut is reported before saving.
Duplicating a saved connection. Right-click a connection and choose Duplicate: an exact copy appears directly beneath it, named " - copy" in the interface language ("- копия" in Russian), then "- copy 2", "- copy 3" as those are taken — duplicating a copy numbers it rather than adding another suffix. With several connections selected, each is duplicated. The copy uses the same saved passwords and keys, as two connections set up by hand to share them would; everything it owns is its own, so editing or deleting either one leaves the other as it was. Port forwards are copied with ids of their own, and a plugin connection's secret fields are copied into the vault under the new connection rather than shared with the original.
Reconnecting on its own after a dropped connection. When an established session loses its connection, the Reconnect button starts counting down with a spinner in it and reconnects when it reaches zero: after 5 seconds, then 15, then 30, then every 60 seconds until the connection is back. Clicking the button reconnects straight away, Stop retrying ends the countdown and leaves the tab as it is, and closing the tab ends it too. The reconnected session keeps its tab's place, its tile and the focus, and by default the terminal output stays: the scrollback and the screen are kept, a dim "Reconnected" line marks where the new session begins, and the new shell carries on below. Only the output is kept - programs that were running on the server do not survive the dropped connection. It works the same for SSH and for protoco...
Nightly nightly-20261104-5194445
Automated build of main at 5194445cf365287df180a508fd809c560b60e54f, published as nightly-20261104-5194445.
This release is replaced by the next nightly. Its download links stay valid only
until then; for a build that keeps its files, use a tagged release.
What's Changed
- fix(sync): team credentials, conflict indicator, retry and device removal; IPv6 addresses by @teoritty in #132
- build(deps): bump github.com/labstack/echo/v4 from 4.15.3 to 4.16.3 in the go-security group across 1 directory by @dependabot[bot] in #126
- build(deps): bump the github-actions group with 4 updates by @dependabot[bot] in #131
- build(deps): bump the codemirror group across 1 directory with 4 updates by @dependabot[bot] in #129
- build(deps-dev): bump the npm-minor-patch group across 1 directory with 5 updates by @dependabot[bot] in #127
- build(deps): bump the go-minor-patch group across 1 directory with 4 updates by @dependabot[bot] in #130
- build(deps-dev): bump qs from 6.16.0 to 6.17.1 in /frontend in the npm-security group across 1 directory by @dependabot[bot] in #133
- Docs/changelog 1.5.0 catch up by @teoritty in #134
Full Changelog: de71d5e...5194445
v1.4.1
Compatibility
| Axis | Version |
|---|---|
pluginApi |
1.0.0 (unchanged) |
| Capabilities | all 1.0.0, unchanged |
| Manifest schema | unchanged |
bundleFormat |
1.0.0 (unchanged) |
| Vault schema | 4 (unchanged) |
| Vault envelope | 1 (unchanged) |
| Audit schema | 1 (unchanged) |
BREAKING
Nothing.
Changed
The Plugins screen no longer shows a Marketplace tab. Its page said only "Coming soon", so the tab was a click that led nowhere. It returns once there is a marketplace to browse; installing from a source or by URL is unaffected.
Fixed
Connecting with a passphrase-protected key asks for the passphrase. Such a connection could never succeed: instead of a field to type into, a system message box said a custom dialog was needed, and the tab then failed with "Authentication failed". A dialog now asks for the key's passphrase while the connection waits, and the key is cached afterwards as its own policy in the Key Manager says. A mistyped passphrase asks again and says it was wrong, up to three tries. Cancelling the dialog, or running out of tries, ends the connection with "Key passphrase was not entered" or "Wrong key passphrase" rather than a generic failure that points at the server. Closing the tab while the dialog is open takes the dialog down with it. The same applies to keys on jump hosts.
Deleting the folder you are in takes you up a level instead of showing an error. Both file panes — the server and the local one — reload the folder on screen after a delete, and when that folder was the one just deleted the reload failed and opened the error dialog with a "not found" message, leaving the pane empty. The pane now moves to the nearest parent folder that still exists, skipping any that went with it, and says nothing: you asked for the folder to go. The same happens when the folder disappears some other way — deleted from a terminal, from the other pane, or by another program — the next time the pane reloads. A folder that was only expanded in the tree, not open, is simply dropped from it.
Other listing failures, such as a folder you have no permission to read, are still reported, now in the pane's own header like a mistyped path rather than in the dialog over the whole window.
Opening a folder you cannot read keeps you where you were. Double-clicking such a folder in either file pane, or going up into one, moved the pane into it anyway: the path bar showed the folder, the listing was empty, and the header said "permission denied". The pane now changes folder only once the new one has been read, so it stays on the folder you were in, with its listing still on screen and the reason in the header. Typing the path of such a folder already behaved this way and still does. Expanding one with its arrow leaves it closed instead of open and empty.
Cancelling a transfer no longer reports an error. Pressing cancel on an upload, a download or a copy in the Transfers panel stopped it and marked the row "Cancelled" — and then opened the error dialog as well, with "context canceled" or whatever the interrupted write happened to fail with. The same happened when cancelling while a dropped folder was still being scanned. A cancelled transfer now ends quietly with its row saying "Cancelled"; a transfer that actually fails is reported exactly as before.
What's Changed
- build(deps): bump actions/upload-artifact from 4 to 7 in the github-actions group by @dependabot[bot] in #111
- build(deps): bump the golang-x group with 3 updates by @dependabot[bot] in #107
- build(deps): bump the codemirror group in /frontend with 4 updates by @dependabot[bot] in #108
- build(deps-dev): bump the npm-minor-patch group across 1 directory with 6 updates by @dependabot[bot] in #110
- fix: 1.4.1 - ask for an encrypted key's passphrase on connect by @teoritty in #112
- fix(files): stay in the current folder when the one opened cannot be read by @teoritty in #113
Full Changelog: v1.4.0...v1.4.1
v1.4.0
What's Changed
- ci: run only the jobs a change could have broken by @teoritty in 7043c3f
- docs(changelog): record the Plugins screen in the 1.3.0 entry by @teoritty in e15479a
- feat(vault): open the vault with a one-time recovery key by @teoritty in 49da56f
- fix(tree): focus the inline rename input so clicking away closes it by @teoritty in 016bf5a
- fix(ui): connection details, rename safety, and dialog polish by @teoritty in 5f36463
- fix(deps): clear the frontend audit backlog by @teoritty in 0b51508
- chore: delete the retired GitHub README rendering by @teoritty in cd41324
- fix(plugin): a refused RPC is not activity by @teoritty in a34e4f1
- fix(ui): make the plugin Ping button and the GitLab issue link do something by @teoritty in 349c8d8
- feat(update): check for releases on GitHub again by @teoritty in 0189e87
- chore: remove the GitLab mirror's CI, templates and references by @teoritty in da09490
- ci: run mutation testing on demand instead of every night by @teoritty in ee05c5c
- docs(changelog): record the return to GitHub in the 1.4.0 entry by @teoritty in 22edbfb
- fix(deps): move devalue to 5.9.4 by @teoritty in fc52bff
- fix(deps): move labstack/echo to v4.15.3 by @teoritty in bf5e007
- ci(dependabot): update monthly, in fewer and grouped pull requests by @teoritty in 3d5aee2
- docs(changelog): date 1.4.0 for the day it ships by @teoritty in ec58086
Full Changelog: v1.3.0...v1.4.0
v1.3.0
What's Changed
- feat(plugins,i18n): standalone Plugins screen and a translated interface by @teoritty in 84b4dbf
- fix(embed,loghub): tunnel burst sizing and three debug-log-window fixes by @teoritty in 246328f
- feat(localshell): a local terminal as the third kind of tab by @teoritty in 3ac95af
- docs: catch the documentation up with 1.3.0 by @teoritty in 32b00c2
Full Changelog: v1.2.1...v1.3.0
v1.2.1
What's Changed
Full Changelog: v1.2.0...v1.2.1
v1.2.0
What's Changed
- fix(build): stop an unstamped build reporting version 1.0.0 by @teoritty in #83
- fix(security): require the master password to weaken plugin trust by @teoritty in #84
- fix(security): verify every plugin release asset and resolve its entry by path by @teoritty in #85
- fix(security): take the host key decision from the session, not the caller by @teoritty in #86
- fix(security): gate non-loopback forward binds and verify the real bound address by @teoritty in #88
- fix(security): pin the SSH algorithm policy and refuse weak host keys by @teoritty in #87
- fix(security): rate-limit master password attempts by @teoritty in #89
- fix(security): stop a plugin raising its own resource limits by @teoritty in #90
- fix(security): name the filesystem paths a plugin asks for at install time by @teoritty in #91
- fix(security): revoke a plugin's capability grants when it is uninstalled by @teoritty in #92
- fix(security): refuse a plugin download redirect that drops TLS by @teoritty in #93
- fix(security): remove a plugin's files when its install is refused by @teoritty in #94
- fix(security): require checksums when loading a user-installed plugin by @teoritty in #95
- fix(security): strip terminal control characters from plugin log lines by @teoritty in #96
- fix(security): bound how many plugin requests the host handles at once by @teoritty in #97
- fix(security): stop an unrecognised apply target widening a recursive chmod by @teoritty in #100
- fix(security): refuse remote entry names that lie about what they are by @teoritty in #98
- fix(security): bound how far a remote directory walk will recurse by @teoritty in #101
- fix(security): bound the transfer concurrency and timeout settings by @teoritty in #102
Full Changelog: v1.1.0...v1.2.0
v1.1.0
What's Changed
- fix(plugins): verify the installed tree, not the staging tree by @teoritty in #72
- feat(plugins): deny plugin processes the shared USER surface on Windows by @teoritty in #73
- docs: state the plugin trust model and stop calling it a sandbox by @teoritty in #74
- fix(plugins): delete the second, unvalidated install path by @teoritty in #75
- fix(plugins): give each plugin instance its own temp directory by @teoritty in #76
- feat(plugins)!: remove localEmbedServer (Mode B) by @teoritty in #77
- refactor(plugins): put a childProcess seam under the spawner by @teoritty in #78
- feat(plugins): report the sandbox mode a plugin process is running behind by @teoritty in #79
- feat(plugins): confine plugin processes with Landlock (Linux) and AppContainer (Windows) by @teoritty in #80
- feat: SSH key manager with an encrypted in-vault key store by @teoritty in #81
- fix(build): report the version in the Windows executable, and drop the committed mutate.exe by @teoritty in #82
Full Changelog: v1.0.0...v1.1.0
v1.0.0
v1.0.0-rc.3
What's Changed
- Chore/cross platform makefile by @teoritty in #26
- Fix/file pane selection dismiss by @teoritty in #27
- fix(tree): derive creation target from selection and delete the whole… by @teoritty in #28
- Fix/surface tabs and discovery delete by @teoritty in #30
- Fix/GitHub install ships plugin bundle by @teoritty in #31
- Feat/adr 015 plugin UI surfaces by @teoritty in #29
- Feat/adr 015 plugin UI surfaces by @teoritty in #32
- Refactor/redaction depth and file pane split by @teoritty in #33
- fix: close the four open code scanning alerts and gate CodeQL by @teoritty in #35
- Refactor/context usage hygiene by @teoritty in #34
Full Changelog: v1.0.0-rc.2...v1.0.0-rc.3