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
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
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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>
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.
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.
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
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>
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>
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>
* 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
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.
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.
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
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>
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
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.
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
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
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.
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.
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.
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.