Skip to content

Merge upstream v6.0.5, rework vault secrets as a hierarchy, fix three regressions - #18

Merged
AustralianRaven merged 1641 commits into
masterfrom
chore/merge-upstream-6.0.5
Aug 26, 2026
Merged

Merge upstream v6.0.5, rework vault secrets as a hierarchy, fix three regressions#18
AustralianRaven merged 1641 commits into
masterfrom
chore/merge-upstream-6.0.5

Conversation

@AustralianRaven

Copy link
Copy Markdown
Owner

Merge upstream v6.0.5, rework vault secrets as a hierarchy, fix three regressions

Summary

  • Merges 1635 upstream Beekeeper Studio commits up to the v6.0.5 release tag (769 files). Thirty-two conflicts resolved; SqlWolf branding, the flat "always ultimate" license rule and the fork's Display/Formatting settings are all preserved.
  • Replaces the single global Azure Key Vault config with many named vaults and an ordered list of secret refs per connection. Vault connection details are stored encrypted in a new vault_provider table; refs travel with the connection, values never do.
  • Drops the fork's own autocomplete casing/quoting implementation in favour of upstream's richer keywordCasing / quoteIdentifiers / quoteCharacter options, and rewires the Formatting settings pane onto them.
  • Fixes issue FEAT: Database Dropdown Should Auto-Populate Regardless of Current Database #16 (SQL Server database dropdown empty unless connected to master) and issue BUG: Queries that output 500+ rows breaks the app #17 (queries returning 500+ rows lock the app in the stacked results layout).
  • Fixes two regressions the merge introduced: SQL Server scripts were being split into separate batches, breaking BEGIN/COMMIT and shared variables; and the dev launcher killed itself on Windows on its first rebuild.

Changed files

apps/studio/src/lib/vault/types.ts

Defines the three-layer vault model: providers, secret refs, and resolved values. Documents the ordering rules that make the feature predictable — ref order sets tier precedence, provider order is the fallback for unpinned refs. Declares the two-method VaultProvider interface that any backend must satisfy.

apps/studio/src/lib/vault/refs.ts

Parses and serialises the ordered <vaultName>:<secretName> ref list stored on a connection. Splits on the last colon, because vault names may contain one and secret names may not. connectionSecretRefs falls back to the legacy vaultSecretName field so connections saved before this change keep resolving.

apps/studio/src/lib/vault/VaultResolver.ts

Resolves an ordered ref list against an ordered provider list. The first ref to define a key keeps it, and shadowed keys are recorded rather than dropped so the UI can explain why a lower tier looks ignored. An unknown pinned vault is an error, never a silent fallback to a different vault; failures are isolated per ref so one unreachable vault degrades only its own tier.

apps/studio/src/lib/vault/registry.ts

Maps a provider type string to its implementation. Adding HashiCorp or AWS Secrets Manager is one entry here plus one class, with nothing above it changing.

apps/studio/src/lib/vault/providers/AzureVaultProvider.ts

Azure Key Vault behind the generic provider interface, replacing the old AzureVaultService. fetchSecret returns a flat object rather than a string, which is what lets one vault secret carry a whole tier of configuration in a single round trip.

apps/studio/src/lib/vault/mapping.ts

Turns merged vault values into connection fields using the mappings configured in Settings. A blank mapping falls back to the field name, so a secret whose keys already match needs no configuration.

apps/studio/src/handlers/vaultHandlers.ts

IPC surface for vault CRUD, connection testing and resolution, replacing azureVaultHandlers.ts. Enforces the two rules that keep credentials safe: list substitutes a boolean for the client secret so it never reaches the renderer, and a blank secret on update means "keep the stored one" rather than erasing it.

apps/studio/src/common/appdb/models/VaultProvider.ts

New TypeORM entity backing the vault_provider table, with the client secret behind the existing EncryptTransformer. Carries a position column because provider order is the fallback order for refs that do not pin a vault.

apps/studio/src/migration/20260827_add_vault_providers.js

Creates the vault_provider table and adds saved_connection.vaultSecretRefs. Additive only — the legacy vaultSecretName column is left in place so older connections and older app versions keep working.

apps/studio/src/store/modules/VaultModule.ts

Renderer-side store for providers and field mappings, replacing AzureVaultModule. Includes a one-time migration that turns the old azure_vault_config setting into the global field mappings plus a first provider named "Azure Key Vault", so existing setups upgrade without manual steps.

apps/studio/src/components/settings/VaultSettings.vue

New Settings pane for managing multiple vaults: add, edit, remove, reorder and test connections, with the global field-mapping table below. Replaces AzureVaultSettings.vue. Explains in-pane that connections pin vaults by name, since that is the decision which makes the configuration portable between machines.

apps/studio/src/components/connection/VaultLoader.vue

Rewritten from a single secret-name box into an ordered list of secret refs, each optionally pinned to a vault. Keeps the explicit fetch button, and adds a result panel with a source dropdown so a merged view or any single tier can be inspected, with overridden keys struck through. Applying writes the mapped values into the connection form; sensitive values are masked in the preview.

apps/studio/src/lib/db/clients/sqlserver.ts

Two fixes. listDatabases now filters on HAS_DBACCESS(name) = 1, which answers "can this login reach this database" identically from any database context — previously a bare sys.databases read returned almost nothing unless the session held VIEW ANY DATABASE, leaving the sidebar dropdown empty (#16). That also supplies the WHERE the optional filter appends to, which was previously a bare AND with nothing above it. Separately, executeQuery no longer splits every script into per-statement batches; it does so only when a transaction statement actually needs intercepting, because separate batches break shared variables, temp tables and @@TRANCOUNT.

apps/studio/src/components/editor/ResultTable.vue

actualTableHeight becomes a computed that honours the tableHeight prop instead of a data field permanently set to "100%". Tabulator only virtualises with a definite height, and "100%" resolves to auto inside a stacked result block sized by its own content — so 5000 rows all went into the DOM (#17). Also guards the tableHeight watcher against a not-yet-created Tabulator, now that the value can actually change.

apps/studio/esbuild.mjs

The dev launcher treated any Electron exit without a signal as a crash and shut the watcher down. Windows has no real signals, so a deliberate restart reports exit code 1 with a null signal — indistinguishable from a crash — and the first rebuild always killed the watcher. Deliberate restarts are now flagged, so yarn bks:dev runs on Windows.

apps/studio/scripts/seedResultsLayout.js

Sets queryResultsLayout directly in an app database. The query screen exposes no control for it and the renderer store is not reachable in a production build, so the e2e run needs the value in place before the app starts.

apps/studio/e2e/tests/largeResultSet.test.ts

Regression guard for #17. Builds its own SQLite fixture through the app, runs a 5000-row select and counts the rows Tabulator committed to the DOM. Fails with "put 5000 of 5000 rows in the DOM" if the height regresses.

apps/studio/tests/unit/lib/vault/refs.spec.ts

Covers ref parsing and serialisation: the last-colon split, round-tripping, blank rows, and the legacy single-secret fallback.

apps/studio/tests/unit/lib/vault/VaultResolver.spec.ts

Covers resolution precedence with mocked providers: first ref wins, ownership and conflicts are reported, unpinned refs walk provider order, an unknown pinned vault errors instead of falling back, and one dead vault degrades only its own tier.

apps/studio/tests/unit/lib/vault/mapping.spec.ts

Covers field mapping: blank mappings falling back to the field name, configured keys, and the declared field ordering of the output.

apps/studio/tests/unit/lib/db/clients/sqlserver.spec.js

Adds coverage for both SQL Server fixes: that listDatabases filters on HAS_DBACCESS and gives the filter a valid WHERE, and that executeQuery sends a script as one batch unless a transaction genuinely needs intercepting.

apps/studio/src/common/appdb/models/saved_connection.ts

Adds the vaultSecretRefs column holding the ordered ref list, and comments the legacy vaultSecretName field as read-only for backwards compatibility.

apps/studio/src/common/appdb/Connection.ts

Registers the VaultProvider entity with the app database connection.

apps/studio/src/common/interfaces/IConnection.ts

Adds vaultSecretRefs to the connection interface alongside the legacy field.

apps/studio/src/migration/index.js

Registers the new vault provider migration.

apps/studio/src/store/index.ts

Swaps the azureVault module for vault and updates the startup dispatch.

apps/studio/src-commercial/backend/handlers/handlers.ts

Swaps IAzureVaultHandlers for IVaultHandlers in the handler union.

apps/studio/src-commercial/entrypoints/utility.ts

Registers VaultHandlers in the utility process in place of AzureVaultHandlers.

apps/studio/src/components/settings/SettingsModal.vue

Renames the "Azure Key Vault" tab to "Key Vaults" and points it at the new pane, reflecting that vaults are no longer Azure-specific.

apps/studio/src/components/connection/CommonServerInputs.vue

Reads the vault-enabled getter from the renamed vault store module.

Removed

apps/studio/src/lib/azure/AzureVaultService.ts, apps/studio/src/handlers/azureVaultHandlers.ts, apps/studio/src/store/modules/AzureVaultModule.ts, apps/studio/src/components/settings/AzureVaultSettings.vue

The single-vault implementation, superseded by the provider-based one. Its behaviour is preserved through the legacy vaultSecretName field and the one-time settings migration.

bnquon and others added 30 commits August 2, 2026 11:19
…ilter-operators

fix: reposition AND/OR operators in table filters
…abot/npm_and_yarn/brace-expansion-1.1.18

chore(deps): bump brace-expansion from 1.1.16 to 1.1.18
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…te-confirmation-title

fix: show favorite title in delete confirmation
…mage-launcher-detection

Fix/appimage launcher detection
Restore general.deleteSelection in the TableTable keymap and remove
the custom keydown.capture handler and matchesVHotkeyBinding helper.
Mac row delete still relies on config (delete + ctrlOrCmd+backspace)
and keeping delete distinct from nullSelection's backspace binding.
On Mac, remap the delete binding to backspace so v-hotkey matches the
key users press when no forward-delete key is available.
…lugin-conn-info

add disabledFeatures to getConnectionInfo
rathboma and others added 25 commits August 17, 2026 13:46
…mplete-revamp

Autocomplete defaults overhaul - restoring upper case
…bulator-persistence

table column widths are replaced by new tabs
…rsistence-writing

only save persistence data if data has changed
The bundled copy was only ever used for a first install, so an app
upgrade never moved existing users off the version they started on
unless the plugin registry served a newer release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…date-bundled-plugins

Update bundled plugins when the app ships a newer version
…nual-commit-typing

Fix typing transaction commands in manual commit
Connecting on Windows with SSH auth set to Automatic fails with "SSH
Tunnel Connection Error: Failed to retrieve identities from agent" when
no agent is running, even though the key resolved from ~/.ssh/config
would authenticate. Starting an empty Pageant works around it.

e8f7b77 made Windows always attempt agent auth (!!socketPath ||
isWindows) so Pageant users could reach their agent. With no Pageant
running, ssh2 cannot read identities and emits an error with level
'agent'. ssh2 treats that as advisory and carries on with the next auth
method, but connect() rejected on any error event, aborting a connection
that publickey would have completed. Before 6.0.1 the agent was never
queried on Windows without SSH_AUTH_SOCK, so this path was unreachable.

Ignore agent-level errors in the connect handler and log them instead.
Terminal failures arrive as client-authentication and still reject, so a
genuinely unauthenticated connection fails as before.

Fixes beekeeper-studio#4661
Brings the fork up to date with 1704 upstream commits.

Conflict resolutions worth knowing about:

- Autocomplete formatting. Upstream shipped its own keywordCasing /
  quoteIdentifiers / quoteCharacter implementation, which supersedes this
  fork's upperCaseKeywords / autoQuoteIdentifiers. The fork's parallel
  implementation in apps/ui-kit is dropped; the Settings UI now writes
  upstream's options, and the ini config remains the fallback.
- CodeMirrorPlugins autoquote. The fork had relaxed the Postgres quote rule
  to /[^a-zA-Z0-9_]/, which stops uppercase identifiers being quoted.
  Postgres folds unquoted names to lowercase, so that was a bug. Reverted to
  upstream's rule.
- License. Kept the fork's flat "always ultimate" logic and rewrote
  license.spec.ts to pin that contract instead of upstream's tiered rules.
- Branding, menus, README. Kept SqlWolf naming; took upstream's new menu
  roles and desktopName.
- Store init. Dropped the fork's duplicate license-sync interval; App.vue
  already polls. plugins/initialize moved to PluginStoreService upstream.
- tunnel.spec.ts path assertions now normalise separators so they pass on
  Windows.

Verified: yarn build, yarn lint (0 errors), yarn test:unit (3207 passing).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces the single global Azure Key Vault config with many named vaults and
an ordered list of secret refs per connection. The way it is driven does not
change: vaults and field mappings still live in Settings, and the connection
form still has an explicit fetch button.

Three layers, kept apart:

1. Vault providers — Settings > Key Vaults. Per-machine, encrypted at rest in
   a new vault_provider table, ordered. The list order is the fallback order
   for a ref that does not pin a vault.
2. Secret refs — an ordered "Vault:secret,Vault:secret" string on the
   connection, most specific first. Refs only; values are never stored there.
3. Resolution — the first ref to define a key keeps it. Shadowed keys are
   reported rather than dropped.

Decisions that matter:

- A ref pins a vault by NAME, not id. The id is local to one machine; the
  name survives an exported connection landing on a teammate's.
- Refs split on the LAST colon. Secret names cannot contain one, vault names
  can, so this is the only unambiguous split.
- An unknown pinned vault is an error, never a fallback. Falling through to
  another vault would hand back a different environment's credentials under
  the same keys.
- Failure is per-ref. One unreachable vault degrades that tier only.
- The client secret never leaves the utility process. The list handler
  substitutes a boolean; a blank secret on edit means "keep", not "erase".
- Providers sit behind a registry keyed by type, with a two-method interface.
  Adding HashiCorp or AWS Secrets Manager touches nothing above the provider.

Backwards compatibility: the legacy vaultSecretName column is still read, so
existing connections keep working. The old azure_vault_config setting is
migrated on first load into a provider named "Azure Key Vault" plus the
global field mappings.

Adds 38 unit tests over ref parsing, resolution precedence and field mapping.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The esbuild watcher restarts Electron on every rebuild: it kills the old
process, then treats any exit without a signal as a crash and shuts the whole
watcher down.

Windows has no real signals. A process killed via process.kill(pid, 'SIGINT')
exits with code 1 and a null signal, which is the same shape as a genuine
crash. The main and utility bundles finish more than 500ms apart, so the first
restart always fires and the watcher always kills itself. yarn bks:dev could
not run on Windows at all.

Flag the deliberate restart so the exit handler can tell the two apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
listDatabases read sys.databases bare. That view only lists everything when
the session holds VIEW ANY DATABASE; from a non-master context an ordinary
login sees almost nothing. Connecting with a default database other than
master therefore left the sidebar dropdown empty, and switching databases
meant detouring through master to make the list appear.

HAS_DBACCESS(name) asks the question that actually matters — can this login
connect to this database — and answers it the same way from any database
context. That also fixes changeDatabase, which refreshes the list using the
connection it just switched to.

The same WHERE gives the optional filter something to hang off. It was
previously appended as a bare AND with no WHERE above it, which would have
been a syntax error the moment anything passed a filter. Nothing does today,
so it never fired.

Other engines are untouched: postgres already has a real WHERE, and mysql
filters in JS.

Not exercised against a live SQL Server — that needs a container this machine
does not have. The generated SQL is pinned by unit tests instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tabulator only virtualises when it has a definite height. ResultTable pinned
its height to the string "100%" and quietly ignored the tableHeight prop every
caller passes.

"100%" resolves only when the container already has a definite height of its
own. That holds for the tabbed layout, where the result panel is sized by the
splitter. It does not hold for a stacked result block, which is sized by its
content, so the height resolved to auto and Tabulator committed every row to
the DOM.

Measured on a 5000-row SELECT:

  tabs     ~40 rows in the DOM
  stacked  5000 rows in the DOM, ~30s to render, window unusable

After honouring the prop, stacked renders 20 rows and settles in ~9s. Tabs is
unchanged, since the pixel height it already passes is what "100%" was
resolving to anyway.

Adds an e2e regression guard that builds its own SQLite fixture through the
app and fails with "5000 of 5000 rows in the DOM" if the height regresses,
plus the seed script it needs to select a layout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Regression from the v6.0.5 merge. Upstream rewrote executeQuery to run a
script one statement at a time so that BEGIN / COMMIT / ROLLBACK could be
routed to the reserved connection behind manual commit mode.

Statements in one script share variables, temp tables and @@TRANCOUNT, and a
batch of their own breaks all three. The identifier splits on semicolons, so

    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
    BEGIN TRANSACTION;
    DECLARE @item ...
    SELECT ...
    COMMIT TRANSACTION;

becomes two batches, the first ending on BEGIN TRANSACTION. That batch returns
with @@TRANCOUNT raised from 0 to 1 and SQL Server answers:

    Transaction count after EXECUTE indicates a mismatching number of
    BEGIN and COMMIT statements. Previous count = 0, current count = 1.

Before the merge the whole script went to the driver as a single batch, which
is why this worked in 6.0.2.

Splitting is now used only when there is something to intercept: a tabId is
present and the identifier actually typed a statement as TRANSACTION.
Otherwise the script is sent as one batch and each returned recordset becomes
a result, as it did before. Manual commit mode is untouched.

Worth noting the identifier types neither BEGIN TRANSACTION nor COMMIT
TRANSACTION as a transaction statement, so this script was being split for no
benefit at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous attempt filtered sys.databases on HAS_DBACCESS(name) = 1. That
was wrong twice over. HAS_DBACCESS can only filter rows sys.databases already
returned, so it cannot reveal a database whose row is hidden, and it returns
NULL for master — so the filter dropped the one database that was always
listed, leaving the dropdown with no options at all.

Diagnosing on a real server showed sys.databases returning exactly two rows
from inside a user database:

    master           ONLINE   has_access = NULL
    <current db>     ONLINE   has_access = 1

That is documented Azure SQL behaviour: inside a user database, sys.databases
exposes only master and the current database, and no permission grant changes
it. The full list is visible from master, which is why clicking master made
the dropdown work.

So read the list from master. Azure forbids USE across databases, so this
takes a short-lived connection of its own rather than a context switch. It
falls back to the current connection when master is not worth trying or cannot
be reached — already on master, integrated auth, or any connection failure —
leaving a login without master access no worse off than before.

The optional filter now gets its own WHERE, which was the one part of the
previous attempt worth keeping.

Also stops updateDatabaseList swallowing failures. It committed nothing on
error, so a failed query and an empty server looked identical: the dropdown
read "no matching options" either way. It now keeps the previous list and logs
the reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@AustralianRaven
AustralianRaven merged commit 6648cca into master Aug 26, 2026
48 checks passed
@AustralianRaven
AustralianRaven deleted the chore/merge-upstream-6.0.5 branch August 26, 2026 05:41
AustralianRaven added a commit that referenced this pull request Aug 26, 2026
PR #18 was squash-merged, which kept every line of the content but discarded
the merge parentage. Git therefore still treated 097c187 as the merge base
with upstream, so GitHub reported master as 1704 commits behind and the next
upstream merge would have re-fought all 32 conflicts against code that already
contains the resolutions.

This records v6.0.5 as an ancestor without touching a single file. The tree is
byte-identical before and after; -s ours changes ancestry only.

Use 'Create a merge commit' rather than squash for future upstream merge PRs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: Queries that output 500+ rows breaks the app FEAT: Database Dropdown Should Auto-Populate Regardless of Current Database