Commit Graph
8199 Commits
Author SHA1 Message Date
Dave Page 4fa95c58af Fix runtime menu refresh crash when menus not yet received (#9762) (#10070)
refreshMenus() rebuilt the application menu from the module-level
cachedMenus, which is only populated once the renderer sends its menu
definition via the 'setMenus' IPC. When a menu refresh was triggered
before that happened - e.g. an auto-update event, or the user closing
the window while the UI was still loading - cachedMenus was undefined
and bindMenuClicks() crashed with 'Cannot read properties of undefined
(reading map)', surfacing as an uncaught-exception dialog.

Guard refreshMenus() so it bails out when there are no cached menus to
rebuild.

Closes #9762
2026-06-12 15:38:19 +05:30
Dave Page ed9bf2f249 Fix syntax error editing SQL functions/procedures with a 'return' substring in the body (#10061)
The SQL-standard body detection used a regex that matched 'return'
anywhere in the body, so a plain SQL body containing a RETURNING clause
(or an identifier like 'returned_value') was wrongly treated as a
SQL-standard (BEGIN ATOMIC / RETURN) body. The generated CREATE OR
REPLACE statement then dropped the AS $BODY$ ... $BODY$ wrapper,
producing invalid SQL and a syntax error on save.

Anchor the RETURN form to the start of the body so only genuine
SQL-standard bodies are detected. Add unit tests for the detection.

Closes #10059
2026-06-12 15:25:34 +05:30
Dave Page 458dec3571 chore(deps): clear vulnerable transitive deps (tar, flatted) (#10049)
Resolves open Dependabot security advisories for transitive npm
dependencies that have no direct manifest entry (so Dependabot cannot
auto-open fix PRs for them).

tar (6 x HIGH): an old tar@6.2.1 was pulled in via
ttf2woff2@4.0.5 -> node-gyp@9.4.1 (and node-gyp's
make-fetch-happen@10 -> cacache@16 chain). ttf2woff2 6+ switched to an
ESM/default export that breaks @vusion/webfonts-generator's callable
usage, so rather than bump ttf2woff2 we override its node-gyp to
^11.2.0 via a scoped resolution. That modernises the whole sub-tree
(node-gyp 11, make-fetch-happen 14/15, cacache 19/20) onto tar@7.5.16
while keeping ttf2woff2 at 4.0.5 so webfont generation still works.

flatted (1 x HIGH): bumped 3.4.1 -> 3.4.2 in the Electron runtime
(GHSA-rf6f-7fwh-wjgh).
2026-06-12 14:50:43 +05:30
Dave Page 8dcc30a05d doc: Fix typo and clarify PGPASS_FILE documentation. #9866 (#10034)
Corrected "varible" -> "variable" and rewrote the broken sentence to
explain that PGPASS_FILE is the path to a pgpass file that is copied into
the container and used as the .pgpass file.
2026-06-12 14:49:41 +05:30
Dave Page 51e90a9827 Correct the macOS Replace shortcut in Query Tool toolbar docs (#9817) (#10033)
doc: Correct the macOS Replace shortcut in the Query Tool toolbar docs. #9817

The Edit toolbar table listed Replace as "Option + Cmd + F (MAC)" /
"Ctrl + Shift + F (Others)", but the default Replace shortcut is
Cmd/Ctrl + R, per register_editor_preferences.py.
2026-06-12 14:47:28 +05:30
Yogesh Mahajan 7aa3f85caf fix(#9701): Ensure to use psycopg3 is used while connecting to postgres DB when PGADMIN_CONFIG_CONFIG_DATABASE_URI is specified. (#9914) 2026-06-11 09:55:09 +05:30
Khushboo Vashi 04fa05c1e6 Fix missing ALTER ... SET DEFAULT for inherited columns in table SQL. (#9774)
When a table inherits a column from a parent, the generated table
SQL/EDIT script omitted the column's default. Emit a post-create
ALTER TABLE ... ALTER COLUMN ... SET DEFAULT for inherited columns
that carry a default, and show the default in the inherited-column
comment, across all version buckets.

Generated columns (colconstype 'g') are excluded from both the new
ALTER statement and the comment default, consistent with the inline
column-definition logic, since SET DEFAULT is invalid for a generated
column.
2026-06-09 15:44:22 +01:00
ZoroXL 77dc9d77ec Fix infinite loading after an idle connection is dropped (#6308)
When a database connection was silently dropped while pgAdmin sat idle
(common on Linux Desktop behind a firewall/NAT), the Object Explorer and
Query Tool would hang on an infinite spinner instead of offering to
reconnect, because connected() only checks local driver state and misses
stale/half-open TCP sockets.

- ping() now performs a real network check (SELECT 1) guarded by the
  connection's transaction_status, so an in-progress query or open
  transaction is never disrupted; on failure it tears down the dead
  connection and returns False.
- The Object Explorer children() endpoint uses ping() to detect a dead
  connection up front and returns 503 CONNECTION_LOST; the tree shows a
  reconnect dialog (deduplicated per server) and marks the node
  disconnected.
- TCP keepalive defaults are applied to all connections so the OS
  surfaces dead sockets in seconds rather than the full retransmission
  timeout.
- The Query Tool re-checks connection status when its tab becomes
  visible again, and its layout/visibility listeners are now cleaned up
  on unmount.

Refs #9700, #8279. Adds a 9.16 release note.
2026-06-09 15:26:28 +01:00
Manolis Stamatogiannakis 8ad45be9c8 Use ServerManager's passfile in connect() credential gate. (#9810)
The passfile kwarg passed to Connection.connect() was only ever used as
a gate for the passexec fallback; it was never forwarded to
create_connection_string(), which builds the DSN's passfile from the
ServerManager's connection parameters. This made the gate inconsistent
with the passfile actually used for the connection.

Use the ServerManager passfile for the credential gate so the check
matches what is used to connect. The manager passfile now takes
precedence over both passexec and any passfile kwarg; warnings are
emitted when either is ignored in its favor.
2026-06-09 15:23:50 +01:00
Dave Page 2918e16734 docs: move misfiled release-note entries from 9.9 to 9.16
The entries for #9875 (EXPLAIN/blank-line query extraction) and #9988
(OAuth2 metadata URL guidance) were added to release_notes_9_9.rst,
which is an already-released version. Move them to the in-progress
9.16 notes where they belong.
2026-06-09 15:22:33 +01:00
Daniel Zabel 06300e76a0 Make init container security context configurable in the Helm chart (#9646)
The two init containers in the Helm deployment template had hardcoded
securityContext blocks, unlike the main container which already renders
its context from .Values.containerSecurityContext via the
renderSecurityContext helper. Switch the init containers to the same
pattern so operators can customise (or disable) their security context.

Default behaviour is unchanged: containerSecurityContext defaults to
enabled with values identical to the previous hardcoded block, and the
helper continues to gate appArmorProfile on
global.compatibility.appArmor.enabled. Verified with helm template that
the rendered init-container securityContext is unchanged for the default
values, honours the appArmor toggle, and is omitted entirely when
containerSecurityContext.enabled=false.

Adds a 9.16 release note.
2026-06-09 15:17:01 +01:00
Ashesh Vashi 397be4e70e Fixed an issue where EXPLAIN and EXPLAIN ANALYZE failed when blank lines separate clauses in the SQL query. (#9875)
getQueryAt now uses the syntax tree as the primary check for whether a
blank-line boundary cut through a SQL statement. It is split into two
helpers: _findQueryBoundaries (the original scan, parameterized by
stopAtBlankLine) and _needsExpansion, which detects when a Statement
node straddles the extracted range and re-scans ignoring blank lines.

A STATEMENT_STARTERS keyword list guards the case where the parser
merges semicolon-less queries into one Statement, and WRAPPER_STARTERS
(EXPLAIN, ANALYZE, WITH) force expansion when the Statement extends past
the range. Also fixes a Lezer boundary bug (tree.iterate is inclusive at
boundaries) with a node.to > startPos check. Adds 21 tests covering
EXPLAIN, negative no-merge cases, comments, and boundary edge cases.
2026-06-09 15:10:07 +01:00
Adam Tao 0223e8a026 fix(openai): tolerate empty/null fields in streaming tool-call deltas (#9828)
Some OpenAI-compatible providers emit empty or null name/arguments/id
fields in streaming continuation deltas to keep the response schema
stable. pgAdmin's accumulator overwrote the real tool name (captured in
the first delta) with the later null, producing a tool call named "null"
that could not be dispatched.

Skip falsy name/arguments/id when accumulating (matching the OpenAI
Python SDK, which ignores nulls the same way) so the values captured in
the first delta survive. Also guard against a null `function` object in
a delta, which previously raised TypeError. Without the id guard a null
id in a continuation delta clobbered the real id, which the final build
then replaced with a random uuid rather than the provider's id.

Adds a unit test covering the null-continuation, multi-chunk-arguments,
and null-function cases, and a 9.16 release note.
2026-06-09 15:08:19 +01:00
Ashesh Vashi 89e84eb913 OAuth2: actionable error when openid scope lacks OAUTH2_SERVER_METADATA_URL (#10007)
When OAUTH2_SCOPE contains 'openid' but OAUTH2_SERVER_METADATA_URL is
not set, Authlib fails deep inside id_token verification with a cryptic
`Missing "jwks_uri" in metadata` error that names neither the config
knob nor the fix.

Add a pre-flight check at the entry of _authorize_access_token: if the
scope includes 'openid' and no (non-whitespace) metadata URL is set,
raise a RuntimeError with actionable guidance before any network
round-trip. server_metadata_url is the only way pgAdmin feeds JWKS to
Authlib, so this carries no regression risk for correctly-configured
providers. Clarify the OAUTH2_SERVER_METADATA_URL comment in config.py
and add regression coverage.
2026-06-09 14:53:28 +01:00
Ashesh Vashi 879ae40cbb CLI: skip delete_adhoc_servers() in CLI mode (#10008) 2026-06-09 14:47:48 +01:00
Ashesh Vashi c88c8f612e docker: read PGADMIN_CONFIG_CONFIG_DATABASE_URI safely via os.environ (#9984)
The container entrypoint substituted ${PGADMIN_CONFIG_CONFIG_DATABASE_URI}
into a double-quoted Python string for `python3 -c`. Combined with the
config_distro.py convention (where the env var's value must itself be a
Python literal, i.e. users set it to 'postgresql+psycopg://...'), the
entrypoint re-wrapped the already-quoted value, producing a string with
literal quotes inside that SQLAlchemy could not parse -- and the Python
crash made the first-launch check capture an empty string, silently
skipping PGADMIN_DEFAULT_EMAIL / PGADMIN_DEFAULT_PASSWORD setup.

Read the env var inside Python via os.environ so the shell no longer
participates in Python-literal quoting (also removing a shell-injection
surface), and use ast.literal_eval to unwrap the legacy quoted form while
letting raw values pass through. external_config_db_exists now stays
"False" on any Python failure so first-launch setup still runs.

Adds a 9.16 release note.
2026-06-09 14:44:26 +01:00
Ashesh Vashi 24f0597dc8 Server UI: clarify SSH tunnel identity-file password prompt label (#10010) 2026-06-09 14:35:42 +01:00
Ashesh Vashi 303f6a2dd8 Use resolve_fks=False in migration reflect() so stale FKs don't crash startup (#9976)
An old configuration database (e.g. a pre-9.0 pgadmin4.db lingering in
%appdata%) can carry a stale foreign key such as server.user_id ->
user_old, a table removed long ago. SQLAlchemy's MetaData.reflect()
defaults to resolve_fks=True, which auto-follows the reflected table's
foreign keys and reflects their targets too; the orphan target trips
NoSuchTableError and aborts the startup migration, surfacing in the GUI
as the misleading "Server could not be contacted".

None of the migrations use the FK-target tables -- each operates only on
the explicitly requested table(s). Pass resolve_fks=False at all 14
meta.reflect() call sites across the 12 migration files so reflection no
longer cascades into broken FK targets. Behaviour is unchanged for
healthy databases.

Adds a 9.16 release note.
2026-06-09 14:32:19 +01:00
Dave PageandClaude Opus 4.8 8afe14ec36 Remove design proposal document committed in error
This internal design proposal was inadvertently included with the
session and file-manager hardening changes and should not have been
added to the repository.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 14:31:10 +01:00
Ashesh Vashi 25415b888a docs(container): warn about K8s init-container tag mismatch and RollingUpdate hazard on shared data volumes (#10014) 2026-06-09 14:28:16 +01:00
Pavan Manish c720a33b44 Add toast_tuple_target to the Materialized View dialog (#9626)
Exposes the PostgreSQL 11+ TOAST_TUPLE_TARGET storage parameter in the
Materialized View properties dialog, mirroring the existing FILLFACTOR
support: an integer field (128-8160) in the Definition group, extracted
from reloptions in properties.sql, emitted in the CREATE ... WITH clause,
and SET/RESET on edit. The recreate-on-definition-change path preserves
the value.

The dialog field and SQL are applied to the version buckets that
supported servers actually resolve to. The original change only touched
the legacy default/ bucket (PG < 12), so it had no effect on any
supported server (13-18, which resolve to 13_plus/15_plus); the
create/properties/update templates are now updated in the 12_plus,
13_plus and 15_plus buckets too. Template output is byte-identical to
the previous SQL when toast_tuple_target is unset.

Adds a 9.16 release note.
2026-06-09 14:16:50 +01:00
Ashesh Vashi f9b9bc9d40 fix(sqleditor): skip read-only columns when saving inserted rows (#10015) 2026-06-09 14:12:11 +01:00
pavanmanishd 39684cdf48 Generate ALTER TABLE SET UNLOGGED/LOGGED when persistence is changed (#9768)
Wire the relpersistence change through to the table update SQL across all
version buckets (default, 11_plus, 12_plus, 15_plus) so the Unlogged toggle
emits ALTER TABLE ... SET LOGGED/UNLOGGED on every supported server.

Closes #9677.
2026-06-09 14:00:15 +01:00
pavanmanishd 4c5702dd47 Close tab on middle-click (#9767)
Closes #9699.
2026-06-09 13:53:10 +01:00
Ashesh Vashi 6bc4341383 fix(llm): expand allowlist for self-hosted endpoints and stop silent fallbacks (#10016) 2026-06-09 13:48:12 +01:00
Ashesh Vashi 5b17cad8a4 fix(pkg/linux): pin psycopg-c build to x86-64 v1 baseline (#10017) 2026-06-09 13:42:30 +01:00
Ashesh Vashi 4dd5cdd9ea fix(schema-diff): reverse-engineer SERIAL columns by default (#10020)
Schema Diff's "Generate Script" emitted CREATE TABLE DDL with
the libpq-expanded form of SERIAL columns:

    col integer NOT NULL DEFAULT nextval('table_col_seq'::regclass)

instead of preserving the SERIAL declaration. Applied to a clean
target, that script fails with `relation "table_col_seq" does
not exist` because the sequence is never created — exactly the
symptom in issue #9896.

SERIAL detection already existed in
columns/utils.py:get_formatted_columns (added in 115208c8d for
ERD generation, Nov 2023), but was gated behind a
``with_serial_cols`` parameter that defaulted to False. Only ERD
and the Schema Diff *comparison* phase opted in via explicit
True. The Schema Diff *Generate Script* path and the browser
tree's *CREATE Script* view went through `_get_resql_for_table`
→ `_formatter(data)` without specifying it, so SERIAL detection
was skipped and #9896 reproduced.

There is no use case where pgAdmin should knowingly emit the
expanded libpq form: SERIAL is purely a CREATE-time macro that
PostgreSQL expands into an integer column plus an implicit
sequence owned by the column. The detection logic is reversing
that expansion, which is the correct interpretation in all
contexts (DDL emission, comparison, ERD, properties dialog).

Flip the default to True at all four declaration sites:

- columns/utils.py: get_formatted_columns
- utils.py: BaseTableView._formatter
- utils.py: BaseTableView.fetch_tables
- __init__.py: TableView.fetch_tables

All four existing explicit-True callers (ERD x2, Schema Diff
compare x2) are unaffected. Silent callers that previously got
the buggy False — `_get_resql_for_table` (Schema Diff Generate
Script and browser CREATE Script), `select_sql` / `insert_sql` /
`update_sql` in the table node, and one path in the partition
flow — now correctly get SERIAL detection. The SELECT/INSERT/
UPDATE generators are unaffected regardless: they emit only
column names, which SERIAL detection never touches (it rewrites
the column type and clears `defval`, leaving the name intact).
The table edit path is likewise safe: `get_sql` diffs each
changed column against a freshly re-fetched old column state
that bypasses SERIAL detection, so the reprojected `old_data`
columns are never consulted for column DDL.

Adds a resql regression in pg/{12_plus, 14_plus, 16_plus}: a
CREATE TABLE scenario covering ``serial`` / ``bigserial`` /
``smallserial`` columns plus a plain ``text`` column, with the
expected reverse-engineered DDL preserving the SERIAL keywords.
Would have failed before this fix.

Known coverage gaps (not blockers for this PR):

- PG11 (uses ``pg/11_plus`` / ``pg/default``): test intentionally
  not added there because PG11 still emits ``WITH (OIDS = FALSE)``
  which the existing expected-output convention reflects, and
  without a live PG11 instance I can't verify the expected SQL
  variant. The source-side fix still applies to PG11.
- PPAS (``ppas/*`` test dirs): not added because PPAS output may
  diverge subtly (oraplus / hyphenated identifiers) and I don't
  have a PPAS instance to verify against. The source-side fix
  applies equally to PPAS.
- The SERIAL detection heuristic is name-based
  (``<table>_<col>_seq``); sequence/table/column renames break
  detection. Pre-existing in 115208c8d; could be replaced with
  proper ``pg_depend``-based detection in a follow-up.

Fixes #9896
2026-06-09 13:35:12 +01:00
Ashesh VashiandClaude Opus 4.8 833504f1be fix(schema-diff): aggregate child counts at parent group rows (#10021)
In Tools > Schema Diff, the top-level group rows ("Schema Objects",
"Database Objects") rendered blank difference counts even when their
child object-type groups (Functions, Tables, Sequences, ...) listed
Source-Only / Target-Only / Different objects, giving no signal at the
parent level that anything differed.

The results tree has three levels: top-level object-group rows, mid-level
object-type rows, and leaf object rows carrying a status. setRecordCount()
only handled leaf children, so mid-level rows counted correctly but
top-level rows (whose children are mid-level rows with no status) stayed
at zero. The render gate 'identicalCount' in row also skipped count
rendering for top-level rows because generateGridData() never seeded
those fields on them.

Fix: setRecordCount() now recognises counted-group children (recursing to
refresh their counts so filter changes propagate, then rolling the totals
up); leaf-only behaviour is unchanged. generateGridData() seeds the four
count fields on every top-level group row so the render gate fires. Adds
Jest coverage driving the now-exported setRecordCount directly, including
the reporter's exact tree and a filter-refresh case.

Fixes #9892.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:32:47 +01:00
Ashesh VashiandClaude Opus 4.8 94a7687d2d chore(cloud): remove EDB BigAnimal cloud deployment feature (#10018)
Removes the EDB BigAnimal cloud-deployment integration from the Cloud
Wizard. AWS RDS, Azure Database, and Google Cloud SQL remain as the
supported deployment targets. Covers the backend blueprint and pgacloud
provider, the frontend wizard steps/components/constants/icons, the
documentation page and screenshots, and stray BigAnimal references in
docstrings.

The BigAnimal-only "Cluster Type" wizard step is removed and the
remaining steps renumber from 0-5 to 0-4. As a side effect, the Azure
cluster-name availability check (previously dead code at activeStep == 2,
skipped past by the now-removed step) is now exercised. Two latent bugs
in that check are fixed at the same time: it inspected res.data.success
although checkClusternameAvailbility already resolves res.data, so a name
collision was never reported; and it passed an Error object to gettext(),
which cannot render it. It now uses res.success and surfaces
error.message with a readable fallback.

A 9.16 Housekeeping release-note entry is added documenting the removal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 13:16:48 +01:00
Dave PageandClaude Opus 4.8 1a8dcd8bcc Add release notes scaffold for 9.16. (#10048)
Create release_notes_9_16.rst with empty sections and add it to the
release notes toctree, so that individual fix/feature PRs can add their
entries without each recreating the file.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:36:30 +01:00
Hari Prasad 937d34ea2d refactor: Optimize startup time with adaptive server pinging (#9782)
* Fix exponential backoff logic and ping race condition in desktop startup
* Adaptive polling mechanism in startDesktopMode().
   -  Fix exponential backoff to cap at 1000ms instead of cycling back to 100ms
   - Move pingInProgress flag before the async pingServer() call to prevent race conditions
* Update showErrorDialog to use clearTimeout consistent with setTimeout-based scheduling
2026-06-09 15:33:51 +05:30
Ashesh Vashi ec3e6414e7 fix: accept prepare/binary kwargs in DictCursor.execute(..) (#10030)
psycopg.Connection.execute(query, params, *, prepare=None, binary=False)
delegates to its underlying cursor as
cur.execute(query, params, prepare=prepare). The DictCursor /
AsyncDictCursor in pgadmin.utils.driver.psycopg3.cursor narrowed the
signature to (self, query, params=None), so any caller that uses the
high-level Connection.execute() path with a cursor_factory=DictCursor
connection hits

    TypeError: execute() got an unexpected keyword argument 'prepare'

The most visible victim is psycopg_pool.ConnectionPool.check_connection,
which sends conn.execute("") to validate every checkout — every
connection then looks broken to the pool and getconn() times out.

Forward prepare and binary (keyword-only) to the underlying
psycopg.Cursor / psycopg.AsyncCursor. Defaults match psycopg's own,
so existing callers see no behavior change.

Adds a regression test asserting both classes expose the kwargs as
keyword-only parameters. The test docstring leads with psycopg.Cursor
substitutability (DictCursor is a Cursor subclass; both kwargs must be
accepted to remain substitutable) and notes that Connection.execute is
just the most visible failure path — it forwards prepare but handles
binary by setting cur.format instead of forwarding it.
2026-06-09 14:11:09 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> d2bdd25160 Javascript dependency: Bump axios from 1.16.1 to 1.17.0 in /runtime (#10025)
Bumps [axios](https://github.com/axios/axios) from 1.16.1 to 1.17.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.16.1...v1.17.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-09 09:09:48 +01:00
Ashesh Vashi 5627944f87 chore(deps): bump JavaScript and Python third-party dependencies (#10023)
Combined fix for 8 packages flagged by GitHub Dependabot (collapsing
6 of them from open dependabot bump PRs and 4 from transitive
vulnerabilities with no existing PR). All eight are transitive — no
direct dep changes — so we override via `resolutions` in web/package.json
and let yarn collapse duplicate-version entries during install.

Resolved (pre → post via resolution):

  Runtime:
    ws                    8.20.0    -> 8.21.0    (patched 8.20.1)

  Dev:
    @xmldom/xmldom        0.7.13    -> 0.8.13    (patched 0.8.13)
    serialize-javascript  6.0.2,
                          7.0.5     -> 7.0.5     (patched 7.0.5)
    ip-address            10.1.0,
                          10.2.0    -> 10.2.0    (patched 10.1.1)
    postcss               8.5.8,
                          8.5.15    -> 8.5.15    (patched 8.5.10)
    qs                    6.15.0    -> 6.15.2    (patched 6.15.2)
    @tootallnate/once     2.0.0     -> 2.0.1     (patched 2.0.1)
    tar (7.x lineage)     7.5.13    -> 7.5.16    (patched 7.5.11)

The tar 6.2.1 lineage (consumed via ^6.1.2/^6.1.11) is unaffected by
these CVEs (alert ranges are 7.x-only), so the resolution is scoped
`tar@npm:^7.5.4` to leave it on 6.2.1.

Supersedes open dependabot PRs #9956 (ws), #9962 (tar), #9966
(@tootallnate/once), and #9974 (qs) — one CI cycle instead of four.

Verification:
- yarn install — clean (only pre-existing peer-dep warnings about
  @mui/system, aspen-core, eve, etc.; no new ones)
- yarn run test:js-once — 824 / 824 pass across 140 test suites
- yarn run bundle:dev — webpack compiled successfully
- All 8 packages confirmed at safe versions via lockfile audit;
  duplicate entries collapsed (yarn.lock net -64 lines)

Out of scope (cannot fix here):
- paramiko (#276 #278): no patched version exists; bump-to-5.0.0
  PRs #9927/#9930 audited 2026-05-20 and deferred to Q4 2026 over
  SSH bastion compat risk
- elliptic (#176): no patched version, dev-only, low severity
- flatted (#224): alert is stale; lockfile already at 3.4.2 (patched);
  will auto-dismiss on next dependabot rescan

* chore(deps): bump Python deps to latest 3.9-compatible

Picks up five Python dependency bumps that are 3.9-safe (still resolve
under Python 3.9 per PyPI requires_python). Four supersede open
dependabot PRs:

- certifi              2026.4.22  -> 2026.5.20
    (no gate; CA bundle refresh; supersedes dependabot #9977 / #9979)

- typer                0.25.*     -> 0.26.*
    (py > 3.9 row only; supersedes dependabot #9995 / #9999)

- testscenarios        0.6.1      -> 0.6.2
    (py > 3.9 row only; supersedes dependabot #9980)

- urllib3              2.6.*      -> 2.7.*  (py > 3.9 row only)
    Picks up two HIGH-severity security fixes in urllib3 2.7.0
    (2026-05-07): GHSA-mf9v-mfxr-j63j (decompression-bomb safeguards
    bypassed under drain_conn / Brotli stream patterns) and
    GHSA-qccp-gfcp-xxvc (ProxyManager.connection_from_url did not
    strip sensitive headers on cross-host redirects). 2.7.0 requires
    Python >=3.10, which the existing 'python_version > 3.9' gate
    already enforces.

- Flask-Security-Too   5.4.*      -> 5.6.* (py <= 3.9 row only)
    Closes a roughly 2-year gap between the 3.9 row (last pin from
    March 2024) and the py > 3.9 row (already on 5.8.*). 5.5/5.6
    only touched flows pgAdmin doesn't use (register V2, MFA / WebAuthn
    templates, username recovery/changing, secret_key rotation) and
    config pgAdmin overrides (default hash bcrypt->argon2 sidestepped
    by SECURITY_PASSWORD_HASH = 'pbkdf2_sha512'). The contract changes
    that mattered (LoginForm.validate -> is_active, UserMixin.is_locked
    hook, single-kwarg find_user) are all already exercised in
    production via the existing FST 5.8.* / Python 3.10+ deployments.
2026-06-08 16:02:43 +05:30
Ashesh Vashi f8e570faea ci: run data-isolation tests in server mode (#10019)
The existing run-python-tests-pg.yml workflow hardcodes
SERVER_MODE = False in config_local.py. Every test that gates
itself on `config.SERVER_MODE` — including the data-isolation
suites — skips itself in CI today. That gap is what allowed the
admin-bypass regression in 9a76ed8 to ship (see #9933, #10006):
the change to web/pgadmin/utils/server_access.py changed
access-control behaviour but the only tests covering it were
server-mode-only and therefore never ran.

This workflow plugs that gap with a narrow, cheap server-mode
CI job:

- Single OS (ubuntu-22.04), single PG version (18) — no matrix
- SERVER_MODE = True in config_local.py
- Runs only the two data-isolation test modules:
    browser.server_groups.tests.test_sg_data_isolation
    browser.server_groups.servers.tests.test_server_data_isolation

Locally with SERVER_MODE=True both modules finish in well under
half a second (3 + 6 tests), so the marginal CI cost is dominated
by the PG/python setup, not the tests themselves.

Future access-control changes to server_access.py (or related
helpers) will fail this workflow if they regress the existing
isolation guarantees, before they reach master
2026-06-08 11:11:12 +05:30
Kevin Bell 92dd7c7962 Fix README typos (#9920) 2026-06-07 18:52:27 +05:30
Murtuza Zabuawala c7d2106462 fix: filtering pg_attribute only by attname can return the wrong attnum if the column name exists in multiple table (#10013) 2026-06-07 18:47:39 +05:30
Murtuza Zabuawala 291a55ec5a chore: Add pg_catalog schema prefix to catalog table queries (#10004) 2026-06-07 12:57:59 +05:30
Dave PageandClaude Opus 4.7 10f99d07e3 chore(regression): remove dead test_advanced_config + Greenplum config gitignore (#9949)
Two related bits of long-stale plumbing in web/regression/:

* test_advanced_config.json.in — the README has told users for years to
  copy this template to test_advance_config.json (no "d") and customise
  it, but a repo-wide grep for either spelling shows zero code, test,
  or CI references. The .in template is dead, and the README/.gitignore
  also disagree about whether the copied filename has a "d" in it,
  which is a typo trap that has gone unnoticed precisely because nobody
  actually performs the copy step.

* test_greenplum_config.json — Greenplum support was removed from
  pgAdmin years ago (only references left in the tree are historical
  release-notes entries from versions 1.4 through 4.12). Nothing reads
  the config any more.

Drop the dead template, prune both stale lines from
web/regression/.gitignore, and simplify the README to describe just
the server-side test_config.json that the framework actually consumes.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 12:43:17 +05:30
Ashesh Vashi ed9dcf6ebf chore(deps): bump electron 41.5.0 -> 42.1.0 and pin packaged version (#9959)
Bumps the desktop runtime to electron 42 (dependabot PR #9945) and
closes a supply-chain gap in the Linux/Mac packaging scripts that
predated this bump.

Why the bump is safe:

  - macOS UNNotification API change — pgAdmin's runtime does not use
    Electron's Notification API (only a UI toast comment in
    src/js/pgadmin.js:211; no `new Notification(...)` anywhere).
  - postinstall no longer downloads electron — production packaging
    fetches the binary directly via wget from GitHub releases, never
    via electron's postinstall script.
  - Offscreen rendering scale-factor change — no OSR usage anywhere
    in runtime/src/js/.

While verifying, found that pkg/linux/build-functions.sh and
pkg/mac/build-functions.sh resolve the packaged electron version
via:

    ELECTRON_VERSION="$(npm info electron version)"

This pulls whatever currently carries the `latest` dist-tag on the
npm registry. Any newly published electron release — including a
hypothetical malicious one — would land in shipped binaries without
review, regardless of what runtime/package.json pins.

Replace with sed-based extraction from runtime/package.json and
fail loudly if extraction returns empty. The Windows installer
(pkg/win32/installer.iss.in) does not have this issue (it bundles a
pre-built tree, no electron download step).

Net change in runtime/yarn.lock is mostly deletions — electron 42
ships with @electron/get 5.x, which dropped a large transitive
dependency tree associated with the old postinstall download path.

Verified:

  - eslint (runtime): clean (silent)
  - yarn install (runtime): resolved to electron 42.2.0 within
    ^42.1.0 range
  - sed extraction smoke-tested: returns 42.1.0 from current
    runtime/package.json
2026-06-07 12:08:56 +05:30
Ashesh Vashi 148705994d fix: remove admin bypass from server access helpers (#9933) (#10006)
In server mode, administrators were auto-granted visibility into every
user's private server groups and servers via four _is_admin() bypasses
in server_access.py. This made the Object Explorer show a separate
top-level "Servers" entry per user when logged in as admin, exposing
private connections the admin should have no access to.

The Administrator role in pgAdmin governs management of pgAdmin itself
(users, preferences) — it is not intended to inherit other users'
database credentials and connection state. Cross-user visibility
requires explicit sharing (Server.shared=True), same as for any user.

Remove the admin bypass from get_server, get_server_group,
get_server_groups_for_user, and get_user_server_query. Drop the now-
unused _is_admin() helper. Update docstrings to make the policy
explicit.

Add a regression test (admin attempts to fetch a non-admin user's
private server group → expect HTTP 410). The original isolation test
only covered non-admin → admin, which is why the regression
introduced by 9a76ed8 was not caught.
2026-06-06 09:10:59 +05:30
hiteshjambhale 679c39f944 fix: resolve E501 pycodestyle line-too-long in llm tools database module (#10003) 2026-06-05 12:25:04 +05:30
Dave Page 2dc9d71d02 fix(webpack): alias react-checkbox-tree to its ESM bundle (#9989)
react-checkbox-tree v2 marks the package as "type": "module" but its
"require" exports condition still points at a UMD bundle. babel-loader
turns our ESM imports into require() calls, so webpack picks the UMD
file and treats it as ESM (because of "type": "module"). The UMD
wrapper's module.exports = factory(...) never runs in that context, and
the default export ends up undefined - causing CheckBoxTree to render
"Element type is invalid" in dialogs like Import/Export Servers.

Alias react-checkbox-tree to its ESM bundle (lib/index.esm.js, exposed
by the package's own "./*": "./*" exports map) so webpack picks the file
that actually has a default export.

Closes #9972
2026-06-05 09:57:09 +05:30
Jaspar Martin 27406b5627 fix: fixed invalid '| None' syntax for python 3.9 (#9993) 2026-06-05 09:55:48 +05:30
Domenico Sgarbossa 5c494ecbf6 Fix (#9917)
* Updated Italian translation for v9.15
2026-06-05 09:55:07 +05:30
Ashesh Vashi 0d11dbc173 fix(docker): make CAP_NET_BIND_SERVICE optional for restricted runtimes (#9985)
The container previously applied CAP_NET_BIND_SERVICE to the python
interpreter so the non-root pgadmin user could bind to ports 80/443.
Some platforms refuse to honor file capabilities:

  - --cap-drop=ALL / OpenShift restricted-v2 SCC zero the bounding set,
    so the kernel returns EPERM on exec of any capability-tagged binary.
    This makes the image fail to start (issue #9657).
  - --security-opt=no-new-privileges / allowPrivilegeEscalation: false
    causes the kernel to silently strip file capabilities on exec, so
    the binary runs but a subsequent bind() to <1024 still fails.

Split the interpreter so neither default behavior nor restricted-runtime
support has to give up the other:

  - Dockerfile copies python3.X to /usr/local/bin/python3-cap and applies
    setcap to the copy. /usr/local/bin/python3.X stays un-capped, so
    /venv/bin/python3 (which symlinks to it) execs cleanly under
    restricted SCCs. A parallel /venv/bin/python3-cap symlink keeps the
    venv activation working when the capped interpreter is used.
  - entrypoint.sh reads /proc/self/status at startup. If NoNewPrivs is
    set, or CAP_NET_BIND_SERVICE is missing from the bounding set,
    gunicorn is invoked through the un-capped python and (when
    PGADMIN_LISTEN_PORT is unset) the default port falls back to 8080
    for plain HTTP or 8443 for TLS. A startup message records the
    choice.
  - Existing deployments with the default 80/443 mapping are unaffected:
    on every unrestricted runtime the bounding set still contains
    NET_BIND_SERVICE and gunicorn runs through the capped interpreter
    exactly as before.
  - PGADMIN_LISTEN_PORT, if set, is honored in both paths.

Docs gain a "Restricted Security Contexts" subsection covering the new
auto-detected fallback and the OpenShift / --cap-drop=ALL invocation.

Fixes #9657
2026-05-28 19:31:55 +05:30
Ashesh Vashi 533aed122f build(mac): drop webpack heap from 6 GB to 4 GB on the macOS build (#9968)
The 6 GB ceiling set by #9967 was too aggressive for the macOS x64
VM's total RAM. Build #1295 on `pgabf-macos-x64` failed in
`_build_runtime` at `unzip electron-vX.X.X-darwin-x64.zip` with exit
code 2 — never even reached webpack. That points at OS-level memory
pressure spilling out of the Node process and starving the rest of
the build: at 6 GB reserved, the box runs out of RAM long before
Terser actually needs the full ceiling.

Drop back to 4 GB, which still gives Terser a full extra gigabyte
beyond the original 3 GB setting that OOM-killed webpack in #1294,
but leaves enough headroom for the other steps in the appbundle
build to coexist.

Only the macOS appbundle path changes (see pkg/mac/build-functions.sh);
linux/pip/Makefile and dev-machine builds keep the 3 GB the `bundle`
npm script ships with.
2026-05-22 15:00:09 +05:30
Ashesh Vashi 3e1921dcd9 build(mac): bump webpack heap to 6 GB on the macOS appbundle build (#9967)
macOS x64 appbundle builds keep dying inside webpack's TerserPlugin at
92% (asset processing). Build #1294 on `pgabf-macos-x64` reached
`<s> [webpack.Progress] 92% [0] sealing asset processing TerserPlugin`
and was killed without producing a V8 fatal-error preamble, which
points at the OS reaping the Node process under memory pressure rather
than V8 hitting its own heap ceiling.

TerserPlugin is already running single-threaded (see
web/webpack.config.js, `parallel: false`), so we can't claw memory back
by reducing parallelism. Bump the V8 old-space ceiling from 3072 MB to
6144 MB inside the macOS appbundle build only — the helper in
pkg/mac/build-functions.sh bypasses `yarn run bundle` and calls
`yarn run webpacker` directly (see commit d96e8634), so this knob is
independent of the npm script and does not affect linux/pip/Makefile
or dev-machine builds. They keep the 3 GB the `bundle` script has been
shipping with for years.

If this still doesn't get the x64 box past Terser we'll switch the
minimiser to esbuild via terser-webpack-plugin's `minify` option; that
is a larger and more invasive change so we are trying the cheap fix
first.
2026-05-22 14:43:58 +05:30
Ashesh Vashi d96e86345e build(mac): split bundle step and capture stderr for diagnostics (#9965)
The macOS x64 appbundle build can fail inside `yarn run bundle` while
producing zero console output -- the Jenkins log goes straight from
"yarn install ... Done with warnings" to the EXIT trap's failure
message, leaving no signal as to whether linter, webpack, or a native
module load was the culprit (build #1293 on pgabf-macos-x64 is the
prompting example).

Split the bundled script into its constituent steps and merge stderr
into stdout so any error text reaches the console even if Jenkins'
shell step drops a tail buffer:

  yarn install
  yarn run git:hash    # cheap source-hash capture, moved up front
  yarn run linter
  yarn run webpacker

`git:hash` is a pure `git log` redirect (see web/package.json) with no
node-module dependency, but `yarn run` needs node_modules so it stays
after install. Pulling it before the heavy steps means the commit_hash
file lands on disk even if webpack later bails out.

Env vars NODE_ENV=production and NODE_OPTIONS=--max-old-space-size=3072
are set explicitly to mirror the cross-env wrapper inside the top-level
"bundle" npm script, so build output stays byte-identical to before.

No-op for successful builds; pure diagnostics win on failure.
2026-05-22 13:58:06 +05:30
Ashesh Vashi ae6dc4ba18 fix(test): stabilize bgprocess polling in backup/restore/IE/maintenance tests (#9958)
The shared polling helpers in:

  - web/pgadmin/tools/backup/tests/test_backup_utils.py
  - web/pgadmin/tools/import_export/tests/test_import_export_utils.py
  - web/pgadmin/tools/maintenance/tests/test_create_maintenance_job.py
  - web/pgadmin/tools/restore/tests/test_create_restore_job.py

all share the same race that surfaced on macos-latest / pg16 in
PR #9955's CI run:

  - Wait budget was 2.5s (5 iterations x 0.5s; maintenance used 5s).
  - The break condition was `execution_time' in the_process`, but
    `execution_time` is the elapsed time of a *running* bgprocess --
    it is set before the wrapped pg_dump / pg_restore / psql / COPY
    actually finishes. The completion signal is `exit_code` becoming
    non-None.
  - So the helper could return control while the wrapped command was
    still running, and the next assertion -- e.g.
    `assert_equal(the_process['exit_code'] in [0, 1], True)` -- would
    fire on `None in [0, 1]`, i.e. `False != True`.

Some scenarios masked the bug by listing `None` in their
`expected_exit_code` set (a tell that someone noticed the polling was
unreliable and worked around it by widening accepted exit codes).
Scenarios that didn't include `None` were the ones that flaked.

Fix all four helpers identically:

  - Poll for up to 60 iterations x 0.5s = 30s, generous enough for
    the slowest CI runner.
  - Break only when `the_process.get('exit_code') is not None`, the
    actual completion signal.
  - Narrow `except Exception` to `except StopIteration`, which is the
    only thing `next(...)` here can raise.

No call-site changes needed; the helper contract (returns once the
job is done; raises if the bgprocess never finished) is unchanged in
spirit and strictly more reliable in practice.

Verified:

  - pycodestyle on the four files: 0 violations.

This fixes the failure observed in the macos-latest / pg16 leg of
PR #9955's CI run (run 26154521710, job 76930277702), which was
unrelated to that PR's lockfile-only changes.
2026-05-20 18:58:32 +05:30