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.
Supersedes dependabot #9926 (and its /web/regression duplicate
#9932). Inherited via `-r ../../requirements.txt`, so the single
edit covers both.
cryptography 48 is a smaller bump than its major-version label
suggests:
- Removed Python 3.8 support. pgAdmin requires Python 3.9+ across
the supported platforms, so this is a no-op for us. (3.9.0 and
3.9.1 specifically are excluded by the new metadata; nothing
in pgAdmin's CI / packaging runs those exact patch versions.)
- Stricter X.509 CRL parsing: a CRL whose inner
`TBSCertList.signature` does not match the outer
`signatureAlgorithm` now raises `ValueError` instead of
being parsed and rejected later during signature verification.
- Added ML-KEM and ML-DSA post-quantum primitives (additive).
pgAdmin's cryptography surface area is narrow and CRL-free:
- web/pgadmin/settings/__init__.py Fernet
- web/pgadmin/utils/session.py Fernet, hashes, HKDF
- web/pgadmin/utils/crypto.py Cipher, AES, CFB8
No imports of `cryptography.x509`, `CertificateRevocationList`,
or `load_pem_x509_crl` anywhere in the tree, so the stricter CRL
parsing in 48 cannot affect pgAdmin.
The OpenSSL 1.1.x / LibreSSL < 4.1 removal that I initially
flagged as a concern actually happened in cryptography 47, which
master is already on. No platform-support regression from this
bump.
Pure lockfile-only updates — no package.json changes. Dependabot
surfaced these as separate PRs because they sit below pgAdmin's
direct deps in the resolution tree, so the manifest-level bumps
applied in #9954 did not pull them along.
web/yarn.lock:
- @babel/plugin-transform-modules-systemjs 7.29.0 -> 7.29.4 (#9923)
- devalue 5.7.0 -> 5.8.1 (#9937)
- fast-uri 3.1.0 -> 3.1.2 (#9922)
- svelte 5.55.1 -> 5.55.8 (#9938)
(5.55.8 supersedes the 5.55.7 dependabot was tracking when the PR
opened; both are within the same ^5.0.0 range.)
runtime/yarn.lock:
- fast-uri 3.1.0 -> 3.1.2 (#9924)
All resolutions stay within their existing semver ranges declared by
the parent packages — no manifest constraints touched. Refreshed via
`yarn up -R <pkg>` in each workspace.
libpq 18 dlopens libpq-oauth-18.so (the SASL OAUTHBEARER flow plugin)
when connecting to a server with an `oauth` pg_hba.conf rule. The
container previously copied only libpq.so.5.18 from postgres:18-alpine
and omitted both the plugin and its libcurl runtime dependency, so
OAuth connections failed with "no OAuth flows are available (try
installing the libpq-oauth package)" before any token exchange could
begin.
Add libpq-oauth-18.so to the existing pg18-builder COPY (it sits next
to libpq.so.5.18 in /usr/local/lib in postgres:18-alpine) and install
the libcurl apk package so the plugin can dlopen libcurl.so.4 at
runtime.
Closes#9951
MaintenanceInputValidationAcceptsValidTest asserted batch_process_mock
was called, but the route short-circuits with success=0 (HTTP 200)
when does_utility_exist() returns a missing-binary error. On Windows
CI psql.exe is not at the path pgAdmin probes, so BatchProcess was
never reached and the five 'accepted' scenarios failed with
'AssertionError: False is not true'. Stub does_utility_exist to None
in this test so it focuses on input-validation acceptance, not on the
runtime PostgreSQL binary layout.
boto3 1.43.0 requires Python >=3.10, which breaks installs on Python
3.9. Add a python_version gate so 3.9 stays on the 1.42.x series (the
last to support 3.9) while newer interpreters track 1.43.*.
Sphinx ran with -W (warnings as errors) and failed both build-docs and
build-python-package CI jobs because release_notes_9_15.rst was not
referenced from any toctree.
Adds entries for items that landed after the release notes were first
written:
- Housekeeping: #9906 (Italian translation update, merged from
origin/master).
- Test-suite stability: 208541cc4 (ImportExportServersTestCase
sys.executable + subprocess error surfacing).
- Documentation (new subsection): 9923eefca (clarification that
MAX_LOGIN_ATTEMPTS applies only to INTERNAL auth; LDAP / OAuth2 /
Kerberos / Webserver brute-force protection is the upstream IdP's
and reverse-proxy's responsibility).
- Dependencies (new subsection): the cumulative non-breaking
dependabot updates aggregated for v9.15, organized by Python /
JavaScript (web/) / JavaScript (runtime/), listing the
package-level diffs without commit-keyed framing. Covers both
aggregating commits (d55ffe405 and 4330a688a) so a reader looking
for "what versions did 9.15 ship with" finds it in one place.
Sphinx build remains clean for release_notes_9_15.rst.
Python (requirements.txt):
- boto3 1.42.* -> 1.43.* (#9908)
- psycopg 3.3.3 -> 3.3.4 (#9911) for python_version >= '3.10'
JavaScript (web/package.json, web/yarn.lock):
- axios 1.15.2 -> 1.16.0 (matches dependabot's #9907 in /runtime,
applied to /web for cross-package consistency)
Electron runtime (runtime/package.json, runtime/yarn.lock):
- axios 1.15.2 -> 1.16.0 (#9907)
- electron 41.3.0 -> 41.5.0 (#9910)
- eslint 10.2.1 -> 10.3.0 (#9912)
- globals 17.5.0 -> 17.6.0 (#9909)
follow-redirects 1.15.11 -> 1.16.0 transitively
Skipped (genuine breaking changes, deferred to a future minor):
- @mui/material 7 -> 9 (#9843)
- @mui/x-date-pickers 8 -> 9 (#9888)
Verified in an isolated worktree:
- jest: 140/0/0 suites, 824/0/0 tests
- eslint: clean (silent)
- pycodestyle: 0 violations project-wide
- python regression: 1879/0/308 (PG18, --exclude feature_tests)
The axios 1.16.0 release notes call out three observable changes; only
the first is potentially relevant to pgAdmin and is a bugfix:
- Fetch adapter now enforces maxBodyLength / maxContentLength (these
were silently ignored on the fetch adapter before 1.16.0). pgAdmin
does not set these limits, so behaviour is unchanged.
- Proxy requests preserve user-supplied Host headers — pgAdmin does
not proxy through axios.
- Basic-auth credentials embedded in URLs are URL-decoded — pgAdmin
does not construct credential-embedded URLs.
psycopg 3.3.4 brings three bugfixes: spurious connection-timeout in C
extension on long-uptime systems, client-side adaptation of enums whose
names need quoting, and consistent Cursor.statusmessage after
executemany().
electron 41.5.0 is a patch within the 41.x line carrying Chromium
security backports plus a Windows frameless-window resize regression
fix and a low-level mouse-hook teardown fix.
Replaces the "(CVE pending)" markers on the seven placeholder issues
with their assigned identifiers:
#9830 -> CVE-2026-7813 (cross-user data access / shared-server escalation)
#9865 -> CVE-2026-7814 (stored XSS via crafted PostgreSQL object names)
#9898 -> CVE-2026-7815 (SQL injection in Maintenance tool option values)
#9899 -> CVE-2026-7816 (OS command injection in Import/Export query export)
#9900 -> CVE-2026-7817 (LFI/SSRF in LLM API configuration endpoints)
#9901 -> CVE-2026-7818 (unsafe deserialization in session manager)
#9902 -> CVE-2026-7819 (symlink path traversal in file manager)
#9904 -> CVE-2026-7820 (account-lockout bypass via Flask-Security /login)
#9835 is a follow-up to #9830 and shares CVE-2026-7813, so its
parenthetical is dropped rather than replaced with a separate ID.
To be revealed publicly when this branch is pushed for the 9.15 release.
24 violations had accumulated on the cve-9.15 branch from the #9901 and
#9902 CVE-fix work and a couple of older spots. Surfaced when the full
suite was run after the #9904 work; no functional change.
- 22 x E501 (line too long > 79):
- 15 in pgadmin/misc/file_manager/tests/test_filemanager_security.py
(13 class declarations using two mixin parents,
2 docstrings)
- 6 in pgadmin/utils/tests/test_session_file_format.py
(5 class declarations, 1 docstring)
- 1 in pgadmin/browser/tests/test_kerberos_with_mocking.py
(extracted self.app.url_map._rules_by_endpoint into a local
before the membership test)
- 2 x E305 (expected 2 blank lines after class/function):
- pgadmin/misc/file_manager/__init__.py (after _open_upload_target)
- pgadmin/browser/__init__.py (after _first_form_error)
Class declarations are wrapped via parenthesised continuation, the
standard pgAdmin convention; docstrings are either shortened or wrapped
across two lines preserving the same meaning. Verified:
- pycodestyle clean project-wide (24 -> 0).
- Affected tests still pass: test_filemanager_security 17/0/0,
test_session_file_format 18/0/0, test_kerberos_with_mocking 2/0/3
(skips are pre-existing and unrelated -- Kerberos blueprint not
loaded in default config).
Two latent bugs in pgadmin/setup/tests/test_export_import_servers.py
caused this test to fail on macOS / modern Linux distros:
1. The test invoked the subprocesses with a hardcoded "python" via
os.system. macOS and many modern Linux distros do not provide a
"python" alias — only "python3" or a venv-specific binary. The
subprocess fails with "sh: python: command not found", exits non-
zero, and writes nothing to the dump-servers output file.
Same root cause that a50a553b0 ("chore: feature tests use
sys.executable") fixed for AppStarter; this is the same fix in the
regression test.
2. Both os.system calls redirected stderr to /dev/null. When the
subprocess failed the empty output file then tripped json.loads
with the misleading "Expecting value: line 1 column 1 (char 0)"
instead of surfacing the underlying "command not found" error.
Switch to subprocess.run with a list (so there is no shell quoting and
no command-injection surface), use sys.executable, capture stderr, and
self.fail() with the captured stderr if the subprocess exits non-zero.
Verified: setup.tests goes from 4 pass / 1 fail / 1 skip to 5 pass / 0
fail / 1 skip on PG18.
The "Avoiding a bruteforce attack" section in login.rst implied that
MAX_LOGIN_ATTEMPTS protected all logins; in fact it only applies to the
INTERNAL authentication source (the /authenticate/login view filters by
auth_source=INTERNAL). Operators using LDAP / OAUTH2 / KERBEROS /
WEBSERVER got no signal that brute-force protection lives at a different
layer.
Adds:
- login.rst: the bruteforce section now states that MAX_LOGIN_ATTEMPTS
is INTERNAL-only and points operators of external sources to the
upstream identity provider's lockout policy and to the reverse proxy
for IP-based throttling.
- ldap.rst: a new "Brute-force protection" section calling out that
LDAP credential lockout is the directory's responsibility (ppolicy /
AD account-lockout GPO) and that request rate-limiting belongs on
the proxy. Cross-references the login-page section.
No code change. Closes the documentation gap that surfaced during the
analysis of the #9904 lockout-bypass fix without inheriting the
maintenance burden of duplicating directory lockout in pgAdmin.
Mirrors the CVE-pending entries for #9898-#9902 with reporter credit.
The detailed CVE record (CVSS, description, affected files) is held in
the local docs/CVEs/ working draft and submitted to MITRE/Vulnogram
externally; only the release-notes summary lands in git pre-disclosure.
pgAdmin enforces MAX_LOGIN_ATTEMPTS only inside its own /authenticate/login
view. Flask-Security's default /login view (registered automatically by
security.init_app) was reachable but never consulted the locked field:
- User inherited Flask-Security's UserMixin.is_locked(), which always
returns True (= "not locked, proceed").
- User inherited Flask-Login's is_active, which only checks the active
column, not locked.
So an attacker with valid credentials could bypass a lockout by posting
to /login after triggering the counter at /authenticate/login.
Override both contracts on the User model so every auth path inherits
them:
- is_active: False when active=False OR locked=True, so
flask_security.login_user() refuses to mint a session.
- is_locked(form_error): returns False (= "locked") and appends the
same "Your account is locked" message /authenticate/login uses, so
LoginForm.validate() rejects the submission cleanly.
Only INTERNAL accounts are reachable by the bypass (LDAP/OAuth2/Kerberos/
Webserver users have no local password, which LoginForm.validate rejects
before the locked check) and lockout itself is internal-only (auth_source
filter at authenticate/__init__.py:124), so the overrides only take
effect for INTERNAL accounts.
Adds a unit test covering the four (active, locked) combinations of the
contract LoginForm.validate() depends on.
Also includes a SQLite-only data-cleanup migration. Migration 6650c52670c2
added the locked column with server_default='false'. SQLite has no native
BOOLEAN affinity, so the literal 'false' was stored as TEXT both in the
column DEFAULT and on existing rows backfilled by ALTER TABLE.
SQLAlchemy's Boolean processor then reads that TEXT back as Python True
(because bool('false') is True for any non-empty string), which means
the new is_active override would otherwise see every legacy row as
locked and refuse login. The new migration normalize_locked_text_default
rewrites every SQLite row's locked to integer 0/1 (NULL -> 0); on a
PostgreSQL config DB the column was always stored as a proper BOOLEAN
so the migration is a no-op there (and integer literals would fail
on a BOOLEAN column anyway). SCHEMA_VERSION is bumped from 51 to 52.
Reported-by: Fernando Bortotti <fernando.bortotti@bsd.com.br>
Drafts the user-facing release notes for the 9.15 release. Covers all
47 non-merge commits since REL-9_14:
- 18 issue-linked entries under New features / Housekeeping / Bug fixes,
with reporter credits (names only) for the six external CVE reports
- #9901 absorbs 14 follow-up commits (session encryption + 0o600,
SHA-256 digest, drop of live AuthSourceManager / cloud provider
instances, DATA_DIR perms, log file mode, log handler hardening,
user_info_server prompt-loop bound) via "Also..."
- #9830 absorbs the @with_object_filters extension to ServerNode.list
- 14 commits without an associated GitHub issue listed under
"Additional changes" (bug fixes / test stability / refactoring /
housekeeping) for transparency
CVE IDs are placeholders ("CVE pending") and will be filled in once
MITRE assigns them. Release date and bundled-utility version are
also placeholders pending the actual release.
click_modal: wait up to 5s for the MuiDialog-backdrop to become
invisible after clicking the modal button. MUI v7 leaves the backdrop
in the DOM during the ~300ms close animation, which intercepts the
next click in tests that chain modal->modal interactions.
open_query_tool: the execute-query toolbar button can re-render between
the visibility wait and the ActionChains.move_to_element call
(Firefox/geckodriver hits this regularly). Retry the move up to 3
times on StaleElementReferenceException, refetching the element each
attempt rather than reusing a stale handle.
Both changes are pure test-side stability fixes; no production code
or behavior is affected.
1. AppStarter.start_app() spawned the pgAdmin subprocess with bare
"python" via subprocess.Popen. macOS (and many modern Linux
distros) do not provide a "python" symlink — only "python3" or a
venv-specific binary. The result was an Errno 2 "No such file or
directory" the moment a feature test tried to launch pgAdmin.
Switch to sys.executable so the spawned pgAdmin uses the same
interpreter and venv as the test runner. Works regardless of how
the venv is named or whether "python" is on PATH.
2. yarn.lock churn from `yarn install` resolving the @tanstack /
moment-timezone / postcss peer-dep ranges. Snapshot the resolved
versions for reproducibility (post the dependency bumps in
d55ffe405).
- Database terminology corrections (bord → tabell, Visa → Vy, etc.)
- Standardize Tabellrymd, Sammansatt utlösare
- Fix punctuation in placeholder lists (Oxford-comma alignment)
- Drop incorrect fuzzy flag from .po header
Note: msgid "on" (from ai_tools.js) is missing here because the PR's
.po was regenerated against a slightly older source tree; it will be
re-added on the next `make messages` run.
Two related issues, both surfaced when running the regression suite
non-interactively:
1. user_info_server()'s while-not-validate retry loops had no upper
bound. With a mocked or closed stdin (test mocks, EOF in CI/cron,
typo'd PGADMIN_SETUP_EMAIL=''), the loop would call input()/pprompt()
forever, printing 'Invalid email address. Please try again.' on
every iteration. We saw this manifest as a 13.5 million-line
25 GB log when test_no_email_deliverability hit the case.
Cap each loop at MAX_PROMPT_ATTEMPTS=5 and raise RuntimeError with a
pointer to PGADMIN_SETUP_EMAIL/PASSWORD env vars on exhaustion.
2. test_no_email_deliverability included pg@postgres.local in its
"should be accepted with deliverability=False" data set. The .local
suffix is in email_validator.SPECIAL_USE_DOMAIN_NAMES which fails
syntactic validation regardless of the deliverability flag, so
validate_email always rejected it - looping forever pre-fix #1.
Set config.ALLOW_SPECIAL_EMAIL_DOMAINS = ['local'] in the test's
try/finally block so .local is allowed for this scenario, restored
afterward to avoid leaking state into other tests.
With both fixes in place, the test can be removed from the regression
runtests --exclude list.
Two test-infra issues exposed by the full regression suite:
1. PSQL socket tests (test_backend_task, test_psql_input,
test_resize_terminal, test_socket_disconnect, test_start_process)
created a fresh, unauthenticated app.test_client() and built a
socketio test client around it. The /pty connect handler is
wrapped with @socket_login_required, so the unauthenticated client
was rejected and is_connected('/pty') returned False.
Switch to the authenticated self.tester from BaseTestGenerator so
the connect handler accepts the connection. Matches the pattern
already used by BaseSocketTestGenerator (test_socket_connect,
test_psql_disabled).
2. test_role_dependencies_sql creates a temporary LOGIN role and
calls create_table as that role. On clusters where pg_hba.conf
does not allow arbitrary roles to connect from 127.0.0.1, the
create_table swallows the connection error via try/except and
the subsequent pg_class lookup returns no row, surfacing as an
opaque "NoneType is not subscriptable" error.
Detect the empty fetchone() and skipTest with a clear message
pointing at pg_hba.conf so the test fails gracefully on
environmentally-restricted clusters instead of erroring.
Tests that worked on legacy CI envs but failed under newer
email_validator / non-postgres-named superuser / no-app-context
on tearDown / etc.:
1. test_import_export_create_job_unit_test.py - E-string scenario
was labelled "Rejected: ... (false positive, safe)" expecting
parser over-rejection. The parser actually correctly identifies
the query as balanced (close paren is inside the literal). Update
expected to True and rename scenario to reflect the real
handling.
2. test_sg_data_isolation.py - tearDown does ORM query/delete after
the @create_user_wise_test_client wrapper has popped the test
client/context. Wrap tearDown's ORM ops in an explicit
app_context().
3. test_validate_user_email.py - the "with deliverability" scenario
used postgres@local.dev expecting rejection. Newer email_validator
no longer rejects this. Switch to postgres@nonexistent.invalid,
which is RFC-reserved and rejected regardless of DNS state. Also
align expected message with the actual format string used by
validate_user.
4-5. test_grant_wizard_save_permissions.py and
test_grant_wizard_get_sql.py - test data hardcoded 'postgres' /
'enterprisedb' as grantee/grantor. On clusters where the
superuser is named differently (e.g. dev Homebrew installs use
the local OS account), the GRANT statement fails with
"role X does not exist". Use self.server['username'] instead.
6. test_role_dependencies_sql.py - the test creates a fresh LOGIN
role and tries to CREATE TABLE as that role; without explicit
CREATE-on-database grant, the create silently fails and the
subsequent pg_class lookup returns no row. Mark the test role
SUPERUSER so it can create the table.
All are test-infra fixes; none of the production code paths under
test were modified.
Two related issues that surfaced in BatchProcessTest:
1. ConnectionLocker.__enter__ accessed Flask session (via 'in' check)
AFTER acquiring the lock. When called outside a request context,
session access raises RuntimeError. Python's 'with' semantics skip
__exit__ if __enter__ raises, so the lock leaked - any subsequent
call to ConnectionLocker hung forever waiting on a lock that
would never be released.
Wrap session access in try/except RuntimeError so missing-request-
context falls through cleanly and the lock is released normally
on with-block exit. Also use .get() chains so partial session
shapes do not raise KeyError.
2. The four batch_process unit tests (backup, import_export,
maintenance, restore) used app_context() instead of
test_request_context(). flask-babel's gettext() in
BackupMessage.details() and similar code paths require a request
context; ConnectionLocker.__enter__ also touches session as above.
Switching to test_request_context('/') gives both the request
binding they need.
Verified: tools.backup.tests.test_batch_process now runs 4/4 passing
(was 3 ERROR + 1 hang before fix#1; 3 FAIL before fix#2).
Both are pre-existing issues exposed by Python 3.14 / flask 3.1 /
flask-babel stricter context enforcement; not introduced by 9.15
CVE work.