Compare commits

...
584 Commits
Author SHA1 Message Date
Ashesh Vashi f09776c22d test(backup, restore): fix stale XSS check after PGDATABASE dbname fix
Backup/restore now pass the target database via PGDATABASE instead of
a CLI/positional argument, so the XSS regression test's check against
the displayed command string could never find the db name (escaped or
not) and always failed. Point Backup's check at the message field,
where the db name is still interpolated; for Restore, where the db
name no longer surfaces anywhere (command shows an always-empty
--dbname, message omits it entirely), assert its absence instead.
2026-09-15 18:11:25 +05:30
Ashesh Vashi d64bc8c24c Updated message catalogs for v9.18
Also drop the fuzzy marker from the Korean catalog header. Babel skips a
catalog whose header is fuzzy, and ko's header still carried the
unfilled template placeholders that make babel mark it so, with the
result that ko/LC_MESSAGES/messages.mo had not been regenerated since
November 2025 -- 83KB against the 263KB the .po compiles to, so most of
the 89% of Korean strings that are translated never reached users.
2026-09-15 17:19:11 +05:30
Ashesh Vashi 1c66edd12d docs: cite a GitHub issue for every 9.18 release note entry
The file mixed issue-backed entries with commit-id ones. Make every
entry issue-backed:

  - The LLM API redirect hardening had no issue, and its entry was the
    only record of Ziya Abdullayev's report outside the commit message.
    It gets #10420, written up with the mechanism, the affected range
    (>= 9.15, since ALLOWED_LLM_API_URLS did not exist before then) and
    the fix commit, and noting that it is hardening rather than an
    exploitable flaw, so no CVE was requested.

  - The Table dialog fix closed two issues, #10179 and #10180, so it
    becomes two entries. That also covers the Definition-tab type
    restriction, which the single commit-id entry had omitted.

  - The remaining commit-id entries are dropped.
2026-09-15 17:02:00 +05:30
Ashesh Vashi 36e08d6d8a fix(maintenance): drop a duplicated comment block above the database guard
The four-line comment explaining why an empty database must be rejected
was pasted twice above the guard in create_maintenance_job(). Comment
only; no behaviour change.
2026-09-14 23:08:27 +05:30
hiteshjambhale 59f8d82aa9 fix(restore, maintenance): reject empty/missing database to close role-name fallback
The PGDATABASE fix only set the env var when the database value was
truthy, so an empty string left both PGDATABASE and --dbname unset.
libpq then falls back down its chain to a database named after the
login role, silently restoring into / vacuuming the wrong database
instead of failing. Reject an empty or missing database with a 400
before any args are built, in both tools, and add regression tests
covering the empty-value path.
2026-09-14 17:33:20 +00:00
hiteshjambhale 3922cdfcbf test(restore, maintenance): harden arg-injection guards per review
- Scan every argv token for the forbidden value (forbidden_arg_substr)
  instead of list-membership only, so a value smuggled into a single
  token (e.g. "--dbname=host=...") is caught.
- Assert pg_restore keeps the required "--dbname=" flag, guarding against
  a regression that drops it (pg_restore errors without -d/--dbname).
2026-09-14 17:33:20 +00:00
hiteshjambhale 038a2619fa test(restore, maintenance): add arg-injection regression tests; fix pg_restore --dbname display
- Add regression scenarios asserting the database value is absent from
  argv and carried in PGDATABASE (maintenance: 1, restore: 2). They fail
  if the fix is reverted.
- Use the attached form "--dbname=" (a single argv token) for pg_restore
  instead of ['--dbname', ''] so the process-details command renders
  correctly -- an empty standalone token was dropped from the display.
2026-09-14 17:33:20 +00:00
hiteshjambhale e3a10c53cb fix(restore, maintenance): pass database via PGDATABASE to close connection-string injection
The restore and maintenance tools passed the user-controlled database
value to --dbname. libpq expands a --dbname value containing "=" (e.g.
"host=evil port=5433 dbname=postgres") into a connection string, letting
a user redirect the connection -- and the exported PGPASSWORD credential
-- to an arbitrary server, regardless of the --host/--port pgAdmin sets.

Pass the target database via the PGDATABASE environment variable instead
(libpq treats it as a literal name and never expands it):
- maintenance (psql) and restore plain/SQL (psql): drop --dbname; the
  tool connects using PGDATABASE.
- restore custom/tar/directory (pg_restore): pg_restore requires
  -d/--dbname, so pass an empty --dbname '' (no "=", not expanded) while
  the real database name comes from PGDATABASE.
2026-09-14 17:33:20 +00:00
Ashesh Vashi cf22515efc Merge remote-tracking branch 'origin/master' into cve-9.18 2026-09-14 22:52:21 +05:30
Ashesh Vashi 81edb68b27 fix(deps): pin psycopg[c] back to 3.3.4 (#10399)
The macOS x64 packaging job fails while installing requirements.txt:

    Preparing metadata (pyproject.toml): finished with status 'error'
    error: subprocess-exited-with-error
    exit code: -10

Exit code -10 is SIGBUS. The only output before the crash is
setuptools' own `[tool.setuptools.ext-modules] in pyproject.toml is
still experimental` warning, so it dies while setuptools reads the
project config, well before pg_config is ever invoked.

psycopg-c ships no wheels, so every platform compiles it from the
sdist. The only build-tooling change between 3.3.4 and 3.3.5 is the
strictly pinned build requirement:

    3.3.0 - 3.3.4    setuptools == 80.3.1
    3.3.5            setuptools == 83.0.0

Because that pin is an equality, PIP_CONSTRAINT cannot override the
version inside the isolated build environment - the only ways to keep
3.3.5 are --no-build-isolation with a pre-seeded setuptools (which
would fork the macOS build procedure from Linux's) or an upstream
fix. Linux builds 3.3.5 fine, so this looks specific to the
relocatable-python framework the macOS bundle is built against.

Pin to 3.3.4 to unblock the build. The encoding.py rewrite that
shipped alongside the 3.3.5 bump stays untouched: it reads the
derived py_codecs/pg_codecs dicts, which are plain dicts in both
versions.

One test expectation was 3.3.5-specific. psycopg 3.3.5 added an
ISO88591 alias to its encoding table, so inverting py_codecs resolves
iso8859-1 to ISO88591; on 3.3.4 only LATIN1 exists, so
get_encoding('LATIN1') returns ['LATIN1', 'iso8859-1'] instead of
['ISO88591', 'iso8859-1']. Both are valid PostgreSQL names for the
same encoding, so the scenario now accepts either and the test passes
on 3.3.4 and 3.3.5 alike.
2026-09-14 22:46:45 +05:30
dev-hari-prasad fd9179f25a chore: configure 3-day release age gate for npm packages (#10390)
Set npmMinimalAgeGate to 3d in web/.yarnrc.yml and runtime/.yarnrc.yml to prevent immediately installing freshly-published packages, providing a grace period against npm supply-chain compromises.
2026-09-10 16:10:12 +01:00
dev-hari-prasad 6acbf875ae chore(security): restore checksumBehavior to throw in web and runtime (#10395)
Revert checksumBehavior from update back to Yarn's default (throw) in web/.yarnrc.yml and runtime/.yarnrc.yml to restore tamper detection and ensure builds fail if downloaded package checksums do not match lockfiles.
2026-09-10 16:10:05 +01:00
dev-hari-prasad 809e812a17 chore(security): disable install scripts by default and narrow approved git repos in /web (#10391)
Revert global enableScripts in web/.yarnrc.yml to Yarn's secure default (false) to prevent arbitrary lifecycle scripts from running on install. Allowlist only ttf2woff2 and unrs-resolver via dependenciesMeta.built in web/package.json, keeping promotional/unneeded scripts blocked. Narrow approvedGitRepositories to pgadmin-org/react-data-grid.
2026-09-10 16:09:54 +01:00
dev-hari-prasad d84d3ce9e3 chore(deps): pin and bump @fortawesome/fontawesome-free to 7.3.1 in /web (#10372)
Replace 'latest' with exact '7.3.1' in web/package.json and update web/yarn.lock.
2026-09-10 16:09:43 +01:00
Ashesh Vashi 155ebe5c28 docs: add assigned CVE numbers to the 9.18 release notes
Fill in the identifiers allocated by the PostgreSQL CNA for the three
security entries that carried a placeholder:

  #10383  webserver Remote-User auth bypass      CVE-2026-86863
  #10384  backup argument/conninfo injection     CVE-2026-86864
  #10385  File Manager save_file symlink follow  CVE-2026-86861

Add the missing entry for #10388, connection-string injection in the
Restore and Maintenance tools (CVE-2026-86862). The fix was written but
never got a release-note line.

Move the release date out to 2026-09-17.
2026-09-10 09:03:59 +05:30
Dave Page 153e3273d8 Fix inherited-column read-only state and Definition-tab type restriction in the Table dialog (#10332)
Backend properties fetch marks a column already inherited from a
parent table with inheritedfromtable, while a column fetched
interactively via 'Inherited from table(s)' carries inheritedfrom
instead and has no attnum yet. inSchemaWithColumnCheck only checked
inheritedfrom, and did so after an isNew() short-circuit that treated
the attnum-less interactive rows as new, so inherited columns ended up
editable and deletable in both cases. Check both fields, and check
them before the isNew() short-circuit, and extend
canEditDeleteRowColumns the same way so the row's edit/delete buttons
are disabled too.

Fix: restrict Definition-tab Data type options to edit_types (#10180)

The expanded row's Definition tab and the inline grid-cell editor each
defined their own copy of the edit_types filter for the 'cltype'
field, but the tab's version received the whole table's data as
'state' rather than the row, since MappedControl resolves a field's
'type' callback against the top-level schema, not the field's own row
(unlike 'cell', which already gets the full row). That made isNew()
and edit_types resolve against the wrong object, so the filter always
no-opped and the tab showed every type instead of the restricted set.

Have MappedControl also resolve a field's declared 'deps' against its
own row (listenDepChanges already does this correctly for 'cell') and
forward them as a 2nd argument to 'type', mirroring what 'cell'
already receives. column.ui.js declares 'edit_types'/'attnum' as deps
on 'cltype' and factors the filter into one shared editTypesFilter()
used by both 'cell' and 'type', so the two stay in sync by
construction.

Fx: honour inheritedfromtable in attlen/attprecision editable checks

The Length/Precision and Scale grid-cell editors only checked
inheritedfrom when deciding whether to allow editing, so a column
loaded with only inheritedfromtable set (i.e. already inherited when
the table was opened) could be left incorrectly editable, unlike the
other inherited-column checks in this schema. Add a shared
isInheritedColumn() helper covering both fields and reuse it in both
editable callbacks and in inSchemaWithColumnCheck.
2026-09-09 15:45:50 +05:30
Dave Page 6c70d7ccb8 fix(pkg): stop shipping the JavaScript build configuration (#10403)
The Windows installer and the pip wheel were both shipping web/yarn.lock,
web/package.json and the rest of the JavaScript build configuration into
the installed tree, even though none of it is used at runtime: only the
webpack output under pgadmin/static/js/generated is. Because node_modules
is stripped, the lockfile that remains describes packages that are not
present, so vulnerability scanners run against an installation report the
whole build-time dependency tree, devDependencies included, as findings
that cannot be acted on and will recur on every release no matter how
current the versions are.

The Linux, macOS and Docker builds already removed these files, so this
brings Windows and pip into line with them, and adds webpack.* to the
Linux and macOS lists so that all of the packagers now strip the same
set. For pip the cleanup runs before syft, so the SBOM describes what
actually ships.

Verified by assembling a web tree with the cleanup applied and starting
pgAdmin from it: /misc/ping and /browser/ both return 200 and every
generated bundle is served.
2026-09-09 14:37:38 +05:30
Dave Page 9d0a61a828 test: drop proc1 from the Schema Diff KNOWN_DIFFERENCES list
The whitespace-only difference that CREATE OR REPLACE left in procedure
bodies was fixed in abf926428, so proc1 now settles once the generated
script has been applied and the test fails on the stale entry, exactly
as it is designed to: the list fails when something on it starts working
so that it cannot quietly rot. Take the entry off.
2026-09-07 22:40:55 +01:00
dev-hari-prasad 50d76765a1 perf: defer the cloud SDK imports until a cloud wizard is used (#10362)
boto3, azure.*, googleapiclient and google_auth_oauthlib were imported at
the top of the AWS RDS, Azure and Google Cloud modules, so every pgAdmin
start paid for all three SDKs whether or not anyone went near the cloud
deployment wizards. Move those imports into the functions that need them,
leaving the blueprints, their menu entries and the exposed endpoint map
eagerly registered, which is the property that makes this viable where
the earlier lazy-blueprint idea was not; repeat calls cost nothing, since
sys.modules caches each module after the first import.

The modules with several call sites get a small _azure_sdk() or
_google_sdk() helper handing back the symbols in a namespace, whilst the
one and two site cases carry a plain inline import with a comment saying
why it sits there. The oauth2client sentinel stays at the top of the
Google module, where it still runs before anything can import
googleapiclient, and _google_sdk() now carries a note pointing back at it
so that a later tidy-up does not move it and quietly reintroduce #10110.

Deferring the imports moves the failure mode for a broken or missing SDK
from startup, where it is obvious, to the first click in a wizard, so
each affected path now fails cleanly rather than leaking a traceback into
the UI: the Google and Azure lookups return their usual (data, error)
tuple, or an empty list with the reason logged; Google.callback() reports
a missing google_auth_oauthlib as a verification error; and the RDS
client helper logs the ImportError and lets its callers answer with empty
metadata instead of a 500.

Also fixes a pre-existing bug in the Google instance_types endpoint,
which assigned the (instance_types, error) tuple returned by
Google.get_instance_types() to a single name and then called .get() on
it, raising AttributeError for anyone who got that far in the wizard.

Measured on Linux with a warm page cache, timing create_app() in desktop
mode over five runs and taking the median, this takes application startup
from around 1567 ms to around 1354 ms; on Windows the saving was reported
as larger, at around 2131 ms down to around 1515 ms.

Partially addresses #10221.
2026-09-07 15:59:36 +01:00
Ashesh VashiandDave Page 0b8e8db0cc fix(pkg): stop the Windows build overwriting the venv's pip (#10396)
* fix(pkg): stop the Windows build overwriting the venv's pip

CREATE_VIRTUAL_ENV creates a virtualenv and then copies the whole of the
system Python's Lib over it, so that the relocated interpreter has a
standard library to use. That copy includes Lib\site-packages, and XCOPY
/Y overwrites the files that exist on both sides without removing the
ones that only exist in the venv, so the venv's freshly seeded pip ends
up spliced together with whatever pip C:\Python313 happens to have.

That was harmless while the two layouts agreed. pip 26.2 turned
pip/_internal/build_env from a module into a package, so on a builder
whose system pip predates 26.2 the new build_env/ directory survives the
copy whilst utils/misc.py is overwritten by the older one, and every
subsequent pip invocation dies before it does anything:

  File "...\pip\_internal\build_env\installer.py", line 22, in <module>
    from pip._internal.utils.misc import get_runnable_pip
  ImportError: cannot import name 'get_runnable_pip' from
  'pip._internal.utils.misc'

It cannot recover on its own either, since the next line of the script
is the pip invocation that upgrades pip.

Copy the standard library with ROBOCOPY and exclude site-packages, which
the venv is entitled to keep to itself. ROBOCOPY rather than XCOPY
because /XD takes a directory to skip and the existing
:CHECK_ROBOCOPY_ERROR helper already handles its exit codes; /R:3 /W:5
so a locked file fails the build rather than retrying for a million
attempts.

* fix(pkg): make the Windows build's ROBOCOPY failures diagnosable

Follow-up review fixes to the site-packages exclusion.

Exclude site-packages by name rather than by full path. The full path is
built from PGADMIN_PYTHON_DIR, so if that is ever set with a trailing
backslash the source and the /XD argument both gain a doubled separator;
should the match then fail, site-packages is copied over the venv again
and the build fails with the same ImportError this was meant to remove.
No directory inside the standard library is legitimately called
site-packages, so matching by name is both simpler and unconditional.

Drop "> nul" from both ROBOCOPY calls in favour of /NFL /NDL /NP.
ROBOCOPY writes its errors to stdout, so with the redirect in place a
copy that returns 8 or more aborted the build with nothing in the log to
say which file or share was at fault; the switches suppress the per-file
noise whilst keeping the header, the summary and any errors.

Guard the copy of the web directory with "|| EXIT /B 1" as well, since
EXIT /B inside :CHECK_ROBOCOPY_ERROR returns from the label rather than
from the caller and a failed copy was therefore ignored, leaving the
build to package an incomplete tree.

Guard virtualenv.exe likewise: with the standard library now copied by
ROBOCOPY, which creates its destination, a failure there would otherwise
surface later at the activate step instead of where it happened.

---------

Co-authored-by: Dave Page <dpage@pgadmin.org>
2026-09-07 15:02:06 +01:00
Ashesh Vashi 0e9c3eab8f Add the master changes merged since the 9.18 notes were written
Covers the ten commits merged from master: the CSP per-request nonce,
the Object Explorer keyboard shortcut (folded into the existing #9631
entry, since it is the same feature reached another way), the Schema
Diff dependency ordering, function-body whitespace, foreign table and
sequence fixes surfaced by the now-asserting regression test, the
server-cursor prepare TypeError, the partitioned index TABLESPACE
clause, the UDF argument grid, the Helm existingSecret path and the
Validate binary path dialog.

Entries within each section are now ordered by issue number, with the
SHA-keyed ones last.
2026-09-07 16:50:01 +05:30
Ashesh Vashi 7edb9c012c Merge remote-tracking branch 'origin/master' into cve-9.18 2026-09-07 16:47:57 +05:30
Kundan 7c2177241f fix: render Validate binary path modal as HTML instead of raw markup (#10394)
The endpoint returned a pre-built <b>/<br/> HTML string, and the frontend
switched to the escaping alertText() during the XSS hardening pass
(9e370d3cb), so the modal showed literal tags instead of formatted text.
Return structured {utility, version} data from the backend and compose
the modal content as React elements on the frontend, keeping the
untrusted binary version output routed through SafeMessage.

- Fall back to the not-found message on empty version output too

|| instead of ?? — an empty-string version from an unexpected
'--version' output shape should still show the fallback message.
2026-09-07 16:46:41 +05:30
hiteshjambhale fb0ca5c4c8 feat: Use per-request nonce in CSP to remove 'unsafe-inline' / 'unsafe-eval' (#10308)
Harden the default Content-Security-Policy so inline scripts run only via a
per-request nonce instead of a blanket 'unsafe-inline', and drop 'unsafe-eval'.

- Generate a per-request nonce (secrets.token_urlsafe) cached on flask.g so the
  exact same value is emitted in templates and the CSP response header.
- Substitute a {nonce} placeholder in CONTENT_SECURITY_POLICY at runtime.
- Tag inline <script>/<style> tags, and set window.__webpack_nonce__ so webpack's
  dynamically injected assets carry the nonce too.
- New default: script-src 'self' 'nonce-{nonce}' (no 'unsafe-inline'/'unsafe-eval').
- style-src keeps 'unsafe-inline': MUI/React inject un-nonced runtime styles and
  inline style="" attributes that cannot be nonced.
- 'unsafe-eval' is not needed by production bundles; the dev ('eval' devtool)
  bundles add it via config_local.py (documented in config.py).

Setting window.__webpack_nonce__ in HTML is a no-op: webpack only substitutes the __webpack_nonce__ identifier inside compiled modules, and MUI/emotion does not read it either (it requires an explicit createCache nonce). Same-origin webpack chunks are already allowed by script-src 'self', so the block did nothing. Remove it; the resourceBasePath inline script keeps its nonce.

Development bundles are built with webpack's 'eval' devtool, which the strict nonce policy blocks, forcing developers to manually add 'unsafe-eval' via config_local.py. Handle it automatically in get_content_security_policy() (which runs per request and therefore sees a DEBUG value overridden in config_local): when config.DEBUG is set and the policy uses a nonce, append 'unsafe-eval' to the script-src directive, without duplicating it. Production is unaffected and custom (non-nonce) policies pass through untouched.

Adds unit tests covering nonce generation/caching/per-request freshness, {nonce} substitution, pass-through of custom/None/empty policies, header emission, and the dev-mode behaviour (including the no-script-src edge case, exact script-src name matching, and no-duplication).

The dev-mode 'unsafe-eval' behaviour is now implemented, so these are ordinary regression tests. Remove the 'PLANNED'/'EXPECTED TO FAIL until impl' banners (which incorrectly told maintainers not to make the source pass) and an unused MagicMock import.
2026-09-07 16:44:38 +05:30
Dave Page d0210a534d fix: correct existingSecret path in Helm deployment template (#10214) (#10313)
The annotation conditions referenced the non-existent top-level
.Values.existingSecret instead of .Values.auth.existingSecret, so the
secret checksum annotation and the empty-secret gating never worked as
intended when an existing secret wasn't supplied.
2026-09-07 16:42:50 +05:30
Dave Page 006fc6bd88 fix: omit redundant TABLESPACE pg_default from generated index SQL (#10342)
PostgreSQL rejects an explicit TABLESPACE clause naming the database's
own default tablespace when the index is on a partitioned table
('cannot specify default tablespace for partitioned relations'), so
the SQL pgAdmin generated for a new index could not be executed
against a partitioned table whenever no non-default tablespace was
chosen. The index_constraint and exclusion_constraint create.sql
templates already guard against emitting this redundant clause;
extend the same guard to the plain index create.sql templates.

Closes #10341
2026-09-07 16:42:15 +05:30
Dave Page abf926428e fix: stop Schema Diff injecting whitespace into applied function/procedure bodies (#10302) (#10317)
Generating a script for a function or procedure that only exists on the
source ("Source Only") routed through FunctionView.sql(), which always
calls reformat_prosrc_code() to wrap the body with a leading and trailing
newline around $BODY$...$BODY$ purely for readability in the SQL panel.
That reformatted body was then executed verbatim against the target, so
PostgreSQL stored the extra newlines in prosrc: the target's body no
longer matched the source's byte for byte, and re-comparing kept
reporting a whitespace-only difference unless "Ignore whitespace" was
ticked.

Schema Diff's update path (CREATE OR REPLACE against an existing target
object) already suppressed this formatting via allow_code_formatting,
added for #7102; this extends the same guard to the create/"Source
Only" path so Schema Diff never adds whitespace of its own to the SQL
it actually applies.

Added a dedicated regression test covering both the "Source Only" and
"Different" cases for a function and a procedure: applying the
generated script now leaves every object comparing as Identical
without needing "Ignore whitespace".
2026-09-07 16:41:35 +05:30
Dave Page f2fdef5ba5 Schema Diff: reinstate dependency ordering of the generated script (#10319)
dependLevel drove the order in which generateFinalScript() writes
objects, with higher levels emitted first so dependencies land before
whatever needs them, but nothing has assigned it since the React port
deleted schema_diff_dependency.js: every row got Object.keys' default
undefined bucket, so the script came out in whatever order the rows
happened to be in.

computeDependLevels() rebuilds the levels for the full result set from
each row's dependencies, walking the reverse graph so a row that other
rows depend on always ends up with the highest of their levels plus
one, with a cycle guard for the circular dependencies the script header
already warns about.

Fixes #10295.
2026-09-07 16:40:27 +05:30
Dave Page 972332ae59 feat: add a keyboard shortcut for collapsing the Object Explorer (#10296)
#10254 made the Object Explorer collapsible by clicking the current
workspace icon, but left that as the only way to do it. This adds a
shortcut, Ctrl+Alt+B by default, registered as its own
toggle_object_explorer preference so anyone can change it.

Ctrl+Alt+B rather than anything in the Shift+Alt family, which is full: b,
f, o, l, h, q, v, s, n, e, d and g are all taken, b by the existing Object
Explorer shortcut, which keeps its meaning of focusing the tree. Ctrl+Alt+B
is close enough to VS Code's Ctrl+B for the side bar to be guessable, and
keeps a family resemblance to the bracket keys used for tab navigation.
Ctrl+Shift+B would have been the other obvious candidate, but Chrome and
Firefox both use it for the bookmarks bar.

Worth noting for anyone reviewing: on keyboard layouts where AltGr is
Ctrl+Alt, this combination can be swallowed by the layout and produce a
character instead. That is true of any Ctrl+Alt binding, the preference is
user-configurable, and Shift+Ctrl+Alt+B is available if the default turns
out to annoy people.

The shortcut itself only fires an event, since the workspace provider owns
the visibility state, and the provider ignores it in classic layout where
there is no workspace toolbar and the Object Explorer is always shown.

Tested in a browser as well as in the unit tests: from the workspace layout
the key collapses and restores the panel, and the preference is registered
alongside the existing shortcuts.
2026-09-07 16:39:33 +05:30
Dave Page bc04db39d5 fix: stop AsyncDictServerCursor forwarding prepare to the server cursor (#10343)
psycopg's AsyncServerCursor.execute() has never accepted a prepare kwarg
(a server-side DECLARE CURSOR can't be a prepared statement) and raises
TypeError on any unexpected keyword, even one whose value is None.

PR #10030 widened AsyncDictCursor.execute()/_execute() to forward
prepare/binary to whatever cursor it holds, fixing a real
psycopg_pool.ConnectionPool.check_connection breakage. AsyncDictServerCursor
inherits that same execute()/_execute() without overriding it, so every
server-cursor query now unconditionally forwards prepare=None straight into
AsyncServerCursor.execute() and fails with
"TypeError: keyword not supported: prepare".

That TypeError isn't a psycopg.Error, so execute_async()'s
"except psycopg.Error" doesn't catch it. It propagates into the background
QueryThread's generic exception handler, which logs it and builds an
internal_server_error response that is discarded (the thread's return value
goes nowhere) - so the query silently never runs, invisible to the user,
and the async cursor is left with no query having actually executed on it.

Give AsyncDictServerCursor its own _execute() that drops prepare before
delegating, leaving DictCursor/AsyncDictCursor's forwarding for the
pool-checkout case it was written for untouched.

Strengthen test_server_cursor.py's existing scenario to assert the poll
response's actual status/result instead of only the HTTP status code and
the echoed server_cursor flag, and add a fast, DB-less regression test
asserting AsyncDictServerCursor._execute never forwards prepare.
2026-09-07 16:38:46 +05:30
Dave Page 3d310d4656 Fix UDF/procedure argument grid delete (and add) in edit mode (#10333)
`canDeleteRow for the function/procedure Arguments grid checked whether the
whole function was new rather than whether the individual row was new,
so once a function was saved the delete icon was disabled for every
argument row, including ones added but not yet saved (same bug pattern
already fixed for enum values in #8208). canAdd had the same whole-object
gate, hiding the "+" button entirely once a function was saved, so there
was no way to add a row in the first place.

Pre-existing (already persisted) arguments remain non-deletable, since
PostgreSQL has no way to remove an argument from a function via
CREATE OR REPLACE.

Also fixes _update_arguments_for_get_sql, which only merged the
'changed' key of the arguments diff and silently dropped (or, without
a 'changed' key at all, raised a 500) any newly added argument, so a
row added via the now-enabled "+" button actually survives into the
generated SQL.

Closes #10252
2026-09-07 16:37:12 +05:30
Dave Page 8c6fd211a7 Schema Diff: make the regression test assert its generated script, and fix what that found (#10305)
The Schema Diff comparison test wrapped applying its generated script, and
the comparison that follows it, in a bare `except Exception` that discarded
both. It reported a pass whatever the script did, which is why it printed
`syntax error at or near ")"` on every run whilst claiming two tests passed.
It now fails when an object's SQL does not apply, and when applying the lot
leaves the two databases different, with a short list of the differences
that are known not to settle yet so that the list cannot quietly rot.

Objects are applied one at a time and retried rather than as a single
script, because the script is no longer ordered by dependency (#10295), so
an object can fail purely because something it needs comes later on;
retrying tells that apart from SQL that is simply wrong.

Turning the assertions on found the following, each of which is fixed here:

* A range type being dropped and recreated because its kind changed lost
  its subtype, because directory_diff() drops a plain value that only one
  side of the comparison has, and rendered `CREATE TYPE ... AS RANGE ()`.
  Once that was fixed it wrote the catalogue's `-` placeholder out as
  `CANONICAL = -`, which the reverse-engineered SQL path already avoids.
  Both are now handled where the comparison data is built (#10304).

* The constructor functions, casts and multirange types that PostgreSQL
  creates for a range type were compared as though a user had written them,
  so the script tried to recreate objects that come into being with their
  parent type: 47 of 151 objects in the test's fixtures were these.
  Internal dependencies are now excluded alongside extension ones, matching
  what pg_dump does.

* Recreating a foreign table declared any column that also differed twice,
  because a changed column was appended to the table's existing columns
  rather than replacing the entry already there (#10297).

* Raising a sequence's MINVALUE above the value it currently sits at, or
  lowering MAXVALUE below it, generated a statement PostgreSQL rejects
  outright, taking every other change to that sequence with it. Such a
  change is now accompanied by the RESTART it requires (#10298).

* A foreign table column added by Schema Diff lost its collation, because
  get_columns.sql calls it collname whilst the column templates render
  collspcname (#10300).

* A comparison that threw part way through emitted its failure and then
  reported success as well, handing the client a fraction of the databases
  as though it were a complete result (#10303).

Two differences remain listed as known: a rebuilt partitioned table keeps
the default partition used as scaffolding for the data copy (#10301), and
CREATE OR REPLACE wraps a function body in newlines, leaving a
whitespace-only difference (#10302).

Fixes #10293

* Stop the partition rebuild script copying from a temporary table

Asserting that the generated script actually applies turned up a
partitioned table rebuild whose script refers to a relation that has
never existed, along the lines of:

    CREATE TABLE schema.temp_partitioned_2 (
        LIKE schema.temp_partitioned_1 INCLUDING ALL
    ) PARTITION BY RANGE (col1);

so that applying it fails with 'relation "schema.temp_partitioned_1"
does not exist'.

There are two things going on here, and both are fixed.

PgAdminModule.register() is called once per application instance, whilst
the blueprint objects themselves are module level singletons, so each
sub-class that appends its sub-modules from its own register() (most of
them do, TableModule included) leaves a duplicate entry behind every
time a second application is created in the same process. Nothing in
production notices, because production creates a single app, but the
regression suite creates several, and anything walking self.submodules
then does its work once per duplicate: with four copies of the partition
sub-module, get_sql_from_submodule_diff generated the same partition
rebuild four times over. self.submodules is now de-duplicated as the
blueprint registers, and parentmodules likewise only gains an entry it
does not already hold.

The second call was only harmful because PartitionsView.get_sql_from_diff
stashed its temporary names on the caller's own dictionaries, replacing
the table's real name with a temporary one, so a subsequent call read the
first call's temporary name back as the original. It now works on copies
and leaves the comparison data it is handed alone, which makes it safe to
call more than once regardless of how it is reached.

* Take a mutable copy of request.form, and close cursors on a failed chunk

Two things picked up in review.

Sequence update() passed request.form straight through to get_SQL(), and
request.form is immutable, so as soon as _add_restart_for_new_bounds had
a restart to add (which is the whole point of it) a form encoded update
raised TypeError. It now takes a dict copy, as the msql path already
effectively did.

apply_sql_chunks() left the cursor open when a statement failed, and a
failed statement is retried on the next pass, so a run could accumulate
one open cursor per attempt. The cursor is now closed in a finally,
whether the statement worked or not.
2026-09-07 16:35:13 +05:30
Ashesh Vashi de71c83d8d Bump version to 9.18 and add the 9.18 release notes
Update APP_REVISION/APP_VERSION_INT in web/version.py and the Helm
chart's version/appVersion, and add docs/en_US/release_notes_9_18.rst
with a toctree entry.

The notes cover everything merged since REL-9_17 (ee7109d40..bd7cde585)
plus the three CVE fixes on this branch: the Webserver auth header
bypass (#10383), Backup argument and connection-string injection
(#10384) and the File Manager save_file symlink TOCTOU (#10385). The
CVE identifiers are still placeholders (CVE-2026-XXXXX) pending
assignment, and #10388 has no entry because its fix is not on this
branch yet.

Changes with no associated issue are listed under Housekeeping and Bug
fixes keyed by commit SHA, following the 9.15 convention, so the
"Additional changes" wrapper is dropped and Dependencies becomes a
top-level section.
2026-09-07 15:02:01 +05:30
Kundan SableandClaude Sonnet 5 8ed0ecc06b webserver auth: address round-3 review follow-ups
Two non-blocking items from asheshv/pgAdmin4-cve#66's approval review:

- The auth_source lockout remedy was circular for the deployments most
  likely to hit it: telling a locked-out admin to "sign in through
  another authentication source" doesn't help when
  AUTHENTICATION_SOURCES = ['webserver'] and there is no other source.
  Documented the actual way out - temporarily add 'internal' back,
  fix the affected users, remove it again.
- A malformed WEBSERVER_REMOTE_USER (not a non-empty string) was
  demoted to info logging along with the client-triggered rejections
  in the previous round, but it's a static operator misconfiguration,
  not something a client varies per request - at info it would go
  unnoticed while every header-based login silently fails. Moved back
  to warning, consistent with the other config-error checks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 19:06:51 +00:00
Kundan SableandClaude Sonnet 5 c0bd0d8665 webserver auth: fix CI-breaking test isolation bug, address round-2 review items
Round 2 of asheshv/pgAdmin4-cve#66's review:

- test_webserver_remote_user_trust.py bound WebserverAuthentication at
  module-import time. test_auth_gating.py (which sorts and runs first)
  evicts pgadmin.authenticate.webserver from sys.modules to observe its
  re-import and never restores it, so the trust test held a discarded
  copy of the module while patch('...webserver.User') patched the fresh
  one - the patch silently missed and hit a real, unconfigured database.
  Reproduced the reviewer's "27 passed, 3 failed" package-level run, then
  fixed it by importing WebserverAuthentication inside each runTest()
  instead, matching this repo's existing deferred-import convention.
  Full authenticate package now passes 30/30.
- Documented the auth_source lockout: login() refusing any non-webserver
  auth_source protects existing accounts, but a site with pre-existing
  internal-auth users (including the default admin) will find nobody can
  log in after enabling this. Added a warning to webserver.rst with the
  fix (change Authentication source in User Management).
- login()'s user-is-None branch called logger.exception() with no active
  exception, writing a useless NoneType traceback and never naming the
  failed username. Now a plain warning naming it.
- Demoted per-request rejection logging (untrusted peer, secret mismatch,
  misconfigured name) from warning to info, since an unauthenticated
  client can trigger any of them on every request; kept genuine config
  errors (a malformed WEBSERVER_TRUSTED_PROXIES entry, or the whole
  setting not being a list/tuple) at warning.
- Guarded against WEBSERVER_TRUSTED_PROXIES being set to a plain string,
  which iterated character-by-character and logged one warning per
  character while still safely rejecting the request.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 19:06:51 +00:00
Kundan SableandClaude Sonnet 5 90e94da9db webserver auth: address CVE fix review comments
Fixes four issues from asheshv/pgAdmin4-cve#66's review:

- HTTP_-prefixed WEBSERVER_REMOTE_USER names (e.g.
  HTTP_X_FORWARDED_USER) were permanently unresolvable via the header
  path: Werkzeug normalizes '_' to '-' in header lookups but does not
  strip the HTTP_ prefix. Normalize the name to its header form before
  looking it up.
- A non-str/bytes or non-ASCII WEBSERVER_SHARED_SECRET made
  hmac.compare_digest raise, turning every login attempt into an
  unhandled 500. Encode both operands and catch the failure, degrading
  to a rejected match with a logged warning instead.
- Added a test exercising the real ProxyFix/X-Forwarded-For bypass via
  app.test_client(), which the existing scenarios (bare
  test_request_context, no ProxyFix in the stack) could not catch.
- Added positive-path tests for login() (a genuine WEBSERVER user still
  logs in; a header-asserted name with no matching User row fails
  gracefully) plus a few low-priority coverage gaps the review flagged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 19:06:51 +00:00
Kundan SableandClaude Sonnet 5 02e327c1b9 Fix authentication bypass in Webserver auth via client-controlled header
WebserverAuthentication.get_user() fell back to request.headers when the
WSGI environ lookup missed, letting any client set Remote-User (or an
equivalent HTTP_-prefixed/hyphenated name) and log in as an arbitrary
existing user, including an Administrator, with no credential check.

Gate the header fallback behind an explicit opt-in
(WEBSERVER_REMOTE_USER_FROM_HEADER), a trusted-proxy IP/CIDR allow-list
(WEBSERVER_TRUSTED_PROXIES, checked against the raw pre-ProxyFix peer so
X-Forwarded-For can't spoof it), and an optional shared secret
(WEBSERVER_SHARED_SECRET). Also refuse to log a header-asserted identity
into any account whose auth_source isn't webserver, so a forged header
can never take over an internal-auth admin even if the gate above is
misconfigured.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 19:06:51 +00:00
hiteshjambhale 5082834a27 fix(backup): pass database via PGDATABASE env to close connection-string injection
The "--" end-of-options marker (previous commit) stops pg_dump from
reading the database value as an option, but libpq still expands a dbname
containing "=" (e.g. "host=evil port=5433 dbname=postgres") into a
connection string -- letting a user redirect the connection, and the
exported PGPASSWORD credential, to an arbitrary server.

Pass the target database via the PGDATABASE environment variable instead
of as a command-line argument (as import_export already does). libpq
treats PGDATABASE as a literal database name and never expands it, so
both the file-write and connection-redirect vectors are closed. Also
fixes a latent bug where a database name legitimately containing "=" was
broken.

Update the regression tests: assert the database name is absent from
argv and carried in PGDATABASE (order-blind assertIn is insufficient).
2026-09-04 13:57:13 +00:00
hiteshjambhale a14469290e test(backup): add regression tests for database-field argument injection
Adds two scenarios to the object-backup unit test:
- normal dbname lands as the trailing positional after '--'
- a malicious '--file=...' value stays a trailing positional after '--'
  and never overrides the storage-confined '--file'

Uses an order-sensitive assertion on the last args (the existing assertIn
harness is order-blind and would pass even if '--' came after the dbname).
2026-09-04 13:57:13 +00:00
hiteshjambhale 82a88b74f9 fix(backup): prevent argument injection via database field
The object-backup path appended the request 'database' field to the
pg_dump argv as a raw positional argument. Under getopt_long argument
permutation, a value like --file=/absolute/path is parsed as an option
and overrides the storage-confined --file, letting a low-privilege user
write pg_dump output outside their storage sandbox.

Insert the '--' end-of-options marker before the database name so
pg_dump always treats it strictly as the positional dbname.
2026-09-04 13:57:13 +00:00
Ashesh Vashi d773db699f test: make save_file symlink test harness-native, add positive control
Follow-up to the previous commit; the fix itself is unchanged.

The new test called create_app() inside runTest(). That is redundant --
runtests.py already calls setApp()/setTestClient() on every test object,
independent of setUp() -- and expensive: it did not complete in over two
minutes locally, while the two existing tests in this file run in 1.1s
total. It also routes through create_app_data_directory(), which calls
sys.exit(1) on any data-directory problem, surfacing as SystemExit
mid-test. Use the harness's already-authenticated test client instead,
which also removes the save_file.__wrapped__() unwrapping; that worked
only because pga_login_required applies @wraps outermost over
@mfa_required, so reordering those two lines would have silently pointed
__wrapped__ at the MFA wrapper.

The old test also stubbed check_access_permission to a no-op, which
tests the write guard with the containment check absent rather than the
actual threat. Let the real check run and plant the symlink from inside
it, which reproduces the check-to-open race deterministically.

Assert on the error message rather than a bare status code. 500 is also
returned by the check_access_permission branch, and "outside file
unchanged" holds whenever any error occurs, so the previous assertions
would pass even if the write never happened for an unrelated reason.

Add a positive control. Nothing asserted that an ordinary save still
succeeds, so a broken _open_upload_target() would have left the negative
test green while every Query Tool and ERD save failed. It also pins the
0o600 mode so that documented behavioral change cannot regress silently.

Report the leaf-symlink rejection specifically instead of leaking the
raw strerror. A user whose save is refused saw "Error: Too many levels
of symbolic links", which explains nothing. Note this is a real
behavioral change: a symlink inside the storage root pointing at a
target inside the storage root passed check_access_permission and used
to work, and is now refused -- the same tradeoff already accepted for
uploads.

Verified with runtests.py against PostgreSQL 18.6 in desktop mode:

  --pkg misc.file_manager            19/19 pass
  --exclude feature_tests            2037 pass, 491 skipped, 1 failure

The single failure is utils.driver.psycopg3 test_encoding (LATIN1 maps
to the iso8859-1 python codec), which reproduces identically on the
unmodified base commit bd7cde585 and is unrelated to this branch.

Both new tests were confirmed non-vacuous by reverting the
_open_upload_target() call to the bare open(): TestSaveFileRejectsLeafSymlink
then fails with "AssertionError: 200 != 500" (the symlink is followed and
the outside target is overwritten) and TestSaveFileWritesRegularFile with
"AssertionError: 420 != 384" (0o644 vs 0o600).
2026-09-04 05:55:58 +00:00
Dave Page 83a31a350c fix: close save_file() leaf-symlink TOCTOU in File Manager
check_access_permission() resolves symlinks via realpath() and validates
containment (CVE-2026-7813), but save_file() re-joins the path afterwards
and wrote through a bare open(), which follows a symlink planted at the
leaf between the check and the write. The upload endpoint already closed
this exact gap with _open_upload_target()'s O_NOFOLLOW-protected open;
save_file() never got the same treatment.

Route save_file()'s write through _open_upload_target() too, so both
File Manager write sinks share the same kernel-enforced protection.
Reported privately per SECURITY.md.
2026-09-04 05:55:58 +00:00
Ashesh Vashi bd7cde5851 chore(deps): bump third-party JS and Python dependencies (#10370)
JS (yarn, patch/minor only): babel toolchain, MUI, react/react-dom,
testing-library, axios, webpack ecosystem, jest, eslint plugins, and
misc small libs. Excluded @simonwep/pickr (webpack prod-bundle
interop break on 1.10.x), @testing-library/user-event@14.6.7 and
webpack@5.110.3 (both npm-quarantined at bump time).

Python (>3.9 requirements branch only, Python 3.9 pins untouched):
Authlib 1.8, google-auth-oauthlib 1.4.1, gssapi 1.12 (ungated),
psycopg[c] 3.3.5, selenium 4.48.0, testscenarios 0.7.0.

Skipped: paramiko 3->5 (blocked on sshtunnel's paramiko.DSSKey
usage), azure-mgmt-resource 26.0.0 (drops Python 3.9 support and
conflicts with the <26 upper bound kept for Azure CLI distro
packaging, see #10247).

Two bumped packages required source fixes:

- psycopg 3.3.5 restructured the private
  psycopg._encodings._py_codecs attribute from a dict to a tuple of
  alias groups, breaking our monkeypatch in
  pgadmin/utils/driver/psycopg3/encoding.py with TypeError: 'tuple'
  object does not support item assignment. Rewrote get_encoding()/
  configure_driver_encodings() to use the derived py_codecs/
  pg_codecs dicts instead, which stay plain dicts across psycopg
  3.2.x-3.3.5 regardless of _py_codecs's internal shape. Verified
  byte-for-byte identical behavior against 3.3.4 for every
  encode_dict entry (SQL_ASCII, EUC_TW, UNICODE, ascii) plus
  UTF8/LATIN1. Added a DB-free unit test
  (pgadmin/utils/driver/psycopg3/tests/test_encoding.py) so a future
  psycopg internals change fails fast here instead of only surfacing
  as an app-wide import crash in the full regression suite.

- react-frame-component 5.3.2 breaks LayoutIframeTab.jsx (the iframe
  wrapper every tool panel - Query Tool, Debugger, PSQL, ERD, Schema
  Diff - renders into): the panel's iframe never mounts, hanging any
  of those tools indefinitely (React error #130 in the production
  bundle). This caused every run-feature-tests-pg CI job to fail.
  Reverted to 5.2.6.
2026-09-03 14:58:04 +05:30
Dave Page bc58657d3d ci: stop fetching an unpinned Yarn before the pinned one (#10306)
Everywhere we build the JS, we asked Yarn to install itself twice:

    yarn set version berry
    yarn set version <the version from packageManager>

The first call fetches whichever release is "berry" today and the second
immediately replaces it, so every build made two downloads to end up with one
Yarn, and the one that could not be reproduced tomorrow was fetched first.

That first fetch broke CI outright on 17 August 2026, during a GitHub
incident, when it returned an empty body and Yarn 1 died writing it out:

    error TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be of
    type string or an instance of Buffer, TypedArray, or DataView
        at writeFile (node:fs:2373:5)

The unpinned call is dropped everywhere it appeared: Make.bat, the macOS and
pip package builds, and the four workflows. The packaging scripts keep their
pinned "yarn set version ${YARN_VERSION}", which still reads packageManager
from package.json, so their behaviour is unchanged beyond losing a download.

The workflows go further and use Corepack, which reads packageManager itself
and provisions exactly that Yarn without writing .yarnrc.yml and
.yarn/releases into the repository root, where the JS project is not. The
Dockerfile already installed and enabled Corepack immediately before both
"set version" calls, so both were redundant there and are simply removed.

CONTRIBUTING.md and README.md already tell contributors to start with
"corepack enable", so this brings the automation in line with the documented
setup. Note that Corepack ships with Node.js only up to (but not including)
25.0.0, so it will need installing explicitly once the runners move past it,
as the Dockerfile already does.
2026-08-21 18:39:32 +05:30
Dave Page 5bd4f3bde6 Javascript dependency: Bump use-resize-observer from 9.1.0 to 10.0.0 (#10265) (#10299)
v10 removes the default export in favour of a named one, so the import in
PgTreeView had to change with it; left as it was, useResizeObserver resolves
to undefined and the Object Explorer dies when the hook is called. The rest of
the v10 changes cost us nothing: it drops the /polyfilled entrypoint and its
@juggle/resize-observer dependency, no longer takes react-dom as a peer, and
requires React >= 18.2, which is comfortably met on React 19. The hook still
returns the { ref, width, height } shape the tree destructures.

Verified by hand in Chrome and Safari with the tree expanded through
databases, schemas, tables and tablespaces, resizing the panel and the window.
2026-08-21 18:37:43 +05:30
Dave Page 9a009fd2b6 Don't follow HTTP redirects on LLM API requests (#10281)
* Don't follow HTTP redirects on LLM API requests

ALLOWED_LLM_API_URLS is checked against the URL pgAdmin is configured
with, but the providers then issued the request through
urllib.request.urlopen(), whose default handler would follow a Location
header on to a destination that check was never applied to.

No legitimate LLM API replies with a redirect, so refuse them outright
rather than re-validating each hop: urlopen_no_redirect() installs a
handler that raises HTTPError instead, and the outbound calls in all
four providers and in the model-refresh endpoints now go through it.

This is hardening rather than a fix for an exploitable flaw, since
returning the redirect in the first place requires control of a host
that is already on the allowlist. Reported by Ziya Abdullayev.

* Handle 308 redirects on Python 3.10 as well

HTTPRedirectHandler only gained http_error_308() in Python 3.11, so on
the 3.9 and 3.10 interpreters we still support (and which CI runs) a 308
was never recognised as a redirect: it bypassed _NoRedirectHandler's
redirect_request() entirely and surfaced from http_error_default() as a
bare 'HTTP Error 308: Permanent Redirect'. The redirect was still not
followed, so this was never a security gap, but the caller got no
explanation of why the request had failed, and the test asserting our
message failed on every CI job as a result.

Mapping http_error_308 onto http_error_301, exactly as 3.11 itself does,
routes 308 back through redirect_request() so that every redirect status
behaves identically on every version we support.
2026-08-21 18:37:15 +05:30
Dave Page 1f02466324 fix: reject empty or null Username on non-shared server import (#10309) (#10320)
validate_json_data() only checked that the Username key was present on
a non-shared server, not that it held anything useful, so an empty or
null value imported cleanly and left behind a server that libpq would
silently authenticate as the OS account running pgAdmin rather than
reject outright. Check the value, matching the truthiness check already
used for shared servers and the "Username must be specified" rule
enforced by the server dialog.
2026-08-21 18:35:35 +05:30
Dave Page c0fa2a18d8 Relax azure-mgmt-resource pin to allow 24.0.0 (#10323)
The exact ==25.0.0 pin was an incidental side effect of a mass
dependency bump and isn't required by pgAdmin's own code: the cloud
wizard only uses the top-level ResourceManagementClient re-export and
resource_groups.list(), both unchanged between 24.0.0 and 25.0.0.
Pinning to >=24.0.0,<26.0.0 lets distro packagers keep pgAdmin
installable alongside Azure CLI, which still requires
azure-mgmt-resource<25.

Closes #10247
2026-08-21 18:34:55 +05:30
Dave Page 46c092cc5d Remove redundant passlib pin that conflicts with libpass (#10325)
Flask-Security-Too >= 5.7 depends on libpass, a fork of passlib that
installs its files under the same passlib/ import path. Our own
unconditional passlib==1.* pin in requirements.txt therefore causes
both packages to be installed into the same site-packages/passlib/
directory, and parallel installers such as uv can end up with a
mixed or overwritten set of files depending on install order.

pgAdmin's own code never imports passlib directly, and the older
Flask-Security-Too 5.6.x branch (used on Python 3.9) already pulls in
real passlib as its own transitive dependency, so dropping our direct
pin does not change what gets installed there. On newer Python
versions, only libpass is installed, resolving the conflict.

Closes #10239
2026-08-21 18:34:06 +05:30
Dave Page f739036066 fix: guard PasswordExec against None host/port/username (#10326)
Service-only connections (pg_service.conf) leave host, port and username
unset, so a per-server Password Exec Command crashed in get() with
TypeError: replace() argument 2 must be str, not None. Fall back to an
empty string for each unset placeholder instead.

Closes #10237
2026-08-21 18:33:16 +05:30
Thomas Han 0ebefaf80e fix: accept SharedUsername when importing shared servers (#9971)
Importing a servers.json definition insisted on a Username attribute for
every server, so a shared server that carried only SharedUsername was
rejected with "'Username' attribute not found for server ...", even though
the dialog treats SharedUsername as the default username for a shared
server. Validation now accepts either attribute when the server is shared,
whilst non-shared servers still require Username.

The loader derives the username it stores in the same way. Username wins
when both are present, since it is the owner's own login and the per-user
override, and SharedUsername is used when Username is absent. That matches
what creating a shared server through the dialog produces, so an imported
shared server prompts for a password on the first connection rather than
sitting on a loading spinner with no username to connect with.
SharedUsername itself does not inherit a legacy Username, because it means
"the default username for the other users of this server" rather than a
fallback for the owner.

Folded in during review:

Validation now looks at the values rather than merely at the keys. Checking
for the presence of a key alone let {"Shared": true, "SharedUsername": null}
and its empty-string equivalent through, and the loader would then store
that as the server's username: on a schema created from the model that is an
integrity error surfaced as a raw "Error creating server" string, whilst on
a database migrated over the years, where the column carries no NOT NULL, it
imports silently and leaves behind a server that cannot connect. The
equivalent pre-existing hole on the non-shared path, where {"Username": ""}
is still accepted, is left for a separate change covering empty usernames in
general.

Tests in web/pgadmin/utils/tests/test_validate_json_data.py cover the
username requirements for shared and non-shared servers, the Service escape
hatch, the integer port check, and shared servers being skipped rather than
rejected for a non-admin user. The JSON format documentation notes that
SharedUsername may stand in for Username.

Fixes #9226
2026-08-18 10:11:58 +01:00
hiteshjambhale 104d018677 fix: dedupe concurrent getNodeAjaxOptions requests (#10307)
getNodeAjaxOptions() in web/pgadmin/browser/static/js/node_ajax.js, the
shared helper behind every AJAX-backed dropdown, cached responses only
once they had resolved, so when many components asked for the same URL
before the first response landed each of them saw an empty cache and
fired its own identical GET. Every column row's Data Type dropdown
mounting at once on a wide table's Columns tab therefore produced one
get_types request per row: around 150 concurrent duplicates on a
150-column table.

Concurrent GETs are now shared in the Axios wrapper. A module-level Map
tracks requests in flight, keyed on the request axios will actually make:
api.getUri() resolves the URI exactly as the request will, honouring the
instance baseURL, plain-object params, URLSearchParams and any custom
paramsSerializer, whilst the instance's own header configuration and the
per-request headers are hashed in alongside it, so an instance built with
extra headers never shares with a plain one. Params are canonicalised
first so that callers passing the same values in a different order still
share. The entry is removed once the request settles, success or failure,
so nothing leaks and later callers fetch fresh data.

Beyond params and headers there is plenty in an axios config that cannot
be captured in a key and that changes either what a caller gets back or
how it can be aborted: transformResponse, responseType, validateStatus,
timeout, signal and so on. Rather than let those share silently, a
request carrying anything outside the shareable set goes straight through
to api.get().

Each caller still runs its own caching and transform on the shared
response, so cached behaviour and response-shape handling are unchanged.

Fixes #10155.
2026-08-18 10:02:30 +01:00
Dave Page 81ac80321e fix: make the auth gating test independent of import order
The test added in #10240 fails in a full run, though it passes when the
authenticate package is tested on its own:

    FAIL: runTest (pgadmin.authenticate.tests.test_auth_gating
                   .AuthSourceGatingTestCase)
    AssertionError: 'oauth2' not found in
                    ['internal', 'kerberos', 'ldap', 'webserver']

It cleared sys.modules for each provider and expected load_modules(), which
reaches them with "from . import <provider>", to import and hence re-register
them. That is not enough: IMPORT_FROM finds the attribute already set on the
parent package by whichever earlier test imported the module, and returns the
previous module object without re-executing it, so the provider never
re-registers itself.

Only oauth2 was affected, because it is the one provider imported earlier in a
full run, by the tests under browser/tests. Nothing pre-imports it when the
authenticate package is run alone, which is why the narrow run passed and CI
did not.

Drop the attribute from the parent package alongside the sys.modules entry, so
the import genuinely happens again.

Verified with the full suite as CI runs it, python regression/runtests.py
--exclude feature_tests: both scenarios pass and the runner's own exit status
is clean, with the only remaining failure a local config-directory permissions
check that depends on the developer's config_local.py.
2026-08-17 16:54:47 +01:00
agu2347 01e5bf0993 Schema Diff: fix false SERIAL/BIGSERIAL differences and invalid ALTER SQL (#10248)
Schema Diff compares tables whose serial columns have been
reverse-engineered back onto the SERIAL/BIGSERIAL/SMALLSERIAL pseudo-type,
and that reprojection left two problems behind for any column declared as
SERIAL.

First, the raw oid of the sequence behind the column's nextval() default
(defseqrelid) was still being compared, and two independently created
databases assign that sequence completely different oids, so structurally
identical columns were reported as different; it is now ignored alongside
its sibling seqrelid.

Second, the SQL generated for a serial column that genuinely does differ
read the column's old properties straight from the catalogue without the
same reprojection, so the real integer type and the real nextval() default
were compared against the pseudo-type and its emptied default. Any genuine
difference, such as a comment or a NOT NULL, therefore also rendered an
invalid ALTER COLUMN ... TYPE bigserial, a spurious DROP DEFAULT, and a set
of sequence options that only an identity column accepts. The changed
column is now normalised back onto the type that ALTER COLUMN can actually
be given, whilst the sequence it owns is left to the sequence object's own
comparison.

Covered by a unit test for the column comparison and by a Schema Diff test
that applies the generated SQL to the target and re-compares.

Fixes #10236
2026-08-17 14:58:21 +01:00
byshy 549d71a203 feat: toggle Object Explorer by re-clicking Default workspace icon (#10254)
Clicking the Default workspace icon whilst that workspace is already
current now collapses the Object Explorer, and clicking it again brings it
back, in the manner of the VS Code side bar. The choice is remembered in a
new Browser/ObjectExplorerVisible setting, so it survives a refresh, and
File → Reset Layout restores the panel along with the rest of the layout.

The panel is hidden with CSS rather than unmounted, so the tree keeps its
expansion and selection state whilst it is out of the way, and because
rc-dock sizes box children with flex, the main panel takes the freed width
with no gap left behind.

Folded in during review:

The Object Explorer keyboard shortcut no longer does nothing when the
sidebar is collapsed. It asks for the panel to be shown first, through a
pgadmin:object-explorer:show event on pgAdmin.Browser.Events. The focus()
call in bindLeftTree() was already inert, since the panel is a div with no
tabindex, so the useful part of that shortcut is the tree selection that
follows it.

LayoutDocker now fires a RESET event which BrowserComponent listens for,
rather than resetLayout() being monkey-patched onto the shared docker
instance. That removes a mutation of an object other code holds a
reference to.

A failed attempt to persist the setting is logged rather than silently
discarded, since otherwise the sidebar could disagree with the server after
the next refresh with nothing to explain why.

The icon's tooltip says Show or Hide Object Explorer while it acts as a
toggle, instead of describing a workspace switch that will not happen, and
aria-pressed exposes the state to assistive technology.

Tests in web/regression/javascript/workspaces/WorkspaceProvider.spec.jsx
cover the default, the saved setting being honoured, each toggle persisting
its new value, two toggles in one tick resolving in order, and the reveal
event.
2026-08-17 14:49:49 +01:00
dependabot[bot] 7b592f2e82 Javascript dependency: Bump image-minimizer-webpack-plugin in /web (#10288)
Bumps [image-minimizer-webpack-plugin](https://github.com/webpack/image-minimizer-webpack-plugin) from 4.1.4 to 5.0.0.
- [Release notes](https://github.com/webpack/image-minimizer-webpack-plugin/releases)
- [Changelog](https://github.com/webpack/image-minimizer-webpack-plugin/blob/main/CHANGELOG.md)
- [Commits](https://github.com/webpack/image-minimizer-webpack-plugin/compare/v4.1.4...v5.0.0)

---
updated-dependencies:
- dependency-name: image-minimizer-webpack-plugin
  dependency-version: 5.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-17 14:48:25 +01:00
Dave Page efc0dea335 docs: describe what AI features transmit to LLM providers (#10291)
The AI documentation covered how to configure each provider but said
nothing about what actually leaves the server once one is configured,
which is the first question anyone working under data residency or
procurement constraints will ask.

This adds a provider-neutral section to the AI Reports documentation
setting out what may be transmitted, which is schema definitions,
configuration settings read from pg_settings, query text, EXPLAIN plan
output and, because the Query Tool assistant can run read-only queries,
row data where the assistant judges it necessary; when nothing is
transmitted at all, which is the default, since no provider is
configured out of the box; and the fact that each provider processes
that data under its own terms and in locations of its own choosing, so
the reader knows to check both those terms and their own organisation's
policies before enabling a cloud provider.

It also makes explicit that the list of providers reflects the APIs
pgAdmin can speak to rather than a recommendation, and cross-references
the new section from the AI Assistant notes in the Query Tool
documentation.
2026-08-17 14:25:47 +01:00
dev-hari-prasad 2de30f25c0 Gate external auth providers behind SERVER_MODE in desktop mode (#10240)
Desktop mode has no use for the kerberos, ldap, mfa, oauth2 or webserver
authentication providers: AUTHENTICATION_SOURCES defaults to internal, MFA is
inert because mfa_enabled() requires SERVER_MODE, and get_logout_url() already
guards its kerberos and oauth2 branches on SERVER_MODE. So skip importing and
initialising them unless SERVER_MODE is set, leaving desktop mode with
internal authentication alone.

The accompanying test needed wiring into our own runner before it would ever
execute: web/pgadmin/authenticate/tests/ had no __init__.py, so pkgutil did
not report it and find_modules() never reached the module, and the test class
derived from unittest.TestCase rather than BaseTestGenerator, so the
TestsGeneratorRegistry metaclass never registered it and get_suite() could not
pick it up. Both are fixed here, and the two scenarios now run and pass under
regression/runtests.py.

The test no longer asserts that pgadmin.authenticate.mfa is absent from
sys.modules in desktop mode, because it is not: both pgadmin/browser/__init__
and pgadmin/user_login_check import pgadmin.authenticate.mfa.utils
unconditionally. The original assertion only held because the test itself
popped the module first.
2026-08-17 13:39:59 +01:00
Dave Page c2398d5b08 chore: stop Dependabot re-proposing pickr 1.10 and Babel 8 (#10286)
Two exclusions, both for bumps we have already established we cannot take.

pickr 1.10 switched to a dual CJS/ESM build whose CommonJS entry point exports
a namespace object rather than the class, so Babel's interop leaves
_pickr.default as an object and every dialog carrying a colour field dies on
mount. That took master's feature tests down for three days (#10264, reverted
in #10278). Because 1.9 -> 1.10 is a minor bump it rejoined the grouped
minor-and-patch PR immediately afterwards, buried among twenty other packages,
so minor updates are excluded here as well as majors until withColorPicker.js
is adapted to the new export shape.

Babel 8 requires Node ^22.18.0 || >=24.11.0, whilst Node 20 remains our
minimum, and every 8.x package peer-depends on @babel/core ^8.0.0 whilst we
are on 7.x. Taking any of them individually, as Dependabot proposed for
preset-env, eslint-parser and plugin-syntax-jsx, leaves an unsatisfiable peer
requirement and a mixed 7/8 tree. Babel 8 needs a coordinated migration across
all ten @babel/* packages once we are on Node 22.
2026-08-17 12:13:56 +01:00
dependabot[bot] 98fda1cbb0 Javascript dependency: Bump the runtime-javascript-minor-and-patch group across 1 directory with 2 updates (#10279)
Bumps the runtime-javascript-minor-and-patch group with 2 updates in the /runtime directory: [electron](https://github.com/electron/electron) and [globals](https://github.com/sindresorhus/globals).


Updates `electron` from 43.3.0 to 43.4.0
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v43.3.0...v43.4.0)

Updates `globals` from 17.9.0 to 17.11.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.9.0...v17.11.0)

---
updated-dependencies:
- dependency-name: electron
  dependency-version: 43.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: runtime-javascript-minor-and-patch
- dependency-name: globals
  dependency-version: 17.11.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: runtime-javascript-minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-17 12:08:25 +01:00
Dave Page c20f0e33bb chore: stop Dependabot proposing major jest-dom bumps (#10280)
@testing-library/jest-dom 7 declares "engines": {"node": ">=22"}, whilst Node
20 remains our minimum and is what most of the buildfarm runs. The bump passes
our JS tests on Node 20 in practice, so this is a deliberate choice not to
depend on an officially unsupported combination rather than a reaction to a
failure.

The "^6.9.1" constraint in web/package.json already prevents 7.x from being
installed; what Dependabot proposes is widening that constraint, which is the
part we do not want, so ignore major updates for this package until the
buildfarm moves to Node 22. See #10271 and #10210.
2026-08-17 10:35:22 +01:00
dependabot[bot] e2ca7bb447 Javascript dependency: Bump @tanstack/react-virtual in /web (#10270)
Bumps [@tanstack/react-virtual](https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual) from 3.13.24 to 3.14.9.
- [Release notes](https://github.com/TanStack/virtual/releases)
- [Changelog](https://github.com/TanStack/virtual/blob/main/packages/react-virtual/CHANGELOG.md)
- [Commits](https://github.com/TanStack/virtual/commits/@tanstack/react-virtual@3.14.9/packages/react-virtual)

---
updated-dependencies:
- dependency-name: "@tanstack/react-virtual"
  dependency-version: 3.14.9
  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-08-17 10:29:57 +01:00
dependabot[bot] e3626c4918 Python dependency: Bump selenium in /web/regression (#10267)
Bumps [selenium](https://github.com/SeleniumHQ/Selenium) from 4.46.0 to 4.47.0.
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.46.0...selenium-4.47.0)

---
updated-dependencies:
- dependency-name: selenium
  dependency-version: 4.47.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-08-17 10:29:46 +01:00
Dave Page 7790264acb chore: batch minor and patch Dependabot updates into one PR per manifest (#10277)
We were carrying 27 open Dependabot PRs, the great majority of them single
patch bumps of transitive packages, and the review cost of that queue is
entirely out of proportion to its risk. Every genuine problem found whilst
clearing it (paramiko 5.0 breaking sshtunnel, use-resize-observer 10.0 dropping
its default export, jest-dom 7.0 requiring a newer Node) was a major bump.

Group minor and patch updates into a single weekly PR per manifest, and leave
major updates arriving individually so each still gets its own review. Grouping
applies to version updates only, so security updates are unaffected and
continue to arrive as separate PRs.
2026-08-17 10:23:20 +01:00
Dave Page 8ceffe310b Revert "Javascript dependency: Bump @simonwep/pickr from 1.9.1 to 1.10.1 in /web (#10264)" (#10278)
This reverts commit 34dba5740e0e0e3bb6dd06afe4d84f9e4c0a9a6e.

pickr 1.10.1 breaks every dialog carrying a colour field, which includes
Register - Server, so the dialog renders as "Something went wrong." and all
feature tests time out waiting for its fields.

The cause is a dual-package interop change rather than anything in our code.
In 1.9.1 the UMD factory ended with "return e = e.default", so module.exports
was the Pickr class itself; Babel's _interopRequireDefault saw no __esModule
marker, wrapped it, and _pickr.default was the constructor. In 1.10.1 the
factory instead returns a namespace object carrying a default getter, still
without an __esModule marker, so the wrapper nests it one level deeper and
_pickr.default is that namespace object:

  TypeError: _pickr.default is not a constructor
      at initPickr (pgadmin/static/js/helpers/withColorPicker.js:69)

Nothing in the JS unit tests, eslint or the webpack build can see this,
because it only fails when the component mounts, which is why the bump went in
with a green board and only the feature tests caught it.

Moving to 1.10.1 later is perfectly possible, but it needs the import in
withColorPicker.js adapted to the new shape, and it must be validated by
opening a dialog rather than by a green Jest run.
2026-08-17 10:22:50 +01:00
dependabot[bot] 34dba57409 Javascript dependency: Bump @simonwep/pickr from 1.9.1 to 1.10.1 in /web (#10264)
Bumps [@simonwep/pickr](https://github.com/simonwep/pickr) from 1.9.1 to 1.10.1.
- [Release notes](https://github.com/simonwep/pickr/releases)
- [Commits](https://github.com/simonwep/pickr/compare/1.9.1...v1.10.1)

---
updated-dependencies:
- dependency-name: "@simonwep/pickr"
  dependency-version: 1.10.1
  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-08-14 13:12:02 +01:00
dependabot[bot] 6fac4f0cbe Javascript dependency: Bump eslint from 10.8.0 to 10.8.1 in /runtime (#10260)
Bumps [eslint](https://github.com/eslint/eslint) from 10.8.0 to 10.8.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.8.0...v10.8.1)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.8.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 13:08:56 +01:00
dependabot[bot] 12830a7be8 Python dependency: Update setuptools requirement from ==83.* to ==84.* (#10261)
Updates the requirements on [setuptools](https://github.com/pypa/setuptools) to permit the latest version.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v83.0.0...v84.0.0)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-version: 84.0.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 13:08:31 +01:00
dependabot[bot] 1b48d616f9 Javascript dependency: Bump @testing-library/user-event in /web (#10262)
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 14.6.1 to 14.6.3.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v14.6.1...v14.6.3)

---
updated-dependencies:
- dependency-name: "@testing-library/user-event"
  dependency-version: 14.6.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 13:08:22 +01:00
dependabot[bot] 39a56a1074 Python dependency: Update cryptography requirement (#10263)
Updates the requirements on [cryptography](https://github.com/pyca/cryptography) to permit the latest version.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/49.0.0...50.0.0)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 50.0.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 13:08:19 +01:00
dependabot[bot] 6de909b2d5 Javascript dependency: Bump globals from 17.8.0 to 17.9.0 in /runtime (#10259)
Bumps [globals](https://github.com/sindresorhus/globals) from 17.8.0 to 17.9.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.8.0...v17.9.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 17.9.0
  dependency-type: direct:development
  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-08-14 12:04:02 +01:00
dependabot[bot] 1908d468f1 Javascript dependency: Bump electron from 43.2.0 to 43.3.0 in /runtime (#10258)
Bumps [electron](https://github.com/electron/electron) from 43.2.0 to 43.3.0.
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v43.2.0...v43.3.0)

---
updated-dependencies:
- dependency-name: electron
  dependency-version: 43.3.0
  dependency-type: direct:development
  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-08-14 12:03:59 +01:00
Flipper 9cd0e35f47 Add backendRefs group and kind to httproute.yaml (#10222) 2026-08-14 11:34:31 +01:00
Martin Manka 3cb33475ec Helm Chart: Add existingClaim to persistence (#10230) 2026-08-14 11:33:39 +01:00
byshyandCursor e6e35bb4c4 fix(pkg): remove trailing quote from Windows ProductVersion (#10250)
rcedit was passed "%APP_VERSION%"", which stamped ProductVersion as
e.g. 9.17" on Windows installer builds.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-14 11:31:43 +01:00
dependabot[bot] 1dd4d00246 Javascript dependency: Bump ip-address from 10.2.0 to 10.3.1 in /web (#10225)
Bumps [ip-address](https://github.com/beaugunderson/ip-address) from 10.2.0 to 10.3.1.
- [Release notes](https://github.com/beaugunderson/ip-address/releases)
- [Commits](https://github.com/beaugunderson/ip-address/compare/v10.2.0...v10.3.1)

---
updated-dependencies:
- dependency-name: ip-address
  dependency-version: 10.3.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:27:59 +01:00
Dave Page 9338129183 chore: stop Dependabot proposing major paramiko bumps (#10257)
paramiko 5.0 removed DSSKey entirely, whilst sshtunnel 0.4.0 still refers to
paramiko.DSSKey in SSHTunnelForwarder.get_keys(), which _consolidate_auth()
calls from the constructor. A major bump therefore does not merely drop DSA
key support, it raises AttributeError before any SSH tunnelled connection can
be established, and nothing in CI covers SSH tunnels so it looks green.
sshtunnel has had no release since 0.4.0 in 2019, so there is nothing newer to
move to on that side.

Ignore major paramiko updates until sshtunnel is fixed or replaced. The
exclusion is repeated under the /web/regression entry because
web/regression/requirements.txt starts with "-r ../../requirements.txt", so
that entry sees the root pins too.
2026-08-14 11:26:51 +01:00
dependabot[bot] 2d989238a4 Javascript dependency: Bump tar from 7.5.16 to 7.5.21 in /web (#10182)
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.16 to 7.5.21.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v7.5.16...v7.5.21)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.21
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:21:06 +01:00
dependabot[bot] aa16a12182 Javascript dependency: Bump shell-quote from 1.8.4 to 1.10.0 in /web (#10185)
Bumps [shell-quote](https://github.com/ljharb/shell-quote) from 1.8.4 to 1.10.0.
- [Changelog](https://github.com/ljharb/shell-quote/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/shell-quote/compare/v1.8.4...v1.10.0)

---
updated-dependencies:
- dependency-name: shell-quote
  dependency-version: 1.10.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:21:03 +01:00
dependabot[bot] d9635cd2cd Javascript dependency: Bump svgo-loader from 4.0.0 to 5.0.0 in /web (#10207)
Bumps [svgo-loader](https://github.com/svg/svgo-loader) from 4.0.0 to 5.0.0.
- [Release notes](https://github.com/svg/svgo-loader/releases)
- [Commits](https://github.com/svg/svgo-loader/compare/v4.0.0...v5.0.0)

---
updated-dependencies:
- dependency-name: svgo-loader
  dependency-version: 5.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:21:00 +01:00
dependabot[bot] 904bb2e1e2 Javascript dependency: Bump anti-trojan-source in /web (#10211)
Bumps [anti-trojan-source](https://github.com/lirantal/anti-trojan-source) from 1.12.0 to 1.12.1.
- [Release notes](https://github.com/lirantal/anti-trojan-source/releases)
- [Changelog](https://github.com/lirantal/anti-trojan-source/blob/main/RELEASE.md)
- [Commits](https://github.com/lirantal/anti-trojan-source/compare/v1.12.0...v1.12.1)

---
updated-dependencies:
- dependency-name: anti-trojan-source
  dependency-version: 1.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:20:56 +01:00
dependabot[bot] 02c8b3748e Javascript dependency: Bump brace-expansion in /web (#10220)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.16 to 1.1.18.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/commits)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.18
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:20:53 +01:00
dependabot[bot] 6696db11be Javascript dependency: Bump socket.io-parser from 4.2.6 to 4.2.7 in /web (#10229)
Bumps [socket.io-parser](https://github.com/socketio/socket.io) from 4.2.6 to 4.2.7.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.6...socket.io-parser@4.2.7)

---
updated-dependencies:
- dependency-name: socket.io-parser
  dependency-version: 4.2.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:20:50 +01:00
dependabot[bot] 109706d8f8 Javascript dependency: Bump fast-uri from 3.1.4 to 3.1.5 in /web (#10232)
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.4...v3.1.5)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:20:47 +01:00
dependabot[bot] 8cc69cf71c Javascript dependency: Bump dompurify from 3.4.12 to 3.4.13 in /web (#10241)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.12 to 3.4.13.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.4.12...3.4.13)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.4.13
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:20:43 +01:00
dependabot[bot] 1a87ee14d9 Javascript dependency: Bump js-yaml from 3.15.0 to 3.15.1 in /web (#10244)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.15.0 to 3.15.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/3.15.1/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/3.15.0...3.15.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 3.15.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:20:39 +01:00
dependabot[bot] 73c5e01309 Javascript dependency: Bump postcss from 8.5.21 to 8.5.23 in /web (#10246)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.21 to 8.5.23.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.21...8.5.23)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.23
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:20:35 +01:00
dependabot[bot] 1cca96b62c Javascript dependency: Bump eslint from 10.7.0 to 10.8.0 in /runtime (#10204)
Bumps [eslint](https://github.com/eslint/eslint) from 10.7.0 to 10.8.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.7.0...v10.8.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.8.0
  dependency-type: direct:development
  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-08-14 11:20:31 +01:00
dependabot[bot] 74687c9ddf Javascript dependency: Bump brace-expansion in /runtime (#10219)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 5.0.7 to 5.0.9.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.7...v5.0.9)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:20:28 +01:00
dependabot[bot] 0d6973c042 Javascript dependency: Bump globals from 17.7.0 to 17.8.0 in /runtime (#10223)
Bumps [globals](https://github.com/sindresorhus/globals) from 17.7.0 to 17.8.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.7.0...v17.8.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 17.8.0
  dependency-type: direct:development
  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-08-14 11:20:25 +01:00
dependabot[bot] 0b9fb6fc06 Javascript dependency: Bump axios from 1.18.1 to 1.19.0 in /runtime (#10224)
Bumps [axios](https://github.com/axios/axios) from 1.18.1 to 1.19.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.18.1...v1.19.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.19.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-08-14 11:20:21 +01:00
dependabot[bot] 60d4dd8c5a Javascript dependency: Bump undici from 7.28.0 to 7.29.0 in /runtime (#10227)
Bumps [undici](https://github.com/nodejs/undici) from 7.28.0 to 7.29.0.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v7.28.0...v7.29.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 7.29.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:20:18 +01:00
dependabot[bot] df7c256b53 Javascript dependency: Bump fast-uri from 3.1.4 to 3.1.5 in /runtime (#10231)
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.4...v3.1.5)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:20:12 +01:00
dependabot[bot] db7cbc7900 Python dependency: Update typer requirement from ==0.26.* to ==0.27.* (#10203)
Updates the requirements on [typer](https://github.com/fastapi/typer) to permit the latest version.
- [Release notes](https://github.com/fastapi/typer/releases)
- [Changelog](https://github.com/fastapi/typer/blob/master/docs/release-notes.md)
- [Commits](https://github.com/fastapi/typer/compare/0.26.0...0.27.0)

---
updated-dependencies:
- dependency-name: typer
  dependency-version: 0.27.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-14 11:20:08 +01:00
dependabot[bot] aac7d33441 Python dependency: Bump certifi from 2026.6.17 to 2026.7.22 (#10205)
Bumps [certifi](https://github.com/certifi/python-certifi) from 2026.6.17 to 2026.7.22.
- [Commits](https://github.com/certifi/python-certifi/compare/2026.06.17...2026.07.22)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2026.7.22
  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-08-14 11:20:05 +01:00
dependabot[bot] 9e0a847410 Python dependency: Bump selenium in /web/regression (#10206)
Bumps [selenium](https://github.com/SeleniumHQ/Selenium) from 4.45.0 to 4.46.0.
- [Release notes](https://github.com/SeleniumHQ/Selenium/releases)
- [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.45.0...selenium-4.46.0)

---
updated-dependencies:
- dependency-name: selenium
  dependency-version: 4.46.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-08-14 11:20:02 +01:00
Dave Page 35d68c47eb fix: follow Flask-Security-Too 5.8.2's corrected is_locked() semantics (#10256)
Flask-Security-Too 5.8.2, released on 12 August 2026, fixed a long-standing
inversion in its login forms: `LoginForm.validate()` previously read a `True`
return from `UserMixin.is_locked()` as "not locked, carry on", and the base
implementation unconditionally returned `True`. Our `User.is_locked()` was
written against that inverted convention, so as soon as CI began resolving
5.8.2 through the loose `Flask-Security-Too==5.8.*` pin, an unlocked user
returned `True`, form validation failed, the login POST redirected with a 302
and every subsequent request arrived as `AnonymousUser`. The server-mode data
isolation tests caught it, though the breakage is not limited to tests: on
5.8.2 nobody could log in at all.

`User.is_locked()` now returns `True` when the account is locked, matching the
corrected upstream contract, and the dependency is floored at 5.8.2 so that we
cannot silently resolve a release which reads the value backwards. The two
conventions are mutually exclusive, hence a floor rather than a version check
in the model. The regression tests are updated to assert the fixed contract.
2026-08-14 11:17:09 +01:00
Dave Page 6487f2a93f Add support for Python 3.14 (#10255)
Python 3.14 is now supported, so add the trove classifier for it to the
pip packaging metadata and move the desktop builds onto it: the macOS
bundle now defaults to 3.14.7, and the Windows build looks for an
interpreter in C:\Python314 by default, with both build READMEs updated
to match. The minimum supported version is unchanged at 3.9.

Whilst here, the SonarQube scanner's Python compatibility list had drifted
somewhat, still naming 3.7 and 3.8 and stopping at 3.11, so it has been
brought into line with the versions we actually support.
2026-08-14 11:16:25 +01:00
Ashesh Vashi 067f7af227 revert(pkg): back out all _fixup_imports path-handling attempts
pkg/mac/build-functions.sh reverted to bd841b882's state - keeps the
notarization diagnostic fix (print REQUEST_STATUS + fetch notarytool
log on failure), drops everything from 0296de48e through 6891b5e12.

Five iterations trying to fix _fixup_imports's otool handling of
"pgAdmin 4 Helper (GPU)" (newline-based path list, otool-classic
fallback via bare name, via resolved absolute path, absolute-path
retries for otool/install_name_tool) all failed to get the appbundle
build past this file. Confirmed along the way: the path itself is
byte-for-byte correct and the file verifiably exists at the moment
otool runs (od -c / ls -la evidence from a live buildfarm run); both
otool and otool-classic, called directly with correct quoting and a
resolved absolute path, still fail identically on it. That's a real
bug/limitation in Apple's tooling on this build node, not fixable by
further changes to how this script constructs or passes the path.

Reverting rather than continuing to guess. The stale nspkg.pth /
--system-site-packages fix in pkg/linux/build-functions.sh (verified
working, pgadmin4-rpm-build #180 succeeded) is untouched by this.
2026-07-30 12:30:49 +05:30
Ashesh Vashi 6891b5e12f fix(pkg): retry otool/install_name_tool with absolute path before giving up
pgadmin4-appbundle-build #116 (macos-arm64) failed even with
otool-classic invoked directly via its real resolved path
(669c2f244's fix) - identical error, byte-for-byte, to calling plain
`otool -L`:

  can't open file: .../pgAdmin 4 Helper  (No such file or directory)

That rules out PATH resolution and otool's internal re-exec dispatch
as the cause too. Both otool -L and otool-classic -L, invoked directly
with correct argv quoting and a verified-correct, verified-present
path, fail identically on this one binary. Whatever's actually broken
is inside these tools' own handling of "(" in a path they're given.

Untested variable so far: this script pushd's into the bundle dir and
passes relative paths ("./Contents/...") throughout. Try a plain
absolute path with each tool before falling back to the diagnostic
dump - cheap to test, and applies equally to the two
`install_name_tool -change` calls further down that edit this same
file in place, since they could hit the identical issue.

Still not proven - if this also fails, the diagnostic dump (retained
as final fallback) will at least confirm parens themselves are the
blocker regardless of path form, which would mean sidestepping via a
temporary parens-free symlink is the next thing to try.
2026-07-30 12:10:59 +05:30
Ashesh Vashi 669c2f2442 fix(pkg): resolve otool-classic's real path, bare name isn't on PATH
pgadmin4-appbundle-build #115 (macos-arm64) failed even with the
otool-classic fallback from 2d17e9e15:

  otool-classic output: .../build-functions.sh: line 260:
    otool-classic: command not found

otool-classic only exists inside the active Xcode toolchain
(/Applications/Xcode.app/.../XcodeDefault.xctoolchain/usr/bin/
otool-classic - the exact path visible in otool's own internal error
message), not as a bare command on $PATH. The previous fallback never
actually tested whether the real otool-classic binary can open the
file - it just failed on a missing command.

Resolve the real path via `xcrun -f otool-classic` (falling back to
deriving it from `xcode-select -p` if xcrun's lookup is unavailable)
and invoke that directly.

Not yet verified against a real buildfarm run.
2026-07-30 12:01:52 +05:30
Ashesh Vashi 2d17e9e15d fix(pkg): retry via otool-classic directly, now with hard evidence
pgadmin4-appbundle-build #114 (macos-arm64) failed on the same
"pgAdmin 4 Helper (GPU)" binary as before, but this time the
diagnostics from d4364555a actually ran and proved the path itself is
not the problem:

  Raw bytes of the path (od -c): plain ASCII throughout, single
  spaces, no non-breaking/unicode look-alikes, no trailing garbage.
  ls -la of the exact path: file exists, correct size/perms,
  timestamped seconds before otool ran - not a race condition either.

otool -L still failed via its internal otool-classic re-exec on that
exact, verified-correct, verified-present file. That's conclusively a
bug in otool's own internal dispatch, not anything this script builds
or passes to it - we can't fix Apple's tool, but we can avoid
triggering its broken code path.

Retry via `otool-classic -L "${TODO_OBJ}"` called directly (our own
argv-based quoting, not otool's internal re-exec, whatever that does
differently) when the initial `otool -L` fails. Every other binary in
the bundle keeps going through plain `otool -L` unchanged - this only
engages for the specific case that's already failing anyway. The
diagnostic dump (od -c / ls -la / otool-classic's own output) is kept
as the final fallback in case even the direct call fails too.

Not yet verified against a real buildfarm run.
2026-07-30 11:55:29 +05:30
Ashesh Vashi d4364555ae fix(pkg): diagnostics never ran - set -e ate the failure before $? check
pgadmin4-appbundle-build #113 (macos-arm64) failed again on the same
otool -L call, but none of the diagnostic output from 401afbfa3 showed
up. Instead:

  The command "OTOOL_OUTPUT=$(otool -L "${TODO_OBJ}" 2>&1)" failed in
  "_fixup_imports" with exit code 1.

That's this script's own ERR trap firing on the assignment statement
itself. Under set -e, `VAR=$(cmd)` still propagates cmd's non-zero
exit to the trap immediately - the previous diagnostic's separate
`OTOOL_STATUS=$?` line right after never got a chance to run, so
nothing was ever printed, same as the original bare failure.

Move the assignment into the condition of the `if` itself
(`if ! OTOOL_OUTPUT=$(...); then`) - bash explicitly exempts a
command being tested by if/while/until (or negated with !) from
errexit, so this time the diagnostic block will actually execute.

Still not verified - watching the next buildfarm run.
2026-07-30 11:28:36 +05:30
Ashesh Vashi 401afbfa31 fix(pkg): add diagnostics for otool -L failures instead of guessing
pgadmin4-appbundle-build #112 (macos-arm64) failed with otool-classic
unable to open a path that our own logging showed as correct
immediately beforehand ("pgAdmin 4 Helper (GPU)"). Rather than work
around it blindly (an otool-classic fallback was tried and reverted -
unverified, and risks masking a real problem instead of fixing it),
capture real evidence the next time this happens:

- raw byte dump (od -c) of ${TODO_OBJ} right before otool runs, and
  again on failure - catches non-ASCII or invisible whitespace that
  a plain `echo` can't show
- ls -la of the exact path and its containing directory on failure -
  confirms whether the file actually exists at that exact moment
  (rules out/in a race with something still writing it)
- otool's actual stdout/stderr and exit code, instead of letting its
  own error text be the only signal

No fallback behavior - fails loudly (exit 1) with this diagnostic
dump so we can tell what's actually happening from the next real
buildfarm run instead of speculating further.
2026-07-30 11:20:38 +05:30
Ashesh Vashi 0296de48ee fix(pkg): _fixup_imports truncates paths with spaces, breaking otool
pgadmin4-appbundle-build #110 (macos-x64) failed with:
  error: otool-classic: can't open file: ./Contents/Frameworks/pgAdmin
  error: otool-classic: can't open file: ./Contents/Frameworks/Electron

_fixup_imports built its worklist with `awk -F':| '`, a regex
alternation that splits on a literal ':' OR any bare space. `file`'s
actual output format is "path: description" (colon-space as one
token), so any space *inside* the path itself - "pgAdmin 4 Helper
(Plugin).app", "Electron Framework.framework", both routine in macOS
app bundles - also got treated as a split point, truncating $1 to
whatever preceded the first space.

The resulting list was then joined with a single space (ORS=" ") and
iterated via an unquoted `for x in $list`, which word-splits on
spaces again - doubly ambiguous between a path's own spaces and the
list's separator, unrecoverable however it's parsed.

Fix: split only on the literal ": " token file emits (`awk -F': '`,
not `-F ':| '`), and switch the whole worklist to newline-separated
instead of space-separated, iterated with `while IFS= read -r`
instead of `for x in $unquoted_list`, at both the outer (executable)
level and inner (library-copy) level (`TODO="${TODO}"$'\n'"..."`).

Not yet verified against a real buildfarm run - can't reproduce the
macOS codesigning environment locally. Watching the next
pgadmin4-appbundle-build run.
2026-07-30 09:48:03 +05:30
Ashesh Vashi 689c969f95 fix(pkg): stop using --system-site-packages, use a scoped .pth instead
The previous fix (bd841b882) tried to delete the specific broken
sphinxcontrib-jsmath nspkg.pth file from the system site-packages
before creating the venv. It didn't work: pgadmin4-rpm-build #177
failed identically on el-10. The delete silently no-opped - that
directory is root-owned on the build node and the build user doesn't
have write access there, so `find -delete` failed while `-print` (which
runs first) still logged the match, making the fix look like it ran.

Root cause is the --system-site-packages flag itself: it uses Python's
site.addsitedir() internally, which does not just add a directory to
sys.path - it also scans that directory for every *.pth file and
executes any "import ..." lines found in them. That's what runs the
broken nspkg.pth's stale namespace-package bootstrap code and corrupts
sys.path before core stdlib resolves, breaking pip's own subprocess.

We only need --system-site-packages for OS-provided packages that
don't have reliable pip wheels (e.g. dbus-python, a hard runtime
dependency per pkg/debian/build.sh's python3-dbus dep, needs
libdbus-1-dev to build from source). A *plain path line* (no "import")
in a .pth file only appends that directory to sys.path - it does not
trigger a further .pth scan of it. So: create the venv without
--system-site-packages, then write the system site-packages
directories as plain lines into a .pth file inside the venv's own
site-packages (which the build user does own). Same OS-package
availability, without ever asking Python to treat the system directory
as a site directory - the broken nspkg.pth is simply never read.

Not yet verified against a real buildfarm run - can't reproduce the
el-10 environment locally. Watching the next pgadmin4-rpm-build run.
2026-07-30 09:36:44 +05:30
Ashesh Vashi bd841b8827 fix(pkg): surface real notarization failures, guard against stale nspkg.pth in venv
macOS build (build-functions.sh): _notarize_pkg only printed
"Notarization failed." on rejection, giving no indication why. Print
the actual REQUEST_STATUS and fetch the full notary log via
`notarytool log` so future failures are actually diagnosable from the
Jenkins console instead of just "status: Invalid".

Linux build (build-functions.sh): _create_python_virtualenv creates
venvs with --system-site-packages, which pulls in the entire system
site-packages dir - including any stale, improperly-uninstalled
package's namespace-package .pth hook. That legacy pip/setuptools
mechanism runs at interpreter startup, before core stdlib is
guaranteed to resolve; a leftover sphinxcontrib-jsmath nspkg.pth on
the el-10 build node corrupted sys.path early enough to break pip's
own subprocess bootstrap, failing the whole build with a misleading
"No module named 'importlib'"/"'traceback'" error
(pgadmin4-rpm-build #176). Remove that exact known-broken file before
creating the venv - not every *-nspkg.pth, since this touches the
*system* Python install on a build node shared by other jobs, and any
other such file could still be load-bearing for something unrelated.
--system-site-packages itself is left untouched - it's required so
venvs can see OS-provided packages not available as clean pip wheels
on every target platform.
2026-07-30 09:21:22 +05:30
Ashesh Vashi 3f99454199 fix: unbound keyring probe at config-import time can hang desktop startup
evaluate_and_patch_config() called keyring.get_password() synchronously
at `import config` time to detect a selected-but-unusable OS keyring
backend. On Debian 13 (and similar headless/RDP sessions with no live
D-Bus/GNOME-Keyring session), that call - and even the keyring import
itself - can block forever, freezing the whole desktop app before it
ever starts.

Revert evaluate_config.py to the pre-9.17 synchronous check (backend
name only, no get_password call). Move the usability probe into
pgadmin.utils.keyring_probe, run from create_app() in a background
daemon thread so it never delays startup, isolated in its own process
via subprocess.Popen(sys.executable, '-c', ...) so a hang can actually
be killed - a thread-only timeout can't do this, it would leave
CPython's per-module import lock held forever and wedge any later
`import keyring` in the parent process too.

subprocess.Popen (plain fork+exec), not multiprocessing.Process, is
required here: create_app() runs at the top level of pgAdmin4.py while
it's still being imported, and multiprocessing's spawn start method
refuses to start a child before the current process finishes
bootstrapping its __main__ module. An earlier version of this fix used
multiprocessing and crashed the probe thread with RuntimeError on every
single test run, which corrupted the SQLAlchemy/sqlite session for the
rest of app init and surfaced as an unrelated-looking "attempt to write
a readonly database" error on module_preference inserts.

config.USE_OS_SECRET_STORAGE is only ever read from request handlers
requiring an authenticated session, never at import time or inside
create_app() itself, so the async resolution race is safe in practice.

Tests mock subprocess.Popen for the timeout/kill/config-fallback
orchestration (deterministic, no real 3s wait or backend dependency),
plus one test class that runs the real probe script in a real
subprocess against a fake keyring module injected via PYTHONPATH, so
the script body itself has real coverage.

Verified with the full regression suite (--exclude feature_tests) in
both desktop mode (2134 passed, 0 failed) and server mode (2251
passed, 0 failed); remaining skips are pre-existing pgAgent-dependent
job tests.
2026-07-30 00:00:33 +05:30
Ashesh Vashi 6e029ab6e5 docs: correct reporter credit for #10193/#10194 to full name
Hung Tran Quoc (GitHub handle @rampage0010) — release notes only
listed the given name.
2026-07-28 12:40:25 +05:30
Ashesh Vashi ee7109d400 Updated version for release v9.17 2026-07-28 12:08:09 +05:30
Ashesh Vashi 78f7a5f6ab test: restore app.PGADMIN_EXTERNAL_AUTH_SOURCE in auth-mocking tearDown
Each of the Kerberos/LDAP/OAuth2/webserver login-mocking test classes
sets self.app.PGADMIN_EXTERNAL_AUTH_SOURCE (the Flask app instance
attribute before_request() actually reads) in setUp, but tearDownClass
only restored app_config.PGADMIN_EXTERNAL_AUTH_SOURCE -- a different
object (the config module). Whichever of these classes ran last
(test_webserver_with_mocking, alphabetically) left the live app
attribute stuck at WEBSERVER for the rest of the suite, making
before_request() route every subsequent unauthenticated /login request
through authenticate.login()'s webserver auto-auth path instead of
rendering the login form -- surfacing as an unrelated CSRF-harvest
failure in test_close_requires_auth much later in the run.

Full server-mode regression: 2507 tests, 0 failures/errors (was 2).
2026-07-28 11:07:37 +05:30
Ashesh Vashi 689d200e9f test: fix server-mode test-harness bugs found running the full suite
get_test_user() created secondary test clients via app.test_client()
but never called setApp() on them, so any test using a second user
crashed with 'NoneType has no attribute config' the first time it
needed to fetch a CSRF token (44 errors in a full server-mode run).

TestSqlEditorCloseRequiresAuth.setUp() logs out the shared
class-level tester before asserting a CSRF token was harvested; when
that assertion failed, unittest skipped tearDown() entirely (by
design, tearDown only runs if setUp succeeds), leaving the tester
logged out for the rest of the suite and cascading into 188 unrelated
failures. Switched to addCleanup(), which always runs.

Full server-mode regression: 2507 tests, was 232 failures/errors,
now 2 (both isolated to this same test's own CSRF-harvest timing in
full-suite order, unrelated to these two fixes).
2026-07-28 10:12:18 +05:30
Ashesh Vashi 339b4f9cd3 Merge branch 'cve-9.17-rebase' into master 2026-07-28 09:12:21 +05:30
Domenico Sgarbossa 5fc319551d Updated message catalogs for v9.17 (#10212) 2026-07-28 09:00:14 +05:30
Ashesh Vashi 9ef9a7136f docs: add 9.17 release notes for CVE-2026-17346 through 17351, 17566
Adds bug-fix bullets for issues #10190-10194, #10200 (previously
assigned CVE-2026-17346..17351) and #10213 (CVE-2026-17566, the
import/export \copy backslash-escape RCE reported by Arpit Jain).
2026-07-28 08:56:41 +05:30
Ashesh Vashi 1496fabe28 fix(security): reject ambiguous backslash-escape in import/export query guard
_is_query_parens_balanced() always treated \' inside a single-quoted
string as an escaped quote, matching psql only when
standard_conforming_strings=off. Under the actual default (scs=on,
every supported PostgreSQL version), psql treats \ as a literal
character, so 'a\' closes the string right there. A crafted query
export payload like SELECT 'a\') TO PROGRAM 'cmd' x' passed the
balance check while the real ) it hid closed the wrapping \copy (...)
context in psql, exposing a live TO PROGRAM clause for RCE.

Since the correct interpretation depends on a server setting we
can't reliably know, reject any backslash inside a single-quoted
string outright instead of guessing.

Reported by Arpit Jain (arpitjain099).
2026-07-27 19:04:29 +05:30
Kundan ef76102bcd fix(llm): close lexer-differential bypass in AI Assistant read-only guard
sqlparse's string-literal lexing can disagree with PostgreSQL's: under
standard_conforming_strings = on (the default), a backslash before a
quote is an ordinary character to PostgreSQL but sqlparse treats it as
escaping the quote, so a payload like
SELECT '\';COMMIT;CREATE TABLE pwn(x int);SELECT 1 --'
passes _validate_readonly_query as a single SELECT while PostgreSQL
executes it as four statements -- the smuggled COMMIT ends the wrapping
BEGIN TRANSACTION READ ONLY and the trailing ROLLBACK is a no-op,
reintroducing the write/RCE bypass the bf47924444 fix was meant to
close (reported by Kai Aizen / SnailSploit).

Run the LLM-supplied query with prepare=True, forcing psycopg3's
extended query protocol. PostgreSQL's own Parse step -- not a
client-side approximation of it -- rejects any text containing more
than one statement, independent of how it's lexed. Threaded through
execute_2darray as an opt-in parameter (default None) so no other
caller's behavior changes. Also set SESSION CHARACTERISTICS AS
TRANSACTION READ ONLY as defense-in-depth against a smuggled COMMIT.

prepare=True alone is not sufficient: psycopg3's PrepareManager.get()
returns Prepare.NO -- checked before it even inspects the prepare
argument -- whenever the connection's prepare_threshold is None, which
is pgAdmin's per-server default ("Prepare threshold" is blank unless an
administrator sets it). On a default-configured server the extended
protocol never actually engaged, so the bypass stayed live. Force
prepare_threshold=0 on the LLM's single-use connection in
_connect_readonly() so the extended protocol -- and PostgreSQL's
single-statement Parse-step guarantee -- is unconditional on this
connection, without touching the server-wide setting or any other
session/caller.

Adds regression coverage at three levels: unit tests pinning that
prepare=True is always passed and that prepare_threshold=0 is forced
on the connection; and an end-to-end test driving the real
/sqleditor/nlq/chat/<trans_id>/stream route (LLM client mocked, real
tool-dispatch path) with the exact smuggled-COMMIT payload, confirming
the guarantee holds from the HTTP entry point down to the driver call.
2026-07-27 16:51:46 +05:30
Ashesh Vashi 2b4307f321 feat: add system-wide default for Geometry Viewer custom tile provider (#10198)
Follow-up to #10142. Geometry Viewer's custom tile provider was only
configurable per-user (Preferences), with no way for an administrator
to set an organization-wide default (e.g. an internal tile server)
that applies out of the box for every user.

config.DEFAULT_GEOMETRY_VIEWER_PROVIDER is a plain dict; the five
custom_tile_* preference defaults (url, name, crs, attribution,
max_zoom) are sourced from it instead of hardcoded literals.
Preference.get() only reads a per-user DB row if the user has
explicitly saved one, otherwise it falls back to this default --
override semantics, one active provider, no naming-conflict surface.

config_local.py/config_distro.py/PGADMIN_CONFIG_* replace this config
variable wholesale, not merged key-by-key, so a naive
DEFAULT_GEOMETRY_VIEWER_PROVIDER['name'] lookup would KeyError-crash
preference registration (and app startup) on any partial admin
override, or AttributeError on a wrong-typed value.
resolve_geometry_viewer_provider_defaults() guards against this:
falls back per-field on missing/wrong-type values, validates crs
against the 3 supported choices, validates max_zoom is a non-bool int
in [0, 25], degrading to the original hardcoded defaults instead of
crashing.

No new attacker-reachable surface: DEFAULT_GEOMETRY_VIEWER_PROVIDER is
filesystem/deployment-level admin config, same trust boundary as
DEFAULT_BINARY_PATHS/OAUTH2_CONFIG. Existing DOMPurify sanitization on
name/attribution and the http(s):// + {x}/{y}/{z} URL validation in
GeometryViewerUtils.js (from #10142) apply uniformly to config-sourced
and per-user values alike.

Adds test_geometry_viewer_provider_defaults.py (9 scenarios: fully
valid, partial override, non-dict, invalid CRS, out-of-range/non-int/
bool max_zoom, non-string fields, empty dict).
2026-07-27 16:07:17 +05:30
Kundan Sable 362c202b25 fix: close remaining front-door-only tool-RBAC gaps (tools_ai, import_export_servers, search_objects, change_password)
Extends the tool-RBAC fix (permission enforced only on a blueprint's
"front door" route, with the rest relying on @pga_login_required alone)
to four permission types the original fix didn't cover:

* tools_ai ("AI Reports"): none of the 14 Security/Performance/Design
  report routes in the llm blueprint checked this permission at all --
  only the frontend menu was hidden behind AllPermissionTypes.TOOLS_AI
  (llm/static/js/ai_tools.js). A user an admin had denied AI access to
  could call the report/report-stream routes directly and have real
  server/database/schema content sent to whichever external LLM
  provider is configured, bypassing both the access control and the
  data-exposure/cost boundary the permission exists to enforce.
* tools_import_export_servers: only 'save' was gated; 'get_servers' and
  'load_servers' relied on @pga_login_required alone. Lower impact than
  the above: get_servers only returns the calling user's own servers,
  and load_servers only previews an already-uploaded file without
  persisting (the actual write stays gated at 'save').
* tools_search_objects: only 'search' was gated; 'types' (a static list
  of supported object-type names for the dropdown, no DB content) was
  not.
* change_password: the self-service password-change route
  (browser.change_password) never checked this permission, so revoking
  "Change Password" from a role in Role management had no effect --
  users under that role could still change their own password. Lower
  severity than the others since the route is inherently self-scoped
  (can only ever affect the caller's own password).

Extends tools/tests/test_tool_permissions_required.py with 17 new
scenarios for the first three, and adds a dedicated
browser/tests/test_change_password_permission_required.py for the last
(different blueprint, different permission category).

Verified against a live PostgreSQL server in SERVER mode: all 40
scenarios (37 + 3) pass with the fix applied. Negative control:
reverting the four fixes reproduces exactly the reported gaps -- 17 of
the 20 pre-existing scenarios still pass unaffected, the 17 new ones
fail, and change_password returns 200 instead of 403 -- confirming the
tests actually catch the regression rather than passing vacuously.
2026-07-27 05:28:57 +00:00
Ashesh Vashi 84b93c268d chore(deps): consolidated dependency bumps (setuptools, fast-uri, tar, shell-quote, svgo) (#10199)
Supersedes 7 open Dependabot PRs by applying the safe ones and
properly fixing the one with a broken lockfile, in one CI cycle:

- setuptools ==82.* -> ==83.* (#10144, #10145 - duplicate PRs, same
  patch). Only touches the `python_version > '3.9'` line; the
  `<82; python_version <= '3.9'` gate for Python 3.9 is untouched,
  so this doesn't affect Python 3.9 support.
- fast-uri 3.1.2 -> 3.1.4 in both /web and /runtime (#10183, #10195)
  - fixes two real CVEs (GHSA-v2hh-gcrm-f6hx, GHSA-4c8g-83qw-93j6).
- tar 7.5.16 -> 7.5.21 (#10182) - patch series, decompression-bomb
  and unbounded-recursion hardening only.
- shell-quote 1.8.4 -> 1.10.0 (#10185) - additive opt-in option +
  parser fixes, no breaking changes.
- svgo 3.3.3 -> 4.0.2 (#10184) - Dependabot's own PR left yarn.lock
  internally inconsistent (dropped the workspace-level `svgo` entry
  while merging version-range blocks), so `yarn install --immutable`
  failed in CI with "the lockfile would have been modified by this
  install". Regenerated properly via `yarn up`/`yarn dedupe` here.
  The direct `svgo`/`svgo-loader` deps are not actually wired into
  any webpack rule (verified via grep) - the real SVG pipeline is
  `@svgr/webpack` -> `@svgr/plugin-svgo` -> svgo 3.3.3, which this
  bump does not touch - so the major version jump has no build
  impact. `yarn.lock` now correctly keeps that separate 3.3.3
  resolution alongside the deduped 4.0.2 one.

Not included (structurally blocked, tracked separately):
- paramiko 3.5.1 -> 5.0.0 (#9927): paramiko 5 removes DSSKey
  entirely; sshtunnel 0.4.0 (dormant since 2021) still references
  paramiko.DSSKey, so `import sshtunnel` would crash immediately.
- pywinpty 2.0.* -> 3.0.* (#10082, #10084): the existing pin cites
  https://github.com/andfoy/pywinpty/issues/545, confirmed still
  open ("process read and write not working as expected in 3.x").

Verified: `yarn install --immutable` clean in both /web and
/runtime, `yarn run linter` clean, full `yarn run bundle:dev`
compiles successfully.
2026-07-25 01:46:48 +05:30
Ashesh Vashi 1e7407bbf6 Merge remote-tracking branch 'origin/master' into cve-9.17-rebase 2026-07-25 01:04:16 +05:30
Ashesh Vashi 9bcc0ff8de docs: fix RST emphasis error in v9.17 release notes
Trailing "SELECT *" broke sphinx's inline emphasis parser (bare *
without matching close), failing the macOS appbundle docs build.
2026-07-25 01:02:36 +05:30
Kundan Sable f75452bfd0 test(security): behavioral regression test for name-literal SQL escaping
Add test_name_literal_sql_escaping.py covering the templates fixed in the
CVE-2026-12044 follow-up: index Statistics (coll_stats.sql, both
dialects), publications (pg + ppas), and subscriptions dependency/
get-position lookups.

Each scenario renders the real template with a stacked-statement
apostrophe payload and asserts (1) the object name appears exactly as
qtLiteral escapes it and (2) the rendered SQL parses as exactly one
statement -- the property that actually prevents statement smuggling.
Verified the semantic assertion fails on the pre-patch raw-interpolation
form (parses as 2 statements) and passes on the fixed form, so the test
genuinely guards the fix rather than trivially passing.

Complements test_stats_template_regclass_cast.py (single-index
pgstatindex path) and the lint guard in test_sql_string_literal_lint.py.
Pure template-render test, no DB required.

Also independently verified during review: the fix's escaping neutralises
a live stacked-statement injection on PostgreSQL 16 (pre-patch slept 5s,
post-patch 0.0s) for both index-stats and pub/sub paths, and confirmed
the browser tree label and Statistics grid render object names as
React-escaped JSX text, so the HTML/XSS probe in an object name does not
execute in those paths. EPAS/ppas runtime path not exercised (ppas
publication templates are byte-identical to the pg variants and are
covered at render level by this test).
2026-07-25 00:55:46 +05:30
Kundan Sable 73b3218992 fix(security): escape index-stats/publication/subscription name sinks (CVE-2026-12044 follow-up)
The 9.16 fix for CVE-2026-12044 hardened qtLiteral and switched sixteen
COMMENT ON / pgstattuple / pgstatindex templates to it, but the fix
missed several sinks that were previously excused in
test_sql_string_literal_lint.py's ALLOWLIST on the (incorrect)
assumption that schema/table/publication/subscription names sourced
from pg_catalog via the browser tree could never contain apostrophes.
They can, since PostgreSQL permits arbitrary characters in quoted
identifiers, so a low-privileged user able to CREATE TABLE, CREATE
PUBLICATION, or CREATE SUBSCRIPTION can plant a name that breaks out of
the unescaped '{{ name }}' interpolation once any user views that
object's Statistics or Dependencies tab.

Switch the following templates to qtLiteral(conn):
- schemas/tables/templates/indexes/sql/{16_plus,default}/coll_stats.sql
  (Index Statistics -- reported as "SQL injection in pgAdmin index
  Statistics (incomplete fix for CVE-2026-12044)")
- publications/templates/publications/{pg,ppas}/default/sql/
  {dependencies,get_position}.sql
- subscriptions/templates/subscriptions/sql/default/
  {dependencies,get_position}.sql

publications/__init__.py and subscriptions/__init__.py now pass
conn=self.conn into the dependencies.sql render_template call so the
qtLiteral filter has a connection to quote against. Remove the
corresponding ALLOWLIST entries in test_sql_string_literal_lint.py now
that these sinks are properly escaped instead of merely assumed safe.

Patch supplied pre-written; manual testing by the reporter reported
both the SQL-injection and HTML-injection probes as positive, but the
exact pre/post-fix state tested was not fully disambiguated in this
session -- treat as reported-but-not-independently-verified pending
follow-up confirmation.
2026-07-25 00:55:46 +05:30
Dave Page 24fdcf0f58 fix(security): add missing auth decorators to Constraints/preferences/debugger/schema_diff routes (CVE-2026-12046 follow-up)
In SERVER mode pgAdmin enforces authentication per route via
@pga_login_required; the before_request hook only handles desktop
auto-login and the Kerberos/Webserver redirect, so a route shipped
without the decorator is reachable unauthenticated (CWE-306). This is
the same defect class as CVE-2026-12046 (the sqleditor close/
update_connection routes).

A sweep found further omissions, now fixed with @pga_login_required:

* Constraints blueprint: nodes, proplist (obj), delete -- the routes
  named in the "SQL injection in pgAdmin index Statistics" follow-up
  report as an incomplete fix for CVE-2026-12046. delete is a
  state-mutating DELETE (removes table constraints); nodes/proplist
  return object information. Adds the missing pga_login_required import
  to this module.
* preferences.get_all_cli (GET)
* debugger.close (DELETE)
* schema_diff.close (DELETE)
2026-07-25 00:55:46 +05:30
Ashesh Vashi e7a8576731 Fix MASTER_PASSWORD_HOOK tokenisation on Windows
posix=False in shlex.split() never strips quote characters, so any
Windows hook path quoted to handle spaces (or any quoted argument)
came out with literal quotes still attached, and unquoted spaced
paths were split into multiple argv elements either way.

Use a shlex.shlex instance with posix=True (correct quote-stripping)
and escape='' (so backslashes in Windows paths are not treated as
escape characters). Document the quoting requirement in config.py
and add regression tests for quoted/unquoted spaced paths.
2026-07-25 00:55:46 +05:30
Dave Page ea7e798aac Fix OS command injection in the MASTER_PASSWORD_HOOK feature
The MASTER_PASSWORD_HOOK setting lets administrators specify an external
command that returns a per-user encryption key, with %u in the configured
string replaced by the current user's name. The previous implementation
substituted the username into the command string and executed the result
with subprocess.Popen(..., shell=True). Because the username can originate
from an external authentication source (OAuth/OIDC claims, Kerberos,
webserver auth), a username containing shell metacharacters allowed an
authenticated user to execute arbitrary commands as the pgAdmin service
account in deployments where the hook uses %u.

Tokenise the trusted hook string into an argument vector first, substitute
the untrusted username into the individual arguments, and execute with
shell=False. The username is therefore always confined to a single argv
element and any shell metacharacters it contains are inert.

Note for administrators: hooks that previously relied on shell features
(pipes, redirection, environment-variable expansion, globbing) in the
MASTER_PASSWORD_HOOK string itself will no longer have those interpreted;
such logic should be moved into the hook script. The documented form,
'<PATH>/script.sh %u', is unaffected.

Adds regression tests covering usernames containing ';', '$()', backticks,
pipes, '&&' and newlines, plus an end-to-end marker-file proof that no
shell execution occurs.

Reported-by: B1gN0Se
2026-07-25 00:55:46 +05:30
Ashesh Vashi 64a9cdbd6a fix: close remaining tool-RBAC gaps found in review
Extends the tool-permission enforcement pattern from the reported CVE
fix to the backup/restore/maintenance/import_export blueprints, which
had the identical front-door-only gap: only create_*_job was gated,
while objects()/check_utility_exists()/get_import_export_settings()
relied on @pga_login_required alone.

Also clears password/save_password/tunnel_password on adhoc server
clones, not just ownership fields - Server.clone() copies every
column, so a non-owner cloning another user's shared server was still
persisting that user's stored credentials into a row the non-owner
now owns.

Adds missing socket test scenarios (erd tables, psql socket_input/
socket_set_role/resize) that the existing test docstring claimed to
cover but didn't.
2026-07-25 00:55:46 +05:30
Dave Page ba1984718a Add RBAC regression tests for tool routes, sockets and adhoc ownership
Add regression coverage for the authorisation fixes:

  * test_tool_permissions_required: a consolidated, per-blueprint check
    that logs in as a user with no roles (hence no tool permissions) and
    asserts every gated backend HTTP route across the query tool, grant
    wizard, schema diff, ERD, PSQL and debugger returns 403. This catches
    any future route added to these blueprints without the decorator.
  * test_tool_socket_permissions_required: asserts the schema diff
    compare_database/compare_schema and psql start_process Socket.IO
    handlers refuse a user lacking the tool permission.
  * test_adhoc_connect_server_ownership: asserts that an adhoc connect
    triggered by a non-owner against an administrator-owned shared server
    persists a server row owned by the caller and not shared.

All three are skipped in DESKTOP mode, where every request is
auto-authenticated as the all-permissions DESKTOP_USER.
2026-07-25 00:55:45 +05:30
Dave Page a7e74a6ed6 Re-home adhoc cloned servers to the calling user
When /misc/workspace/adhoc_connect_server is given a sid, it clones the
existing server. Server.clone() copies every column of the source row,
including user_id, shared and shared_username. When a non-owner triggered
an adhoc connect against an administrator-owned shared server, the clone
inherited the administrator's ownership and shared flag, so pgAdmin
persisted a new, administrator-owned, shared adhoc server row created at
the behest of another user; the row is committed before the connection is
attempted, so it survived even when the connection failed.

Force the cloned adhoc record to belong to the current user and to be
private (user_id, shared, shared_username) before committing, mirroring
the new-server branch, so a non-owner can no longer persist a
cross-tenant server record.
2026-07-25 00:55:45 +05:30
Dave Page d36bd8dc96 Enforce tool RBAC on every backend route, not just the entry point
The tool permissions (tools_query_tool, tools_grant_wizard,
tools_schema_diff, tools_erd_tool, tools_psql_tool, tools_debugger) were
enforced only on a single "front door" route per tool, whilst the rest of
each tool's backend workflow relied on pga_login_required alone. An
authenticated user who had been denied a tool could therefore still drive
the tool through its other routes and Socket.IO handlers:

  * Query Tool: View/Edit Data via sqleditor.initialize_viewdata and the
    rest of the view-data chain, bypassing the gate on initialize_sqleditor.
  * Grant Wizard: object discovery (objects), SQL preview (modified_sql)
    and the actual privilege change (apply) were ungated; only acl_list
    was protected. This allowed real GRANTs to be generated and applied.
  * Schema Diff: initialize, servers, get_server, connect_server,
    connect_database, databases, schemas, ddl_compare and the
    compare_database/compare_schema socket handlers were ungated; only
    panel was protected.
  * ERD: initialize, prequisite, sql (table DDL generation) and the
    tables socket handler were ungated; only panel was protected.
  * PSQL: the panel route and the entire /pty Socket.IO namespace
    (start_process, socket_input, socket_set_role, resize) had no tool
    permission check at all, so a denied user could still obtain an
    interactive psql session.
  * Debugger: the directly addressable get_arguments/set_arguments/
    clear_arguments routes were ungated.

Apply permissions_required to the HTTP routes and socket_permissions_required
to the Socket.IO handlers so the tool permission is enforced consistently
across each tool's surface. The permission check is the outermost
decorator, so it runs before any connection or transaction lookup.
2026-07-25 00:55:45 +05:30
Dave Page 461c3afba9 Add socket_permissions_required decorator for Socket.IO RBAC
Flask-Security's permissions_required only guards HTTP routes; pgAdmin's
Socket.IO event handlers had no permission-aware equivalent and relied on
socket_login_required, which checks authentication but not the tool
permission. Add socket_permissions_required as the socket counterpart of
permissions_required so that event handlers can enforce the same
tool-level RBAC as the routes.

It reads the user's permissions via has_permission() rather than
flask_principal's Permission().can(), so it does not depend on the
principal identity having been loaded onto the socket request context,
and it honours pgAdmin's Administrator bypass. On failure it disconnects
and raises ConnectionRefusedError, mirroring socket_login_required.
2026-07-25 00:55:45 +05:30
Ashesh Vashi 54ff7b4dce Updated message catalogs for v9.17 2026-07-25 00:54:55 +05:30
Ashesh Vashi 5d39a52a73 docs: add release notes for v9.17 2026-07-25 00:21:27 +05:30
Niv Greenstein 108797d48a feat: add custom tile provider settings for Geometry Viewer (#10142)
Geometry Viewer's base-layer choices were hardcoded (Empty, Street,
Topography, Gray Style, Light Color/Dark Matter), with no way to
point at a private/internal tile server or a provider not in that
built-in list.

Add a geometry_viewer preferences category (custom_tile_url, _name,
_crs, _attribution, _max_zoom) under sqleditor. When a valid custom
tile provider URL is configured (must be http(s) and contain {x},
{y}, {z}), it's used as the default base layer; built-in tiled
layers are hidden unless the custom provider's CRS is also
EPSG:3857 (Web Mercator), since they can't be reprojected together.
Non-mercator custom providers (e.g. EPSG:4326) only apply to SRID
4326 geometry data; other SRIDs still render on the existing blank
Cartesian plane.

Custom name/attribution are DOMPurify-sanitized before being passed
to Leaflet (both are innerHTML sinks). URL is not sanitized but is
gated by the http(s):// prefix check, so javascript:/data: URLs are
rejected; it's consumed as a tile-image src template, not an HTML
sink.

Adds GeometryViewerUtils.spec.js covering URL validation, defaults,
sanitization, CRS resolution/fallback, and base-layer composition.
2026-07-24 22:17:51 +05:30
Ashesh Vashi 7339eb8dcc fix: report the correct error message on trigger node.sql failure (#10197)
The failure path for the second query (node.sql, resolving the
trigger function's node data) reported errormsg=rset — the result
dict from the first, already-successful get_function_oid.sql query
— instead of res, the actual error from the failed query. Any
failure here (permissions, catalog access) surfaced a stale,
misleading payload instead of the real cause.
2026-07-24 21:59:06 +05:30
Ashesh Vashi 34108aa50f fix: remove unused sqlalchemy exists import in server_access.py (#10196)
'exists' was imported but never called; the .exists() at line 165 is
the query-builder method on a Query instance, not this function.
2026-07-24 21:58:00 +05:30
lkmatsumura 1e1240038a refactor(server_groups): centralize shared-group visibility and access logic (#10077)
Replace the per-server ServerGroupModule.has_shared_server() loop and
scattered ad hoc shared-server checks with two centralized helpers,
get_server_groups_for_user() and get_server_groups_for_user_query(),
in server_access.py — a single SQL EXISTS query instead of iterating
servers per group. get_server_group() reuses the same shared-access
logic, and returned groups now carry is_shared_group metadata so the
browser tree can render the correct icon and derive edit/delete
permissions consistently.

update() now resolves the target group via get_server_group(gid,
hide_shared=True), so a non-owner can no longer rename another
user's shared group; delete() already blocked deleting shared or
first-user groups and now reports clearer errors for both cases.
Desktop mode's non-SERVER_MODE query path uses
literal(0).label('is_shared_group') (a bare int has no .label()).

Adds test_sg_permissions.py covering owned/shared/desktop group
visibility and access.
2026-07-24 19:11:27 +05:30
Hari Prasad 966a8ae99a docs: add basic starter contributing guide (#10093)
* docs: add basic starter contributing guide

Add a CONTRIBUTING.md file to document the contribution workflow for pgAdmin 4.

Includes:
- Getting started instructions
- Issue reporting guidelines
- Development workflow and coding practices
- Pull request requirements
- Development environment setup
- Database migration guidance
- Security reporting process
- Community support resources

This provides a single reference for new and existing contributors.

* add language tag on the opening fence of the Windows command block

* Add architecture overview, define $PGADMIN4_SRC, and link GitHub URLs

* docs: update contribution guidance

Clarify contributor workflow, fix the runtime link, and add references to existing contributor docs.
2026-07-24 19:05:11 +05:30
Dave Page 75dd304d25 fix(cloud): stop googleapiclient pulling in the system oauth2client (#10110) (#10112)
On packaged Linux installs the bundled venv is created with
--system-site-packages (issue #7173) so it can reach system packages such
as dbus-python. That also exposes the deprecated system oauth2client and
whatever pyOpenSSL ships alongside it. googleapiclient imports oauth2client
optionally, and on Ubuntu 24.04 that drags in a pyOpenSSL too old for our
bundled cryptography, aborting startup at blueprint registration with
"AttributeError: module 'lib' has no attribute 'GEN_EMAIL'".

pgAdmin only ever authenticates to Google via google-auth and
google-auth-oauthlib, so park a None sentinel under
sys.modules['oauth2client'] before importing googleapiclient in both the
web module and the standalone pgacloud provider. The optional import then
fails cleanly and googleapiclient falls back to google-auth.

Closes #10110
2026-07-24 19:02:29 +05:30
n0099 67755809a6 zh_Hans_CN translate: truncate 截断 -> 清空 (#10138)
this also sync with phpmyadmin: https://github.com/phpmyadmin/phpmyadmin/blob/c605fd63bc2e48b682e06d6d1041de544ddf861c/resources/po/zh_CN.po#L12154
2026-07-24 19:01:55 +05:30
Kobi Hikri e65edd4669 Pin sonarqube-scan-action to a full commit SHA (v8.2.1) (#10154) 2026-07-24 19:01:03 +05:30
Kundan 9bf83b4489 fix: return Response as-is and pass conn when expanding a trigger's function (#10177)
Expanding a Trigger node under a Table to view its function threw
'Response' object is not iterable, crashing the tree.

get_children_nodes() can legitimately return a Flask Response instead
of a list (e.g. gone("Could not find the specified trigger
function") when the trigger's function has no matching row in the
trigger-function node query, as happens for a genuine internal-
language function such as suppress_redundant_updates_trigger).
NodeView.children() / PGChildNodeView.children() unconditionally
sorted whatever get_children_nodes() returned, crashing on a
Response instead of passing it through.

Separately, TriggerView.get_children_nodes() rendered the trigger-
function node.sql template (which quotes fnid via qtLiteral(conn))
without passing conn into the template context. This call site
predates qtLiteral requiring a connection; after qtLiteral was
hardened in 658bb585d to raise ValueError instead of silently
degrading when conn is missing, this broke node.sql rendering for
every trigger, not just the internal-language case.

Fix both: check isinstance(children, flask.Response) in children()
before sorting/iterating and return it as-is, and pass
conn=self.conn into the node.sql render call, matching every other
render_template call site in the file.

Fixes #10117
2026-07-24 19:00:31 +05:30
Regina Obe 58e5fc7aed Fix for #10187 (#10188)
Extension UI gives error with PG 19
column reference "comment" is ambiguous LINE 5: e.comment

Issue caused because in pg19 :

SELECT * FROM pg_catalog.pg_available_extensions();

now outputs an additional column `location`
after default_version and comment is now 4th column
2026-07-24 18:38:40 +05:30
Ashesh Vashi 577a518fcb fix: scan all Mach-O binaries for bundle linkage, not just .so/.dylib (#10189)
_verify_bundle_linkage's host-library scan only matched files by
.so/.dylib name suffix, so a shipped executable (Contents/MacOS/*)
or a Python.framework payload with a host-linked dependency but no
matching suffix could slip through unchecked — the same class of
bug #10135 was added to catch.

Detect Mach-O executables/libraries by content via `file` instead
(same pattern the codesign step in this file already uses),
regardless of extension or executable permission bits, so readable
non-executable dylibs are covered too.
2026-07-24 18:34:03 +05:30
Dave Page 1884356245 fix: fail the macOS build if any bundled library links outside the bundle (#10135)
9.16 failed to start on Intel Macs (#10123): cryptography had no
prebuilt Intel macOS wheel, pip compiled it from source, and its
openssl-sys build linked the builder's Homebrew OpenSSL into
_rust.abi3.so instead of the bundled one. _fixup_imports deliberately
skips _rust.abi3.so, so the dangling reference shipped unnoticed and
the app died on startup for anyone without that external dylib.

Add _verify_bundle_linkage, run after the bundle is assembled and
relocated but before code-signing: walks every .so/.dylib and fails
the build if any install-name points at a build-host prefix
(/usr/local, /opt/homebrew, /opt/local, $SLAVE_HOME). OS libraries
and @loader_path/@rpath/@executable_path references pass untouched.
Turns this class of bug into a build-time failure instead of a
runtime one.
2026-07-24 18:22:08 +05:30
Kundan b85fb6c650 fix: use pg_depend ownership instead of name guessing for SERIAL detection (#10167)
Column-owns-sequence was detected by guessing the sequence name as
<table>_<col>_seq and string-matching it in nextval(...). Renaming
a table/column broke the guess (false negative, #10100); an
unrelated sequence with the same guessed name broke it the other
way (false positive, #10101).

Check col.get('seqrelid') (real pg_depend ownership) instead, and
exclude identity columns. SERIAL is now emitted iff a genuine
ownership dependency exists, independent of naming.

Fixes #10100, #10101
2026-07-24 18:15:20 +05:30
Kundan 5ff4f694fd fix: Schema Diff filter chip toggle broadcasting stale empty selection (#10172)
selectFilterOption() computed newOptions inside the functional
setSelectedFilters(prev => {...}) updater, then used that same
variable to dispatch TRIGGER_CHANGE_FILTER synchronously. Since the
updater runs lazily on next render, every chip toggle broadcast an
empty filter, so the tree always showed "No difference found" until
a full re-Compare.

Compute the new selection synchronously from current state and use
it for both the state update and the dispatched event.

Fixes #10102
2026-07-24 18:14:22 +05:30
hiteshjambhale f9935b3da5 fix: honor selected EOL sequence when copying query text to clipboard (#10160)
getSelectionFromState() joined ranges with the chosen EOL but sliced
each range via state.sliceDoc(), which always uses the document's
own \n regardless of the LF/CRLF preference. Introduced in #8691
when the copy path switched from doc.sliceString(..., lineSep) to
sliceDoc(...) to fix multi-cursor copying.

Slice with state.doc.sliceString(range.from, range.to, lineSep) so
the chosen EOL applies within a range too; the #8691 multi-cursor
join behavior is unchanged.

Fixes #10158
2026-07-24 18:13:27 +05:30
Dave Page cec1b38b2f fix: warn when OAuth2 provider settings are misplaced at the top level of config (#10113)
OAuth2 provider settings are only read from the OAUTH2_CONFIG list;
bare top-level keys like OAUTH2_CLIENT_ID or
OAUTH2_SSL_CERT_VERIFICATION are silently ignored. This is an easy
trap in container deployments where every other option is set via
an individual PGADMIN_CONFIG_<KEY> env var.

Add warn_on_misplaced_oauth2_config(), called once at OAuth2 module
startup, that logs when per-provider OAUTH2_* keys exist at the top
level with no provider configured in OAUTH2_CONFIG. No behavior
change for correctly configured or unconfigured deployments.
Documents the supported container approach in oauth2.rst.

Closes #10053
2026-07-24 18:12:22 +05:30
Kundan 6f8be955a1 fix: render disconnect tree label via className, not embedded HTML (#10169)
Disconnect handlers in server.js/database.js built the transitional
tree-node label as an HTML string and passed it to setLabel(), which
writes via label.textContent — correctly escaping markup, so the
literal "<span class='text-muted'>[Disconnecting...]</span>" text
was shown instead of a greyed-out label.

Pass plain text plus a className: 'text-muted' instead; setLabel()
applies the className to the label span (default file-name class
otherwise), so muted styling comes from CSS, not injected markup.

Fixes #10106
2026-07-24 18:11:22 +05:30
Kundan 49e955a855 feat: add preference for default View Data row limit (#10170)
The unqualified "View Data" action always ran with no LIMIT, unlike
"First/Last 100 Rows" which hardcode limit=100 — unusable on large
tables with no way to cap it.

Add view_data_default_row_limit preference (int, default 0 =
unlimited) under sqleditor Options. GridCommand.__init__ applies it
as self.limit when cmd_type == VIEW_ALL_ROWS and the value is
positive; objectquery.sql already renders LIMIT only when > 0, so
no template changes needed. First/Last 100 Rows untouched.

Closes #10104
2026-07-24 18:10:13 +05:30
Kundan 6ad6024bca fix: detect a selected-but-unusable OS keyring and fall back gracefully (#10168)
evaluate_and_patch_config() only disabled USE_OS_SECRET_STORAGE when
keyring.get_keyring().name == 'fail Keyring'. On Debian 13 over RDP, a
real backend (e.g. SecretService) is selected because no D-Bus/GNOME
Keyring session is running, so the name check passes even though every
actual keyring call fails — leaving the crypt key unset and every
operation raising a bare CryptKeyMissing.

Probe the selected backend with a harmless read of a never-existing
entry after selection. A healthy backend returns None without
prompting; an unusable one raises, and we disable
USE_OS_SECRET_STORAGE so the app falls back to the master-password /
in-app crypt key mechanism instead of failing outright.

Fixes #10107
2026-07-24 18:08:27 +05:30
Kundan aeef9c2d80 fix: ALT+F5 not falling back to statement under cursor (#10173)
triggerExecution() only fell back to getQueryAt(cursor) when
getSelection() was falsy. Since #7293/#8691 (non-continuous
highlighted-block support), getSelection() flattens and joins all
ranges, so multiple empty cursor ranges can produce a truthy but
whitespace-only string (e.g. "\n"), skipping the fallback entirely —
ALT+F5 silently tried to execute whitespace. Same stale check existed
in checkUnderlineQueryCursorWarning(). Also fixes a latent
state.selection.head reference (EditorSelection has no such
property; correct field is state.selection.main.head).

Treat a whitespace-only selection as empty before the fallback check
in both functions. Real highlighted selections always contain
non-whitespace, so #7293/#9570 behavior is unaffected.

Fixes #10109
2026-07-24 18:07:26 +05:30
Ashesh Vashi 8812025a8a chore(deps): Bump patch/minor dependency bumps (#10176)
Consolidates 20 open dependabot PRs (JS + Python) into one bump, applied
directly rather than cherry-picked (PR branches were stale and would have
reverted unrelated fixes like the yarn packageManager pin). Adds further
same-major patch/minor bumps found by auditing beyond dependabot's own PR
list.

JS (web + runtime): axios, brace-expansion, form-data, undici, js-yaml,
dompurify, @babel/core, webpack, sharp, electron, eslint, react-checkbox-tree,
autoprefixer, eslint-plugin-jest, globals, jest, jest-environment-jsdom, svgo,
terser-webpack-plugin, typescript-eslint, webpack-bundle-analyzer,
@date-io/date-fns, @szhsin/react-menu, @tanstack/react-query, @types/react,
ajv, anti-trojan-source, ip-address, marked, moment-timezone, papaparse,
postcss, react, react-dom, react-draggable, react-timer-hook, sql-formatter,
zustand.

Python: certifi, selenium (version-gated: 4.45.0 requires Python >=3.10,
4.44.0 kept for <=3.9 to preserve Python 3.9 support).

Reverted / excluded, with reasons:
- azure-mgmt-resource 26.0.0: moved ResourceManagementClient from
  azure.mgmt.resource to azure.mgmt.resource.resources, breaking
  pgadmin/misc/cloud/azure/__init__.py at import time. Caught by the Python
  regression suite. Kept at 25.0.0.
- @simonwep/pickr 1.10.0: switched its build tool to tsup, which marks its
  UMD bundle as an ES module via Symbol.toStringTag instead of the
  __esModule flag Babel's interop helper checks for. Babel double-wraps the
  export, so `new Pickr(...)` resolves to a non-constructor and crashes
  every dialog that mounts a color picker -- reproduces only in the
  production/minified webpack build, not the dev bundle or Jest. Pinned to
  ~1.9.1 (tilde, not caret) so a future install can't silently float back to
  1.10.x.
- paramiko 3->5 (#9927): structurally blocked by sshtunnel 0.4.0 still
  referencing paramiko.DSSKey.
- @mui/material / @mui/x-date-pickers 7/8->9 (#10091, #10092): known
  UI-breaking, needs the accompanying component fixes tracked on a separate
  branch, not a bare version bump.
- react-arborist, @tanstack/react-virtual, react-frame-component: same-major
  bumps available but excluded -- core object-browser tree, already-fragile
  virtualization code, or tilde-pinned range respectively.
- A handful of JS packages hit Yarn 4.15's registry quarantine gate (blocks
  just-published versions); backed off to the next-older version instead of
  forcing through.

Added core-js as an explicit devDependency: it was never declared despite
webpack's Babel config (useBuiltIns: 'usage', corejs: 3) requiring it --
it only worked because @simonwep/pickr 1.9.1 happened to pull it in
transitively, which broke when pickr was briefly bumped.

Verified: eslint (web + runtime), full JS test suite (149/149 suites,
916/916 tests), webpack production build compiles clean, Python regression
suite (2388/2388, excl. Selenium), Selenium feature_tests (17/19 pass; the
2 failures trace to a local pldbgapi-extension gap, unrelated to any
bumped dependency).
2026-07-23 15:28:38 +05:30
Ashesh Vashi 1993109475 fix: pin Yarn version from packageManager field in remaining build scripts (#10175)
#10156 fixed CI build failures caused by Yarn 4.x fetching a newer patch
with different builtin compat hashes, breaking --immutable lockfile
validation, but only patched pkg/linux/build-functions.sh and the GHA
workflows. Make.bat, pkg/mac/build-functions.sh, pkg/pip/build.sh, and
Dockerfile still hardcoded 'yarn set version 4', which is what broke the
Windows Jenkins snapshot build (job 1669).
2026-07-23 13:06:29 +05:30
Ashesh Vashi b15c745dc1 fix: pin Yarn version from packageManager field in build scripts (#10156)
* fix: pin Yarn version from packageManager field in build scripts

Replace hardcoded 'yarn set version 4' in build-functions.sh with a
dynamic lookup from each workspace's package.json packageManager field.
Also syncs runtime yarn version to 4.15.0 to match web/package.json.

Fixes CI build failures caused by Yarn 4.x fetching a newer patch that
produces different builtin compat hashes, breaking --immutable lockfile
validation.
2026-07-16 11:48:36 +05:30
Luiz K Matsumura dfc4ef37d0 refactor: clarify ServerGroup relationships to Server and SharedServer (#10076)
The Server and SharedServer models each declared a relationship named
'servers' that actually pointed at the parent ServerGroup, with backrefs
('server'/'sharedserver') providing the collections. The naming was the
inverse of what it modelled and made the call sites read oddly.

Redefine the relationships in the natural direction using explicit
back_populates: ServerGroup gains 'servers' and 'sharedservers'
collections (carrying the existing delete-orphan cascade), and Server and
SharedServer each gain a 'servergroup' reference. The two call sites that
read the group name are updated from server.servers.name to
server.servergroup.name accordingly.

This is a naming/modelling cleanup with no functional or schema change, so
no migration is required.
2026-06-19 10:32:02 +01:00
somaz 7e76fb4eb0 Add Gateway API HTTPRoute support to the Helm chart (#10095)
Add an opt-in Gateway API HTTPRoute template to the Helm chart as an
alternative to the existing Ingress, addressing #9942. It is disabled by
default (httpRoute.enabled: false) so existing installs are unaffected.

The template mirrors the existing ingress.yaml conventions: the
pgadmin4.fullname backend, commonLabels/commonAnnotations propagation, and
a hostname that falls back to ingress.hostname when httpRoute.hostnames is
unset. parentRefs is required (a fail guard fires when it is empty),
apiVersion defaults to gateway.networking.k8s.io/v1, and the default rule
forwards "/" (PathPrefix) to the pgAdmin service. Custom hostnames and
rules can be supplied for full control. The new values are documented in
the chart README.
2026-06-19 09:41:54 +01:00
Jeremy Schneider 0c6740f146 feat: include authenticated user identity in HTTP access log (#9991)
Set an X-Remote-User response header containing the authenticated username
on every request when the LOG_AUTHENTICATED_USER config option is enabled
(disabled by default). This allows the HTTP access log to be configured to
include user identity via standard log format directives
(%({x-remote-user}o)s in gunicorn, %{X-Remote-User}o in Apache) without
requiring any changes to pgAdmin's session or authentication behaviour. The
default gunicorn access log format is updated to surface the header.

The username is sanitised to a header-safe value: it is transliterated to
Latin-1 (HTTP headers are Latin-1 only) and any non-printable characters,
including CR/LF, are stripped, so unusual usernames cannot cause a 500 on
every response.
2026-06-19 09:26:32 +01:00
Dave Page 888a053231 fix(mac): prune dangling symlinks before codesign so Gatekeeper accepts the bundle (#10108)
A signed, notarised and stapled pgAdmin 4.app was rejected by Gatekeeper
with "invalid destination for symbolic link in bundle". Gatekeeper walks
every symlink in the bundle and rejects the whole app if any link does
not resolve to a real file inside it; notarisation does not catch this, so
a broken link slips through stapling and only surfaces as a Gatekeeper
failure on the end user's machine.

The embedded Python.framework ships such links: an arm64-only build still
carries a bin/python3-intel64 launcher symlink (whose target
_strip_architecture deletes when it removes the foreign-arch files), and
the bundled Tcl/Tk frameworks carry PrivateHeaders links pointing at a
Versions/Current that has none.

Add a _prune_dangling_symlinks step that removes every dangling symlink in
the bundle after architecture stripping and before signing, then fails the
build if any remain, so this cannot slip past notarisation again.
2026-06-18 18:36:11 +05:30
Ashesh Vashi 3660d19535 fix: Move the 'Back to logic' link outside the <form/>.
Moving the URL from the form to avoid any unnecessary form validation.
Refs #10037
2026-06-17 15:07:48 +05:30
Ashesh Vashi 660ecf2113 fix(cli): register module preferences before set-prefs validation (#10105)
PR #10047 made `Preferences.save_cli()` validate against the registered
preference object, but `setup.py set-prefs` only enters `app_context()`
and never calls `run_before_app_start()`, leaving `Preferences.modules`
empty. Every CLI write then fell into the "Module 'X' is no longer in
use." path and was reported as "Invalid value provided".

Trigger registration explicitly (run_before_app_start enters both
app and test_request contexts internally, satisfying registration
callbacks that touch current_user) and propagate the typed error
message from save_cli so users see the actual reason a value was
rejected.
2026-06-17 14:49:53 +05:30
Ashesh Vashi 71c18fb4a7 docs: handle Vietnamese diacritics in LaTeX PDF build
The PDF docs build in the Check documentation builds workflow began
failing after the 9.16 release notes added "Mai Phạm Hiền" as the
reporter for CVE-2026-12049. pdflatex's default utf8 inputenc maps
Latin-1 and Latin-Extended-A but rejects the precomposed Vietnamese
code points U+1EA1 (a with dot below) and U+1EC1 (e with circumflex
and grave) with:

  ! LaTeX Error: Unicode character ạ (U+1EA1)
                 not set up for use with LaTeX.

Declare the two characters in the LaTeX preamble via
\DeclareUnicodeCharacter so they typeset correctly:

  ạ -> \d{a}        (a with combining dot below)
  ề -> \`{\^e}      (e with circumflex + grave)

Picked this over switching to xelatex because it is a single-file
change, keeps the existing pdflatex toolchain, and the CI workflow
already has the apt packages it needs (no .github/workflows change).

Verified by running make docs-pdf in an ubuntu:22.04 container that
mirrors check-doc-builds.yml (same apt packages, locale-gen
en_US.UTF-8, sphinx + sphinxcontrib-youtube): 682-page pgadmin4.pdf
produced cleanly, zero Unicode-character errors in the log.
2026-06-16 15:36:24 +05:30
Ashesh Vashi 9b071d7287 test(sqli-lint): groom allowlist for PG-14-default templates
Master commit 7b9b8cdd0 (#10054 / Issue #10050) rebased the
version-specific SQL templates so the new default targets PG 14, and
collapsed the 11_plus/, 12_plus/, 14_plus/ buckets into default/.
After merging that into cve-9.16, the SQL-string-literal lint
introduced for CVE-2026-12044 (658bb585d, 2ae0d3610) reported:

  4 NEW violations at the new default/ paths --
    subscriptions/sql/default/create.sql :: '{{ data.streaming}}'
    subscriptions/sql/default/update.sql :: '{{ data.streaming}}'
    grant_wizard/pg/default/sql/function.sql :: '{{ kind }}'
    grant_wizard/ppas/default/sql/function.sql :: '{{ kind }}'

  25 STALE entries pointing at the removed _plus/ paths --
    aggregates/sql/{11,12}_plus/create.sql (data.initial_val,
      data.moving_initial_val)
    tables/index_constraint/sql/11_plus/properties.sql
      (constraint_type)
    tables/sql/{11,12}_plus/properties.sql (pg_get_partkeydef)
    subscriptions/sql/14_plus/{create,update}.sql (data.sync,
      data.streaming)
    grant_wizard/{pg,ppas}/11_plus/sql/function.sql (func_type,
      icon, kind)
    search_objects/sql/{pg,ppas}/11_plus/search.sql,
      search_objects/sql/ppas/12_plus/search.sql (search_text,
      obj_type, LABELS_SCHEMACOL)

Each new entry carries the same justification as the corresponding
removed _plus/ entry (no semantic change to what is allowlisted; only
the path universe shrank). The four 'data.streaming' and 'kind'
additions are not new violations in the source-code sense -- the
expressions themselves were already allowlisted at the version-suffix
paths -- they only became visible at default/ paths after the rebase.

Verified: test_sql_string_literal_lint passes on PG 17 and PG 18
(33/33 each); pycodestyle clean.
2026-06-16 15:02:21 +05:30
Ashesh Vashi 846aa3718c Merge remote-tracking branch 'origin/master' into cve-9.16
# Conflicts:
#	docs/en_US/release_notes_9_16.rst
2026-06-16 11:47:25 +05:30
Ashesh Vashi 0c17c82ddc Updated version for release v9.16 2026-06-16 11:44:14 +05:30
dependabot[bot] 5fe93608de chore: Update cryptography to 49.0.0 requirement in /web/regression (#10087)
Updates the requirements on [cryptography](https://github.com/pyca/cryptography) to permit the latest version.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/48.0.0...49.0.0)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 49.0.0
  dependency-type: direct:production
...
2026-06-16 11:27:35 +05:30
dependabot[bot] 797927aee5 Javascript dependency: Bump axios from 1.17.0 to 1.18.0 in /runtime (#10088)
Bumps [axios](https://github.com/axios/axios) from 1.17.0 to 1.18.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.17.0...v1.18.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.18.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-16 11:25:13 +05:30
Dave Page e9363232a9 Allow any Font Awesome style for the OAuth2 button icon. #7641 (#10036)
The login page hardcoded the 'fab' (brands) Font Awesome style for the
OAuth2 button icon, so non-brand icons could not be used. Use the
configured OAUTH2_ICON as-is when it already specifies a style class
(e.g. 'fas fa-key'), and fall back to 'fab' when only an icon name is
given, preserving backward compatibility.
2026-06-16 11:24:20 +05:30
Dave Page 9c7bdd0765 Validate preference values set via the CLI. #7346 (#10047)
The CLI set-prefs path (save_pref -> Preferences.save_cli) wrote the raw
value to the configuration database without any type validation and
always reported success, unlike the GUI path which validates via
_Preference.set(). Route save_cli through the same set() validation
(set() now accepts an explicit user_id so it works outside a request
context), and make setup.py set-prefs check the result and report
preferences whose value was invalid.
2026-06-16 11:22:06 +05:30
Dave PageandAshesh Vashi 14ccd2619e Fix View/Edit Data crash on a stale/non-filter session transaction object (#9744) (#10071)
* Fix View/Edit Data crash on a stale/non-filter session transaction object

initialize_viewdata restores the filter and data-sorting from any command
object previously stored in session['gridData'] under the same trans_id. It
assumed that object was always a filter-capable (View/Edit Data) command and
accessed old_trans_obj._row_filter / ._data_sorting directly.

That assumption is wrong: the same trans_id may have been used by the Query
Tool (a QueryToolCommand, which does not inherit SQLFilter), or the session
may contain an incompatible object persisted by an older version after an
upgrade. In those cases the attribute access raised AttributeError, returning
a 500 from the endpoint and - in desktop mode, where this runs during startup
- preventing the application from loading at all.

Guard the restore with isinstance(old_trans_obj, SQLFilter) (short-circuited
before the did/obj_id checks) so a non-filter object is simply skipped. Add a
regression test that seeds a pickled QueryToolCommand under the trans_id and
asserts initialize/viewdata returns 200.

Closes #9744

* test: avoid hard-coded PK constraint name colliding across the suite

The regression test created its table with a fixed 'table_pk' primary key
constraint name, which collides with the same name used by TestViewData in
this package when the full suite runs against one database. That made the
CREATE TABLE fail in CI (table not found -> IndexError on the OID lookup).
Let PostgreSQL auto-name the primary key instead.

---------

Co-authored-by: Ashesh Vashi <ashesh.vashi@enterprisedb.com>
2026-06-16 11:18:53 +05:30
Domenico Sgarbossa a0c71a668e Updated Italian translation for v9.16. (#10094) 2026-06-16 11:13:17 +05:30
Ashesh Vashi db1ab34f76 Merge remote-tracking branch 'origin/master' into cve-9.16 2026-06-13 01:11:55 +05:30
Ashesh Vashi f02e9be500 docs(release-notes): backfill 9.16 entries from master commits
Add 14 release-note entries that were merged on master but not yet
captured in the 9.16 notes: 1 new feature (#2431), 6 housekeeping
(#9817, #9866, #9917, #9959, #10014, #10023) and 7 bug fixes (#9701,
#9782, #9933, #9952, #9985, #10013, #10030). Entries inserted in
numeric order within each section.

Also add an "Additional changes (no associated issue) -> Dependencies"
section mirroring the 9.15 format, listing net direct dep bumps
between REL-9_15 and HEAD across Python (requirements.txt,
tools/requirements.txt, web/regression/requirements.txt), web/
package.json, and runtime/package.json. Transitive yarn resolutions
and the setuptools pin (covered by bug fix #9829) are excluded.
2026-06-13 01:11:14 +05:30
Ashesh Vashi 4beb7e4670 Updated message catalogs for v9.16 2026-06-13 00:45:56 +05:30
Ashesh Vashi 8c4fb7f9f9 Merge remote-tracking branch 'origin/master' into cve-9.16
# Conflicts:
#	docs/en_US/release_notes_9_16.rst
2026-06-13 00:41:26 +05:30
dependabot[bot] 8c5805279d Javascript dependency: Bump shell-quote from 1.8.3 to 1.8.4 in /web (#10079) 2026-06-12 23:23:49 +05:30
Dave Page c33927f55b Add a "Back to login" link to the password pages. #9301 (#10037)
The Forgot Password and Reset Password pages had no way to navigate back
to the login page. Added a "Back to login" link (using the login URL
already used by the login form) to both pages.
2026-06-12 23:07:27 +05:30
Dave Page 94564e406b Fix double dot in generated font and image filenames. #9806 (#10032)
Webpack 5 asset modules include the leading dot in the [ext] token, so
the 'img/[name].[ext]' and 'fonts/[name].[ext]' templates produced
filenames with a double dot (e.g. Roboto-Bold..ttf). Use '[name][ext]'
so the emitted filenames are correct.
2026-06-12 23:05:02 +05:30
Dave Page 7b9b8cdd07 Rebase the version-specific SQL templates to target PostgreSQL 14 (#10054)
The base (default) SQL templates previously targeted PostgreSQL < 12.
Re-base them so the default target is 14 - the oldest supported server
version - by collapsing every version bucket <= 14 (11_plus, 12_plus,
13_plus, 14_plus and the old default) into a single `default`, keeping
per file the content a v14 server resolves today. Buckets for newer
versions (15_plus, 16_plus, 17_plus, 18_plus) are retained as overrides.

The transformation is behaviour-preserving for every server version >= 14:
template (and test-fixture) resolution is byte-identical before and after
for all supported versions, verified programmatically across every bucket
container and confirmed by the resql, ERD and Schema Diff suites against
PostgreSQL 18.

Also drop PostgreSQL/EDB Advanced Server 13 from the 9.16 supported-server
list and repoint the sqleditor explain_plan tests (which referenced the
removed 12_plus/13_plus buckets) at the new default template.

Closes #10050
2026-06-12 23:02:56 +05:30
0b4bb3ea3f Propagate column renames to FK and unique constraints in the table dialog (#9060) (#10046)
* Propagate column renames to FK and unique constraints. #9060

In the new-table dialog, the primary key already updated its column
references when a column was renamed, but foreign key and unique
constraint definitions did not, leaving them pointing at the old name.
Mirror the PK rename-propagation in the foreign_key and unique_constraint
depChange handlers (and add 'columns' to the unique constraint deps so it
fires on column changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address review feedback for column rename propagation (#9060)

- Remap unique constraint INCLUDE columns on rename. The INCLUDE list
  holds bare name strings (not {column} objects), so renaming an
  included column previously emitted stale DDL. Now mirrors the
  primary key INCLUDE handling.
- Add regression tests covering rename propagation in depChange for
  foreign_key and unique_constraint, including the unique constraint
  INCLUDE case.
- Correct the release note wording from "Create/Edit Table" to
  "Create Table"; the propagation only runs on the new-table path
  (state.oid === undefined).

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ashesh Vashi <ashesh.vashi@enterprisedb.com>
2026-06-12 23:01:28 +05:30
Dave Page 6de0f371b2 Pin setuptools<82 on Python 3.9 to keep pkg_resources. #9829 (#10045)
setuptools 82 stops shipping pkg_resources, which passlib (via
Flask-Security-Too on Python 3.9) imports, so a clean install on Python
3.9 failed with "No module named 'pkg_resources'". Python 3.9 is still a
supported target (RHEL/Rocky/AlmaLinux 8 and 9 build with system Python
3.9). Split the pin by Python version, mirroring the existing
Flask-Security-Too split.
2026-06-12 22:54:53 +05:30
Dave Page 0649bcdb96 Restore Alt+drag rectangular selection in the editor. #9864 #10029 (#10043)
The fix for #9570 (stop Alt+F5 showing a crosshair cursor) changed the
rectangular-selection eventFilter to require Alt+Ctrl and moved the
crosshair cue to Control. That broke the long-standing Alt+drag block
(column) selection and left an inconsistent Ctrl crosshair cue.

Restore the default rectangularSelection() (Alt+drag) and drop
crosshairCursor entirely - the crosshair-on-Alt was exactly the artifact
#9570 wanted gone, and CodeMirror's crosshair cannot be limited to an
active drag. This brings back block selection (#9864, #10029) while
keeping #9570's intent (no crosshair on the Alt+F5 shortcut).
2026-06-12 22:53:44 +05:30
Dave Page 2a34eda5bc Fix ERD delete crash on stale foreign key column. #8318 (#10042)
removeOneToManyLink looked up a column by the FK's stored local_column
name and read .attnum unconditionally. After a column rename the stored
name no longer matches, so _.find returned undefined and .attnum threw,
blocking deletion of the table/link. Use optional chaining so a stale FK
simply doesn't match the link being removed and deletion proceeds.
2026-06-12 22:50:50 +05:30
Dave Page ba60f0ae63 Apply server foreground colour to object counts (#9766) (#10041)
Apply muted server foreground colour to column type labels (#9766)

Issue #9766 asked for both the object counts and the column type text
to follow the server's custom foreground colour. The dynamic per-server
CSS rule only recoloured the file-name and children-count spans, leaving
the column type text (span.text-muted) at its default low-contrast
colour.

Recolour span.text-muted to a reduced-emphasis blend of the foreground
colour using color-mix, so the datatype still reads as de-emphasised
secondary text while following the server colour.
2026-06-12 22:49:46 +05:30
Dave Page 9939d335e0 Let clicks pass through the object breadcrumbs popup. #9128 (#10038)
The breadcrumbs popup is an absolutely-positioned, informational overlay
at the bottom-left of the object explorer, so it intercepted pointer
events and blocked clicks on the tree items beneath it. Set
pointer-events: none so clicks pass through to the tree.
2026-06-12 22:47:40 +05:30
Dave Page 50f95f0fcb Preserve jsonb number representation in the JSON editor (#9854) (#10074)
The Query Tool's JSON cell editor pretty-printed jsonb values by parsing
and re-stringifying them with json-bignumber. While that preserves big
integers, it normalizes decimals through a JS float, so trailing
fractional zeros are dropped (10.00 -> 10, 3.140 -> 3.14). Because the
reformatted text is what gets written back, opening an unrelated jsonb
document and saving it silently rewrote numbers it never edited - which
can break applications that rely on the canonical jsonb text.

Switch the editor to lossless-json, which preserves the exact numeric
representation (big integers and trailing zeros alike), and pass it to
the underlying vanilla-jsoneditor as its parser so the in-editor format
action and tree/table modes are lossless too. The lossless helpers are
centralized in a small json_utils module with unit tests.

Closes #9854
2026-06-12 22:46:13 +05:30
Dave Page cc8ce7e153 Fix blank Query Tool screen caused by malformed runtime locale (#10066)
The Query History panel formats entry dates/times with
Date.prototype.toLocaleDateString()/toLocaleTimeString(). On runtimes
whose default locale (derived from the OS/environment) is malformed,
these throw "RangeError: Incorrect locale information provided".

Because getDateFormatted()/getTimeFormatted() are called during render
(via getGroups/getGroupHeader/getDatePrefix), the uncaught exception
unmounts the whole SQL editor React tree, leaving the user with a blank
white screen and losing any unsaved query work.

Guard both helpers and fall back to a moment-based format (moment uses
its own locale data and does not depend on the broken Intl default) so
the editor keeps working instead of crashing.

Closes #7596
2026-06-12 22:22:18 +05:30
Ashesh Vashi b458e69528 Merge remote-tracking branch 'origin/master' into cve-9.16
# Conflicts:
#	docs/en_US/release_notes_9_16.rst
2026-06-12 22:14:56 +05:30
Dave Page f6961bce49 Gracefully handle a missing crypt key in the Query Tool connection endpoints. (#10065)
After a backend/pod restart the in-memory crypt key is gone, so
manager.connection() raises CryptKeyMissing. The new-connection
endpoints (_check_server_connection_status and get_new_connection_*)
swallowed it in a broad "except Exception", logged a full ERROR
traceback, and returned a generic error the client cannot recognise.
The standard recovery (a 503 CRYPTKEY_MISSING response that the client
uses to transparently re-establish the key and retry) therefore never
fired, leaving a spurious "Crypt key is missing" message in the Query
Tool and noisy tracebacks in the log.

Re-raise CryptKeyMissing (along with ConnectionLost /
SSHTunnelConnectionLost) before the generic handler, matching the
pattern already used by the query execution path, so these endpoints
emit the standard CRYPTKEY_MISSING response and the client recovers
gracefully.

Closes #10027
2026-06-12 22:09:03 +05:30
Dave Page 922f06c7d2 Strip foreign-architecture code from the macOS bundle (#10063) (#10064)
The macOS app is built for a single architecture (matching the build
machine, via ${ARCH}), but relocatable-python pulls the python.org
universal2 installer, so the entire Python.framework ships both arm64
and x86_64 slices. PostgreSQL-sourced dylibs may be universal too. The
foreign slice is dead weight that bloats the bundle and DMG.

Add a _strip_architecture step, run after _complete_bundle and before
code-signing (lipo invalidates signatures, so the existing sign passes
re-sign the thinned binaries). It removes the universal2 stragglers
(python*-intel64 launcher, config-*-darwin/python.o) and lipo-thins
every fat Mach-O in the bundle to the build arch, preserving file modes
and warning on anything lacking the target slice. Already single-arch
inputs (Electron and its helpers) are skipped.
2026-06-12 15:57:09 +05:30
Dave Page 255550e981 Fix Query Tool password re-prompt loop for unsaved passwords (#9091) (#10073)
When a tool (Query Tool, View/Edit Data, etc.) opens a connection for a
server whose password was not saved, it relies on the password cached on
the server manager. If that cached password is unavailable, the tool
prompts for it. The entered password was POSTed to the connect_server
endpoint, which short-circuited with "Server connected" whenever the
server's primary connection was already established -- silently discarding
the entered password. The tool's own connection therefore still had no
password and re-prompted immediately, producing an infinite prompt loop
in which the re-entered password appeared to be rejected.

Cache the entered password on the server manager (encrypted) in that
short-circuit path so the tool's connection can reuse it. The password
overwrites any cached value, so a regenerated short-lived cloud auth
token (AWS RDS IAM / Azure Entra) takes effect immediately.
2026-06-12 15:51:01 +05:30
Dave Page 5a868bde90 Warn before opening a very large JSON value in the data grid (#9868) (#10075)
Opening a huge JSON/JSONB cell in the Query Tool's cell editor parses,
pretty-prints and renders the entire document on the main thread. For
pathologically large values (e.g. a jsonb object with 100k keys) this
blocks the UI thread and pgAdmin becomes completely unresponsive, with
no chance for the user to back out.

Guard the JSON editor: when the raw cell value exceeds a size threshold,
render nothing until the user confirms via a warning dialog. If they
cancel, the editor is closed without doing the expensive work. Small
values are unaffected and open immediately as before. The editor already
uses commitOnOutsideClick: false, so the confirm dialog does not dismiss
the editor.

The size-threshold logic is a small, separately tested helper.

Closes #9868
2026-06-12 15:41:35 +05:30
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
Ashesh Vashi 4751795087 docs: clarify 9.16 release-notes entries for #10068 and #10078
Tighten the release-notes prose for the two security entries whose
original one-liner didn't carry the impact framing settled during
Dave Page's 2026-06-11 CVSS review.

#10068 (CVE-2026-12048): re-lead with the critical-severity chain.
Default pgAdmin Content-Security-Policy allows inline script and an
iframe srcdoc inherits the embedding origin, so attacker JavaScript
ran same-origin to the victim's authenticated pgAdmin session and
could read every saved server connection credential and issue
arbitrary SQL against every server the victim was connected to.
The previous wording described the sinks but not the impact.

#10078 (CVE-2026-12044): re-lead with the stored
pgstattuple/pgstatindex sink (low-privilege user names a table or
index foo'bar, superuser viewer triggers SQL under the superuser
role). That sub-defect is what earns the score; the description-
field self-injection is bundled because the fix is the same. The
previous wording led with the self-injection only.

Both rewrites match the descriptions used in the CVE JSON records
submitted to the PostgreSQL CNA.
2026-06-12 09:49:44 +05:30
Ashesh Vashi cc4deed6eb docs: add CVE numbers and security fix entries to 9.16 release notes
Add CVE-2026-12044 through CVE-2026-12050 to the Bug fixes section and
set the release date to 2026-06-18.
2026-06-12 09:15:44 +05:30
Ashesh Vashi 04977b593e Merge remote-tracking branch 'origin/master' into cve-9.16 2026-06-12 08:58:28 +05:30
Dave Page 0ff9ecfe67 test(llm): pin sandbox-weakening commands and parser edge cases
Extend the read-only query validator's regression suite with 17 more
scenarios lifted from a v2 of the original #10022 patch that was
never committed (recovered from an uncommitted draft).

Five new accept scenarios cover parser corner cases:
  - leading whitespace
  - trailing semicolon followed by trailing whitespace
  - leading line comment
  - semicolon inside a string literal (must not split the statement)
  - (SELECT 1) UNION (SELECT 2), confirming the leading-keyword
    traversal walks past parentheses

Twelve new reject scenarios pin commands that directly attack the
BEGIN TRANSACTION READ ONLY wrapper rather than merely attempting a
generic write:
  - bare COMMIT / END / ROLLBACK / ABORT / BEGIN as single statements
    (previously tested only as the leading verb of a multi-statement
    payload)
  - START TRANSACTION (synonym for BEGIN)
  - SAVEPOINT (savepoint manipulation)
  - SET LOCAL transaction_read_only = off
  - SET SESSION default_transaction_read_only = off
  - DISCARD ALL (resets session state -- search_path, prepared
    statements, etc.)
  - multi-statement payloads where the leading statement is an
    allowed verb (SELECT or WITH) followed by ROLLBACK and a write
    -- closer to a real attack shape than the trailing-COMMIT PoC

If a future refactor of _ALLOWED_LEADING_KEYWORDS or the statement-
counting logic lets any of these slip through, the new scenarios
fail loudly. Module is now 77 passed / 0 failed / 0 skipped.
2026-06-11 10:11: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
Ashesh Vashi 2ae0d36109 fix(sqli): close remaining apostrophe-in-identifier sinks and tighten the lint
Layered follow-up to the COMMENT description SQLi fix. Closes three
related gaps the original patch did not cover.

1. Stats templates — apostrophe-in-identifier SQLi (10 sites).

The pgstattuple/pgstatindex call sites in the stats templates rendered
the target relation as an embedded single-quoted literal, e.g.

    pgstattuple('{{schema_name}}.{{table_name}}')
    pgstatindex('{{conn|qtIdent(schema)}}.{{conn|qtIdent(name)}}')

A user with CREATE privilege on a schema could plant a table or index
named `foo'bar` (PostgreSQL permits any character except NUL inside a
quoted identifier). Any viewer who then opened that object's stats
panel would render an unbalanced SQL literal. qtIdent does NOT escape
apostrophes — it escapes the embedded double quotes that delimit a
quoted identifier — so the outer single-quoted literal still broke
out. Same bug class as the COMMENT description vector, just gated on
DDL privilege instead of dialog access.

Fix replaces all 10 sites with the canonical regclass form:

    pgstattuple({{ tid }}::oid::regclass)
    pgstatindex({{ exid|cid|idx }}::oid::regclass)

The OID is already passed by each handler (tables/utils.py,
exclusion_constraint/__init__.py, index_constraint/__init__.py,
indexes/__init__.py, views/__init__.py). The cast eliminates the
embedded string literal entirely — the bug class can no longer recur
through these sites.

Files:
  tables/sql/{default,16_plus}/stats.sql
  views/templates/mviews/{pg,ppas}/default/sql/stats.sql
  tables/templates/exclusion_constraint/sql/{default,16_plus}/stats.sql
  tables/templates/index_constraint/sql/{default,16_plus}/stats.sql
  tables/templates/indexes/sql/{default,16_plus}/stats.sql

2. Lint regex — embedded-Jinja blind spot.

The original lint regex `'\{\{[^}]+\}\}'` only matched literals where
the Jinja interpolation filled the entire single-quoted body. It
missed the embedded form `'foo{{ x }}bar'` — exactly the shape used
by the stats templates above and by `'%{{ search_text }}%'` in
search_objects.

The broadened regex `'[^'\n{}]*(?:\{\{[^}]+\}\}[^'\n{}]*)+'` catches
both forms. The `[^'\n{}]` body constraint is what stops the regex
from spanning a Jinja control block `{%...%}` — without it the regex
would walk across long `{% if %}` chains and produce huge false-
positive matches that span unrelated literals.

Allowlist extended with one-line justifications for every additional
occurrence the broadened regex now surfaces (search_objects manual
escape, CATALOGS.LABELS_SCHEMACOL macro, gettext-translated catalog
labels, opcintype oidvector pairs, the pg_get_partkeydef CASE
expression where the interpolation is between two separate literals
rather than inside one).

3. qtLiteral — close the inner-except silent-failure hole.

The fail-fast for a missing `conn` was the right shape but the inner
`except Exception: print(value)` still silently returned the raw
input when `psycopg.sql.Literal(value).as_string(conn)` raised
(unadapted custom type, encoding error, etc.). Same SQL-injection
sink the outer fail-fast just plugged, gated on a different failure
mode. Removed the except so the exception propagates — callers can
now react explicitly instead of seeing an unescaped value flow into
their SQL.

Tests:
  test_stats_template_regclass_cast.py — 10 scenarios that render
    each stats template with an apostrophe-bearing identifier payload
    and assert (positive) the pgstat* call uses ::oid::regclass and
    (negative) no `pgstat*('...'` form recurs, and the apostrophe
    payload never appears inside a literal in the rendered SQL.
  test_qtliteral_no_silent_unescape.py — asserts qtLiteral raises on
    an unadaptable value rather than silently returning it.

42 tests / 0 failures across the touched packages.
2026-06-10 21:37:47 +05:30
Dave Page 658bb585d1 fix(sqli): HTML-escape description fields and harden qtLiteral
Fixes a SQL injection vulnerability where authenticated users could
break out of SQL string literals in COMMENT ON ... IS '<description>'
clauses by submitting an apostrophe-laden description through pgAdmin
dialogs. The original report covered domains; the patch expands the
fix to every site of the same pattern.

Three layers of defense:

1. Site fixes (16 places) — Replace '{{ x.description }}' with
   {{ x.description|qtLiteral(conn) }} across templates for domains,
   domain constraints, foreign tables, languages, event triggers,
   and the views OID-lookup query. Plumbs conn=self.conn through
   every render_template call that needed it. Also fixes a `{ % elif`
   Jinja typo in foreign-table schema diff that was preventing the
   elif branch from being reachable.

2. Driver hardening — qtLiteral (in utils/driver/psycopg3/__init__.py)
   used to silently return the raw unescaped value when conn was
   falsy. Now raises ValueError with a message pointing at the two
   fixes (render_template(..., conn=) or pass conn as the second
   argument). Surfaces this whole bug class loudly going forward,
   and immediately uncovered 8 latent plumbing bugs in
   schemas/__init__.py, schemas/functions/__init__.py,
   schemas/tables/utils.py, foreign_servers/__init__.py, and 7 sites
   in roles/__init__.py — all now fixed.

3. Regression tests (3 new files):
   - test_comment_description_sql_escaping.py — renders each
     previously-vulnerable template with an apostrophe-injection
     payload and asserts the escaped fragment is present (15
     scenarios).
   - test_sql_string_literal_lint.py — walks every *.sql template,
     flags every '{{ ... }}' single-quote-wrapped Jinja
     interpolation, and compares against a curated allowlist (75
     entries, each with a justification — OIDs, fixed enums,
     server-derived identifiers, SQL-comment headers, etc.). New
     occurrences fail the test until either qtLiteral is used or
     an allowlist entry is added.
   - test_qtliteral_requires_conn.py — unit test asserting the new
     fail-fast behavior.

Reported by Jasser Chebbi (j3seer).
2026-06-10 21:37:47 +05:30
Ashesh Vashi f81433ae2f Fix RCE via unauthenticated session deserialization in SQL Editor close/update routes.
The 'close' (DELETE /sqleditor/close/<trans_id>) and
'update_sqleditor_connection' (POST /sqleditor/initialize/sqleditor/
update_connection/...) endpoints were the only state-mutating SQL Editor
routes missing @pga_login_required. Both reach pickle.loads on
session['gridData'][trans_id]['command_obj'] via
close_sqleditor_session() and check_transaction_status() respectively.

Combined with a forged session file (precondition: SECRET_KEY leak +
write access to sessions/), this gave an unauthenticated attacker a
pickle deserialization sink and arbitrary code execution in the pgAdmin
process. Adding the login decorator forces is_authenticated/MFA checks
before the unsafe deserialization path is reached, matching the
convention used by every other endpoint in the module.

Includes a server-mode regression test that harvests a CSRF token from
GET /login (mirroring the attacker's path) and asserts both endpoints
reject the unauthenticated request before reaching the route body.
Self-skips in DESKTOP mode because pgAdmin's before_request hook
re-authenticates DESKTOP_USER on every request there, so no auth
decorator can be exercised in an unauthenticated state. Wired into the
existing server-mode CI workflow alongside the data-isolation tests.

Reported by Fernando Bortotti <fernando.bortotti@bsd.com.br>.
2026-06-10 20:01:44 +05:30
Ashesh Vashi 60d149864b fix(cloud): HTML-escape SDK error text across cloud module endpoints
Promote the post-connection-SQL sanitiser to a generic helper and apply
it to every cloud-module endpoint that propagates AWS / Azure / Google
SDK exception text into a JSON response field. Closes the RDS
HTML-injection vector reported against /rds/verify_credentials/ and
sweeps the same pattern across the verify-credentials, deploy, regions,
and update-server paths so no remaining cloud endpoint embeds raw
SDK / OS exception text into the response.

Sanitiser
  - Move web/pgadmin/utils/driver/psycopg3/text_sanitize.py to
    web/pgadmin/utils/text_sanitize.py.
  - Rename sanitize_driver_message -> sanitize_external_text; the
    function now describes its real role (HTML-escape text from any
    external/untrusted source — driver, cloud SDK, OS process).
  - Move tests to web/pgadmin/utils/tests/test_text_sanitize.py.
  - Update the single existing importer in psycopg3/connection.py.

Backend wrap sites
  - misc/cloud/rds/__init__.py — verify_credentials info= (the
    reported case: AWS STS IncompleteSignature echoes access_key
    verbatim into the exception string) and get_regions errormsg=
    (boto3 Session().get_available_regions exception).
  - misc/cloud/azure/__init__.py — verify_credentials and
    check_cluster_name_availability errormsg= (3 sites).
  - misc/cloud/google/__init__.py — verify_credentials
    (PermissionError + generic Exception path-resolution branches and
    the get_auth_url error), verification_ack, projects, regions,
    instance_types, database_versions — 8 sites covering every path
    that propagates SDK / file-resolution exception text.
  - misc/cloud/__init__.py — central /deploy endpoint (errormsg=resp
    where resp is str(e) bubbled up from deploy_on_rds /
    deploy_on_azure / deploy_on_google) and update_cloud_server
    (errormsg=server where server is the str(e) from db.session.commit
    failure).

Frontend
  - components/FormComponents.jsx — declare plainText in
    FormFooterMessage.propTypes (it already passes through via
    spread).
  - misc/cloud/static/js/CloudWizard.jsx — add plainText to the three
    FormFooterMessage sites. INFO messages there are gettext literals,
    so plain-text rendering is uniformly safe; the ERROR path now
    follows the SafeMessage contract for backend-derived strings.

Tests
  - rds/tests/test_rds_verify_credentials_xss.py — wiring test that
    patches RDS.validate_credentials to return an AWS-shaped error
    embedding an <iframe> payload, asserts the response info field is
    entity-encoded and contains no raw markup. Includes a happy-path
    guard against the sanitiser altering 'verified'.
  - azure/tests/test_azure_verify_credentials_xss.py — same pattern,
    Azure.__init__ touches current_user so the Azure class is patched
    to a stand-in that yields the HTML-bearing validation error.
  - google/tests/test_google_verify_credentials_xss.py — covers the
    PermissionError and generic-Exception branches of the
    client_secret_file resolution path (most directly user-influenced).
  - test_post_connection_sql_xss.py — docstring + comment updates for
    the renamed function.

Frontend rendering is already DOMPurify-guarded at the NotifierMessage
sink, so the reported PoC payload would not execute even without
these backend changes; the fixes here are the defence-in-depth layer
the report's section-5 "Suggested directions" calls out, ensuring API
consumers other than the browser (audit logs, third-party clients)
never see raw markup.

876 / 876 JS tests + ESLint clean. Cloud / utils / server-groups
Python test packages pass (24 + 60 + 102, with pre-existing skips
unchanged).
2026-06-10 20:01:44 +05:30
Dave Page 9e370d3cb6 fix(xss): comprehensive XSS hardening across notification and Explain flows
Mitigates a stored XSS vector where a malicious PostgreSQL server's
ErrorResponse — or any backend-derived string — could inject HTML into
pgAdmin's DOM via the notifier, Explain visualiser, or form-input error
paths. Combines three complementary layers of defence.

Frontend — DOMPurify at every HTMLReactParse sink:
  - NotifierMessage (toasts) and FormFooterMessage
  - FormInput help / error messages, FormNote
  - ModalProvider AlertContent + confirmDelete
  - ToolErrorView (tool open failure)
  - Explain/Analysis NodeText (plan extra-info renderer)
  - SQL editor confirm dialogs (transaction, promotion, close-running)
  - Dialogs/ConfirmSaveContent
  - PreferencesHelper modal alert
  - SelectThemes helper text
  No HTMLReactParse call site renders text unsanitised after this.

Frontend — plain-text contract for backend-derived strings:
  - New components/SafeMessage.jsx exports SafeMessage (plain text,
    preserves newlines via pre-wrap) and SafeHtmlMessage (sanitised
    HTML via DOMPurify + html-react-parser).
  - NotifierMessage and ModalProvider.AlertContent accept a `plainText`
    prop. When true the body renders via SafeMessage.
  - Notifier gains errorText / alertText / warningText / infoText /
    successText methods that plumb {plainText: true} through. These
    are the correct choice whenever the message may originate from a
    PostgreSQL server, driver, OS process, remote API, or any other
    untrusted channel.
  - pgRespErrorNotify and pgNotifier use alertText / errorText
    internally. The redundant _.escape and \\n -> <br/> substitution
    in pgNotifier are gone — the SafeMessage container's pre-wrap
    preserves newlines without further transformation.
  - FormInput.errorMessage and ToolErrorView render as plain text
    unconditionally; their inputs are always validation / driver
    errors.
  - ~50 callers across browser, tools, dashboard, debugger, misc, llm,
    preferences, schema diff, and the SQL editor are migrated to the
    *Text variants where they pass a backend-derived field
    (err.response.data.errormsg, parseApiError(error), res.errormsg,
    res.info, res.data.result, err.message, interpolated PG object
    names).
  - Browser auto-update notification rewritten to plain text — the URL
    is shown inline instead of being interpolated into an <a href>
    attribute. The gettext template-literal anti-pattern on that path
    is fixed too (URL is a separate %s argument).

Cleanup:
  - helpers/Notifier.jsx — remove the dead AlertContent function and
    its seven imports (Box, CloseIcon, DefaultButton, PrimaryButton,
    HTMLReactParser, CheckRoundedIcon). The function was defined but
    never instantiated; the real top-level alert path goes through
    ModalProvider.AlertContent. Eliminates a false-positive sink that
    recent audits cite — the cited line numbers reference a function
    that never runs.

Explain — escape Recheck Cond / Exact Heap Blocks:
  - nodeExplainTableData concatenates plan-node fields into HTML
    strings parsed by html-react-parser. Every sibling field (Filter,
    Index Cond, Hash Cond, Join Filter, Rows Removed by Filter, Hash
    Buckets/Batches/Peak Memory) wraps the value in _.escape();
    Recheck Cond and Exact Heap Blocks were the two outliers
    concatenating raw. An attacker who introduces a table with a
    crafted column name and triggers a Bitmap Heap Scan over it could
    inject HTML into Explain output that a victim later views in the
    visualiser. With the rest of the XSS fix this would otherwise be
    neutralised by DOMPurify at the sink — escaping at construction
    matches the sibling fields and provides defence in depth.

Backend — HTML-escape PG-returned text in post-connection-SQL:
  - New web/pgadmin/utils/driver/psycopg3/text_sanitize.py exports
    sanitize_driver_message. Strips C0 control characters
    (0x00-0x08, 0x0B, 0x0C, 0x0E-0x1F, 0x7F) — preserving TAB, LF,
    CR so multi-line error messages stay readable — then HTML-escapes
    via html.escape(..., quote=True).
  - execute_post_connection_sql logs the raw PG-returned text so the
    server log stays human-readable, and applies the escape only to
    the value crossing into the JSON response body, where downstream
    consumers may render it as HTML:
      <iframe srcdoc="..."> -> &lt;iframe srcdoc=&quot;...&quot;&gt;
    Real Postgres errors (which never contain literal markup) round-
    trip unchanged for human readability. Protects third-party JSON
    consumers (audit logs, API clients, support tickets) that do not
    have the frontend's DOMPurify.

Tests
  - components/SafeMessage.spec.js — 25 payloads (iframe srcdoc, SVG
    onload, MathML href javascript:, embed/object data:, marquee
    onstart, template/noscript, CSS expression, etc.).
  - Notifier.spec.js — 9 cases (errorText / alertText plumbing,
    pgRespErrorNotify 410 vs non-410, pgNotifier raw errormsg pass-
    through, "Unknown error" fallback).
  - ToolErrorView.spec.js — 3 cases.
  - ModalProvider.spec.js — 4 cases.
  - FormComponents.spec.js extended with NotifierMessage plainText
    mode + FormInput errorMessage plain-text rendering.
  - Explain regression test renders Analysis with HTML-laden extraInfo
    and asserts no <iframe> / <script> reaches the DOM.
  - Python: test_sanitize_driver_message.py (21 sanitiser + 5
    execute_post_connection_sql wiring scenarios with a real Flask +
    flask-babel app context and a stand-in connection).
  - test_post_connection_sql_xss.py — end-to-end integration: register
    a server, set post_connection_sql to a query referencing a relation
    whose name contains an iframe payload, POST to the connect endpoint,
    and assert the response JSON errormsg is entity-encoded and never
    raw markup.
  - fake_pgadmin.js notifier mocks and enable_disable_triggers_spec.js
    spies updated for the new API.

875 / 875 JS tests pass; ESLint clean.
2026-06-10 20:01:44 +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] 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 8383c1220b chore(tests): unskip MFA validation-view test by completing dummy harness
The previous commit (2e14bd95d) made the MFA test package discoverable
and resilient but left one scenario --
'Validation view of a MFA method should return a HTML tags' -- as a
documented unittest.SkipTest. The skip reason was that
validate.html -> security/render_page.html -> base.html references
'current_app' and 'csrf_token()' in Jinja, and the dummy Flask app
built by test_create_dummy_app() does not expose either: pgAdmin's
real create_app() injects current_app via an @app.context_processor
(web/pgadmin/__init__.py:922) and Flask-WTF registers csrf_token() in
the Jinja env. The bare Flask(name, ...) used by the dummy app has
neither, so the GET-path render fails with
UndefinedError: 'current_app' is undefined.

Add the same two globals to the dummy app:

* A @app.context_processor returning {'current_app': current_app} so
  the Flask proxy is bound at render time, mirroring what
  create_app() does in production.
* A jinja_env.globals['csrf_token'] = lambda: 'dummy-csrf-token' that
  matches what Flask-WTF would otherwise install. Tests asserting on
  the rendered HTML can rely on the marker being present and stable.

With both globals available the template renders, /mfa/validate
returns a 200 HTML response, and the scenario goes from SkipTest to
a real assertion. The fix is intentionally scoped to the dummy harness
in tests/utils.py -- no production code change.

Result on this worktree: pgadmin.authenticate.mfa.tests now runs as
13 passed / 0 failed / 0 skipped (was 12/0/1). Full suite goes from
1818/0/444 to 1819/0/443.
2026-06-08 19:41:51 +05:30
Ashesh Vashi 2e14bd95dd chore(tests): make MFA test package discoverable and resilient
The MFA test directory was missing __init__.py, so
find_modules('pgadmin', False, True) in regression/runtests.py could
not walk into it. The test classes therefore never reached the
TestsGeneratorRegistry registry and the entire pgadmin.authenticate.mfa
test suite was silently absent from the default test run. The
regression for #10028 added in this branch was caught by that gap.

Adding the missing __init__.py exposes a second pre-existing problem:
TestMFATests.setUp called BaseTestGenerator.setUp, which posts to
/browser/server/connect/... and asserts a 200 response. The MFA
scenarios all run against a dummy Flask app (or pure mocks), so that
endpoint is not registered and the connect_server assertion fires
before any check_*() function runs. Skip the BaseTestGenerator setUp
since these scenarios deliberately do not need a real PostgreSQL
server.

Three further fixes that surface once the suite actually runs:

* mfa_enabled() and init_app() both short-circuit when SERVER_MODE
  is False. test_config.json defaults to DESKTOP mode, so every
  scenario in the suite was taking the disabled path. Force
  SERVER_MODE=True for the duration of the TestMFATests class and
  restore the previous value in tearDownClass.

* check_validation_view_content patched flask.current_app to capture
  logger.exception() calls. Those only fire on the POST path of
  /mfa/validate; the test exercises only the GET path, so the patch
  was dead code -- and additionally turned flask.current_app into a
  MagicMock, which broke Jinja's lookup of current_app in
  validate.html. Drop the patch and the now-unused ValidationException
  import.

* check_validation_view_content still cannot render validate.html
  against the bare dummy Flask app because the template references
  current_app.config and extends Flask-Security's
  security/render_page.html, neither of which the dummy app provides.
  Skip the scenario with a clear unittest.SkipTest reason rather than
  paper over with a brittle patch; rebuilding the dummy harness to
  expose those globals is its own follow-up.

Result on this worktree: pgadmin.authenticate.mfa.tests now runs as
12 passed / 0 failed / 1 skipped (the dummy-app/template gap),
up from a silent 0/0/0. Full suite goes from 1806/0/443 to 1818/0/444.
2026-06-08 19:25:48 +05:30
Dave Page fff6a48185 fix(mfa): reject external 'next' targets in MFA flow to close open redirect
The MFA flow honoured the user-supplied "next" query/form parameter
without checking that it pointed back inside pgAdmin, so an attacker
who got a logged-in user to click /mfa/validate?next=<external> could
land them on an attacker-controlled host straight out of the auth
flow -- a trusted-domain redirect ideal for credential-phishing
follow-on.

Add a single _is_safe_redirect_url helper that allows only same-origin
http(s) targets (relative paths included) and rejects:

* external hosts in absolute and protocol-relative ("//host") form;
* non-http schemes a browser will still follow (javascript:, data:,
  mailto:);
* userinfo tricks ("http://localhost@attacker.example/");
* backslash variants ("/\\host", "\\\\host") that some browsers
  normalize to forward slashes, enabling protocol-relative bypasses;
* empty / missing targets.

Gate every redirect that consumes the user-supplied next value through
the helper -- the GET and POST branches of /mfa/validate and the POST
branch of /mfa/register -- falling back to the internal browser index
when the target is anything other than safe. The registration POST
keeps its existing 'internal' sentinel ("rendered from the in-app
dialog") since that string is matched exactly and is not a URL.

Ship the validator with a dedicated table-driven unit test covering
each accept/reject category and an integration test that POCs the
attacker payload from the report and asserts the response redirects
to the internal index instead of attacker.example.

Reported by: Mai Phạm Hiền <mai.phamhien171@gmail.com>
Reviewed by: Kundan Sable <kundan.sable@enterprisedb.com>
2026-06-08 18:55:51 +05:30
Ashesh Vashi 3b1a6ce481 fix(server): schema-qualify remaining pg_catalog calls in ServerNode
The SQL injection fix for create_restore_point now calls
pg_catalog.pg_create_restore_point so that a non-default search_path
on the connection cannot redirect the call to a shadow definition.
Apply the same hardening to the other pg_* calls in the same file
that were still unqualified: pg_reload_conf in reload_configuration,
and pg_xlog_replay_pause / pg_wal_replay_pause / pg_xlog_replay_resume
/ pg_wal_replay_resume in wal_replay.

These were not exploitable on their own -- the SQL string is static
and not user-derived -- but resolving them via pg_catalog removes any
dependency on the connection's search_path being trustworthy.
2026-06-08 18:42:40 +05:30
Dave Page 3379c39865 fix(server): parametrise named restore point query to prevent SQL injection
ServerNode.create_restore_point interpolated the user-supplied "value"
field directly into a SQL string with str.format(), so an authenticated
pgAdmin user could inject additional statements through that endpoint.
The injection ran with the user's own database role -- so it did not
cross a privilege boundary and granted no capability the role does not
already have via the Query Tool -- but it bypassed the documented SQL-
execution path and any application-layer controls a deployment may
have built around the Query Tool.

Pass the name as a bound parameter, and qualify the function with
pg_catalog so a non-default search_path on the connection cannot
redirect the call to a shadow definition.

Add a regression test that mocks the driver and asserts the call
arrives as ("SELECT pg_catalog.pg_create_restore_point(%s);", (name,))
with no payload spliced into the SQL text, plus defence-in-depth
assertions that the SQL string contains neither pg_sleep nor the PoC's
closing-quote signature, so a future refactor that reintroduces string
formatting is caught.

Reported by: Geo <cve@sageby.com>
Reviewed by: Kundan Sable <kundan.sable@enterprisedb.com>
2026-06-08 18:42:40 +05:30
Dave Page bf47924444 fix(llm): reject multi-statement and non-read-only AI assistant queries
The AI Assistant's execute_sql_query tool runs LLM-generated SQL inside
a BEGIN TRANSACTION READ ONLY wrapper. However, the LLM-supplied query
was sent to psycopg as-is, so a multi-statement payload beginning with
COMMIT, END, ROLLBACK, or ABORT terminated the read-only transaction
and ran subsequent statements in autocommit mode. With ordinary write
privileges this allowed unauthorised data modification; for a superuser
or pg_execute_server_program role this chained to remote code execution
on the database host via COPY ... TO PROGRAM.

Validate the LLM-supplied query before any connection work happens:

* The input must parse to exactly one non-empty/non-comment statement.
* The leading real token (after stripping leading whitespace, comments,
  and punctuation) must be one of SELECT, WITH, EXPLAIN, SHOW, VALUES,
  TABLE. Everything else -- DML, DDL, CALL, COPY, DO, SET/RESET, the
  transaction-control verbs, and the rest -- is rejected up front.

PostgreSQL's READ ONLY mode continues to enforce the remaining cases
(data-modifying CTEs, EXPLAIN ANALYZE on writes, volatile side effects)
at runtime, so the validator is the load-bearing check for multi-
statement / top-level escapes and READ ONLY is the backstop for the
rest.

Add a 60-scenario regression suite under
web/pgadmin/llm/tests/test_database_tool_security.py covering the
original PoC payloads (COMMIT/END/ROLLBACK/ABORT/SET/BEGIN), multi-
statement masked by comments, the full allow- and deny-list of leading
keywords, dollar-quoted literals containing semicolons, and degenerate
inputs (empty, whitespace-only, comment-only, quoted identifier).

Reported by: Isaac Chen <isaac9503@gmail.com>
Reviewed by: Kundan Sable <kundan.sable@enterprisedb.com>
2026-06-08 18:42:40 +05:30
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
Ashesh Vashi 25f0d853c8 chore(deps): bump cryptography 47.0.* -> 48.0.* (#9960)
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.
2026-05-20 18:57:44 +05:30
Murtuza Zabuawala 142a55b864 Feat: Added a option to colorize panel/tab header based on server color #2431 (#9799) 2026-05-20 18:56:02 +05:30
Ashesh Vashi 3b801fc6ba chore(deps): bump transitive lockfile resolutions surfaced by dependabot (#9955)
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.
2026-05-20 17:22:35 +05:30
Ashesh Vashi aad2dfd725 chore: Apply non-breaking dependency updates from open dependabot PRs (#9954)
Python:
- requirements.txt: google-auth-oauthlib 1.3.1 -> 1.4.0
  (#9929 / #9931), gated so Python 3.9 stays on 1.3.1 (1.4.0
  requires python_version >= 3.10). Mirrors the existing
  boto3 1.42.*/1.43.* split.
- tools/requirements.txt: requests >=2.33.1 -> >=2.34.2 on
  python_version > '3.9' (#9943 / #9944).
- web/regression/requirements.txt: selenium 4.43.0 -> 4.44.0
  (#9946). The selenium pin already requires Python >=3.10 in
  master, so the bump introduces no new 3.9 gap.

JavaScript (web/package.json, web/yarn.lock):
- postcss 8.5.12 -> 8.5.14 (#9874 / #9889)
- @tanstack/react-query 5.100.5 -> 5.100.9 (#9878)
- ip-address 10.1.0 -> 10.1.1 (#9918)
- packageManager pin yarn@4.14.0 -> yarn@4.15.0 and regenerate
  yarn.lock at lockfile __metadata.version 10. CI runs yarn
  4.15.0 with hardened mode on public PRs and refuses to migrate
  the lockfile from version 9 (yarn 4.14.x) to 10; master passes
  today only because hardened mode is PR-only.

Electron runtime (runtime/package.json, runtime/yarn.lock):
- axios 1.16.0 -> 1.16.1 (#9948)
- eslint 10.3.0 -> 10.4.0 (#9947)

Skipped (genuine breaking changes, deferred to follow-up PRs):
- @mui/material 7 -> 9 (#9843)
- @mui/x-date-pickers 8 -> 9 (#9888)
- cryptography 47.0.* -> 48.0.* (#9926 / #9932)
- paramiko 3.5.1 -> 5.0.0 (#9927 / #9930)
- electron 41.5.0 -> 42.1.0 (#9945)

Verified in an isolated worktree:

  - jest:        140/0/0 suites, 824/0/0 tests
  - eslint:      clean (web + runtime, both silent)
  - pycodestyle: 0 violations project-wide

Each version was cross-checked against the corresponding
dependabot PR diff via `gh pr diff`. Each Python bump was
cross-checked against PyPI's requires_python so Python 3.9
support stays intact.
2026-05-20 14:53:04 +05:30
Dave Page af888027a4 fix(docker): ship libpq-oauth-18.so and libcurl for PostgreSQL 18 OAuth (#9952)
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
2026-05-20 12:39:42 +05:30
Ashesh Vashi 86a8b165ab fix(test): mock does_utility_exist in maintenance accept-valid tests
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.
2026-05-06 15:03:18 +05:30
Ashesh Vashi bdbca57332 docs: note Python 3.9 boto3 split in v9.15 release notes
Annotate the boto3 1.42 -> 1.43 dependency line to reflect that 1.43
requires Python >=3.10, so 3.9 stays on the 1.42.* series.
2026-05-06 12:46:58 +05:30
Ashesh Vashi dda6d0e3a8 fix(deps): pin boto3 to 1.42.* on Python 3.9
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.*.
2026-05-06 12:43:59 +05:30
Ashesh Vashi 411d718fa7 docs: add v9.15 release notes to toctree
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.
2026-05-06 12:03:29 +05:30
Ashesh Vashi d17616eda8 docs: round out v9.15 release notes with stragglers
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.
2026-05-05 19:33:16 +05:30
Ashesh Vashi c6a6d46283 Updated version for release v9.15 2026-05-05 19:07:40 +05:30
Ashesh Vashi 4330a688aa chore: Apply non-breaking dependency updates from open dependabot PRs
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.
2026-05-05 18:47:55 +05:30
Ashesh Vashi 96e6528243 Merge remote-tracking branch 'origin/master' into cve-9.15 2026-05-05 16:22:33 +05:30
Ashesh Vashi ba9ff51aaa docs: assign CVE IDs to v9.15 release-notes placeholders
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.
2026-05-05 16:14:02 +05:30
Domenico Sgarbossa 4eb899c92c Updated Italian translation for v9.15 (#9906) 2026-05-05 16:07:08 +05:30
Ashesh Vashi 6469b69565 style: fix pre-existing pycodestyle violations across CVE-9.15 test files
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).
2026-05-05 15:03:00 +05:30
Ashesh Vashi 208541cc48 fix: ImportExportServersTestCase uses sys.executable + surfaces subprocess errors
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.
2026-05-05 15:03:00 +05:30
Ashesh Vashi 9923eefcab docs: clarify scope of MAX_LOGIN_ATTEMPTS and document brute-force protection for LDAP
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.
2026-05-05 15:03:00 +05:30
Ashesh Vashi 32d194691e docs: add release-notes line for #9904 lockout-bypass fix
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.
2026-05-05 15:03:00 +05:30
Ashesh Vashi d336c1e786 fix: enforce account lockout on Flask-Security's /login endpoint (#9904)
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>
2026-05-05 15:00:19 +05:30
Ashesh Vashi 7f230d05fa docs: add release notes for v9.15
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.
2026-05-02 12:59:54 +05:30
Ashesh Vashi fcb3db7f54 Merge remote-tracking branch 'origin/master' into cve-9.15 2026-05-02 12:12:20 +05:30
Ashesh Vashi 2f211dc04c Updated message catalogs for v9.15 2026-05-02 12:11:32 +05:30
Ashesh Vashi a11d289bd9 test: harden click_modal backdrop wait and open_query_tool stale-element retry
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.
2026-05-02 12:10:51 +05:30
Ashesh Vashi a50a553b0a chore: feature tests use sys.executable; sync yarn.lock to package.json
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).
2026-05-02 11:02:30 +05:30
Ashesh Vashi e7eaef0dc0 Merge remote-tracking branch 'origin/master' into cve-9.15 2026-05-02 11:00:24 +05:30
Ashesh Vashi 849c679fc6 Updated message catalog post merging few translations 2026-05-02 10:59:22 +05:30
rx500 f43c781e98 Update messages.po (es) (#9893)
Word translation corrected.
2026-05-02 10:56:59 +05:30
Daniel Nylander 119a99375d Update Swedish translation. (#9764)
- 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.
2026-05-02 10:53:37 +05:30
MichalBartos ff0fc9e7eb Fix translation for 'Refresh' in Czech (#9832) 2026-05-02 10:37:12 +05:30
Degit22 aec3ea117f Update Russian translation. (#9839) 2026-05-02 10:19:18 +05:30
Ashesh Vashi ddc371adf7 fix: bound user_info_server prompt loops + allow .local in deliverability test
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.
2026-05-02 01:52:29 +05:30
Ashesh Vashi 0fad04de85 fix: PSQL socket tests use authenticated tester; role-deps test skips on auth failure
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.
2026-05-02 01:39:00 +05:30
Ashesh Vashi 1f7194924f fix: harden 6 regression tests against environmental drift
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.
2026-05-02 01:29:01 +05:30
Ashesh Vashi d78a7cc3bf fix: ConnectionLocker session safety + batch_process tests need request context
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.
2026-05-02 01:10:33 +05:30
Ashesh Vashi 657529280e fix: guard session['auth_source_manager'] access in BatchProcess.start
processes.py:322 unconditionally indexed session['auth_source_manager']
to gate the KRB5CCNAME copy. When the Flask session does not have the
auth_source_manager key (test request contexts that bypass the login
flow, or sessions whose login hasn't populated this key yet), the
indexing raises KeyError, aborting BatchProcess.start() before it ever
spawns the subprocess. The error has no obvious traceback in the
runtests harness because it bubbles up through BackupCreateJob's mock
infrastructure - the test just records ERROR with no message.

Replace [...] with .get(...) chains so a missing auth_source_manager
yields None and the Kerberos branch is skipped, matching the original
intent.

Surfaced by BatchProcessTest "When backup server" / "When backup
globals" scenarios which mock current_user but leave session in its
default unauthenticated state. Same shape on master; the fix is a
one-line robustness improvement.
2026-05-02 00:54:48 +05:30
Ashesh Vashi d55ffe405b fix: Apply non-breaking dependency updates from open dependabot PRs
Python (requirements.txt, tools/, web/regression/):
- cryptography 46.0 -> 47.0 (move CFB8 import to decrepit module to
  silence the 47.0 deprecation warning and survive 49.0 removal)
- typer 0.24 -> 0.25 for python>3.9 (drop removed [all] extra)
- safety >=1.9.0 -> >=3.7.0 (CI audit tool)
- requests >=2.21.0 -> >=2.33.1
- testtools 2.8.7 -> 2.9.1
- pycodestyle >=2.5.0 -> >=2.14.0

JavaScript (web/package.json, web/yarn.lock):
- postcss 8.5.6 -> 8.5.12
- moment-timezone 0.6.0 -> 0.6.2
- @tanstack/react-query 5.90 -> 5.100.5

Skipped (genuine breaking changes): @mui/material 7->9 (#9843),
@mui/x-date-pickers 8->9 (#9888).
2026-05-02 00:18:47 +05:30
Ashesh Vashi 4eb184739a fix: replace 'e.message' with str(e) and add @with_object_filters to ServerNode.list
Two pre-existing master bugs surfaced by the data-isolation regression
tests:

1. 'e.message' on caught exceptions (13 sites). Exception.message was
   removed in Python 3 (it lived on BaseException in Py2). When the
   except handler runs, accessing e.message raises AttributeError,
   masking the original exception. Werkzeug HTTPException doesn't
   expose .message either - it uses .description / .__str__.

   Replace 'e.message' with str(e) across:
   - browser/server_groups/__init__.py (delete/update/create handlers)
   - browser/server_groups/servers/__init__.py (5 handlers)
   - browser/server_groups/servers/databases/__init__.py
   - misc/cloud/__init__.py
   - tools/debugger/__init__.py
   - tools/grant_wizard/__init__.py (2 sites)

   Sites guarded by hasattr(e, 'message') first (psycopg3 driver) are
   left as-is; module.messages dict access (utils/__init__) is also
   unrelated.

2. ServerNode.list() missing @with_object_filters decorator. PR #8917
   (99b822e47) added object_filters as a required positional arg to
   list() but only added the @with_object_filters decorator to
   get_nodes(), leaving list() with a signature the Flask routing
   couldn't satisfy. Surfaced by SharedServersGetTestCase 'Get a all
   shared server' test, which calls the list endpoint directly.
2026-05-01 23:57:57 +05:30
Ashesh Vashi d57acce354 fix: harden validation/preference/connection-params paths against pre-existing edge cases
Five small defensive fixes that were exposed by running the full
regression suite end-to-end:

1. utils/validation_utils.py: validate_email() now returns False
   instead of raising TypeError when passed a non-str/bytes value
   (e.g. None from a missing form field). Matches the wrapper's
   contract that it only ever returns bool.

2. tools/user_management/__init__.py: list endpoint guarded against
   users with no roles. u.roles[0].id -> u.roles[0].id if u.roles
   else None. Triggered by ChangePasswordTestCase fixtures.

3. utils/preferences.py: control_props['tags'] / ['creatable']
   replaced with .get(...) so preferences whose control_props omit
   these keys do not raise KeyError on update.

4. browser/server_groups/servers/__init__.py (create endpoint):
   convert_connection_parameter() is bidirectional (list<->dict).
   The save path always wants the storage shape (dict). When input
   is already a dict (internal callers / tests mimicking storage
   form), skip conversion to avoid the dict->list round-trip that
   breaks the MutableDict column. Same fix applied to the workspaces
   save path.

5. misc/workspaces/__init__.py: same defensive handling for
   convert_connection_parameter() on the save path.

These are all pre-existing master bugs surfaced by edge-case test
data; none are introduced by the 9.15 CVE work.
2026-05-01 23:29:43 +05:30
Ashesh Vashi dc61039e93 fix: quote username in views/mview test helper for dotted local roles
Same dot-username bug as 504775de8 / 9b29bc203 but in a different shape:
view_test_data.json query strings are Python expressions interpolating
server['username'] into ALTER TABLE ... OWNER TO and GRANT ... TO
clauses. views/tests/utils.py:create_view evaluates these expressions,
producing SQL with an unquoted identifier. A local PG role with a dot
(e.g. 'ashesh.vashi') was rejected with 'syntax error at or near "."'.

Substitute server['username'] with Driver.qtIdent(None, server['username'])
in the query template before evaluating, so the resulting SQL contains
the properly-quoted identifier. The substitution covers every
occurrence in the template (some queries have OWNER TO and GRANT TO in
the same string).

Accounts for the remaining 72 'syntax error at or near "."' cases that
504775de8 + 9b29bc203 did not reach.
2026-05-01 23:09:09 +05:30
Ashesh Vashi 9b29bc2033 fix: quote username in types/compound_triggers test helpers for dotted local roles
Two more test setUp helpers had the same dot-username bug as
user_mappings (504775de8): server['username'] was substituted into
the OWNER TO clause unquoted, so a local PG role with a dot (e.g.
'ashesh.vashi') was rejected with 'syntax error at or near "."'.

- types/tests/utils.py - 'ALTER TYPE ... OWNER TO %s' now uses
  Driver.qtIdent() for the username.
- compound_triggers/tests/utils.py - sql_query template substitution
  now passes the qtIdent-quoted username.

Same Driver.qtIdent(None, server['username']) approach as in 504775de8.
2026-05-01 23:00:48 +05:30
Ashesh Vashi 504775de80 fix: quote username in user_mappings test helper for dotted local roles
The setUp helper in user_mappings/tests/utils.py interpolated
server['username'] directly into the CREATE USER MAPPING DDL, so a
local PostgreSQL role containing a dot (e.g. 'ashesh.vashi') was
parsed by PG as a schema-qualified identifier and rejected with
'syntax error at or near "."'. This blocked every test whose setUp
created a user mapping (UserMappingGetSQLTestCase, etc.).

Wrap the FOR target with Driver.qtIdent() so the identifier is
double-quoted when it contains special characters. Mirrors the
approach used by the resql framework's _normalize_owner() / <OWNER>
substitution introduced in d112dc3b9 - that fix covered SQL
expectation comparison; this fix covers test setUp DDL generation,
which the resql logic does not reach.

The OPTIONS user/password are left as raw '%s' since they are SQL
string literals (single-quoted), where dots are syntactically safe.

Verified: 33/33 user_mappings tests pass; zero 'syntax error at or
near "."' occurrences in the targeted run.
2026-05-01 22:54:09 +05:30
Ashesh Vashi 044355c5e0 fix: harden EnhancedRotatingFileHandler._open and add regression tests
Found by aggressive review of 83abb1c4f:

1. fd leak on os.fdopen failure — os.open returns a raw fd; if
   os.fdopen() raises after, the fd is orphaned. Wrap in try/except
   and close on failure. Built-in open() doesn't have this issue
   because it manages the fd internally.

2. Silent regression of close-on-exec — Python's built-in open() sets
   fds non-inheritable by default since PEP 446 (3.4+). os.open()
   does not. Replacing the default open path therefore made the log
   fd inheritable across fork/exec — mostly harmless in pgAdmin's
   subprocess paths (they default close_fds=True) but still a
   silent behavioral diff. OR in O_CLOEXEC to match.

Also adds web/pgadmin/utils/tests/test_enhanced_log_rotation.py
covering: new file mode, rotated archive + new active file mode,
pre-existing file mode unchanged, and fd non-inheritable. Skipped on
Windows where POSIX mode bits and PEP 446 semantics differ. Verified
all 4 cases pass via web/regression/runtests.py --pkg utils.
2026-05-01 16:38:49 +05:30
Ashesh Vashi 85366aa128 fix: create pgadmin4.log with mode 0o600
Override EnhancedRotatingFileHandler._open() so the active log file
and rotated backups are created owner-only on POSIX. The parent
DATA_DIR is already 0o700, so this is defense-in-depth — but the
log file can contain sensitive context (auth events, query
fragments, paths) and should not be world/group readable on its own.

Pre-existing log files keep their current permissions; new installs
and rotated files pick up the tighter mode. Windows uses the default
open path since os.open mode is ignored there.
2026-05-01 16:38:49 +05:30
Ashesh Vashi 6f4f28def7 refactor: factor wtforms-error-to-JSON into helper, drop dead import
Two endpoints (change_password, forgot_password) had previously been
patched ad-hoc to handle wtforms validators that emit Babel
LazyString (LazyProxy) error messages — those instances report as
iterable, so json.dumps fails with "Circular reference detected" if
they reach the encoder. Both fixes lived inline with slightly
different shapes and a shared trap waiting to bite the next caller.

Extract `_first_form_error_message(form, default=None)`: walks
form.errors, force-resolves the first LazyString to plain str, returns
the default when the form has no errors. Use it from both endpoints.

Also drop the now-dead `from flask_security.views import
default_render_json` import — the only callsite in browser/__init__.py
was replaced when that function's signature drifted; the import was
left behind.

No behavior change beyond consolidating the two ad-hoc patterns; both
endpoint test suites still pass.
2026-05-01 16:38:49 +05:30
Ashesh Vashi fb9ce563fb fix: tighten DATA_DIR file/dir permissions at creation
Two related hardening changes around the pgAdmin data directory:

1. Atomic 0o600 for pgadmin4.db
   ----------------------------
   pgadmin/__init__.py:run_migration_for_sqlite() and
   setup.py:setup_db()'s run_migration_for_sqlite() previously chmod'd
   pgadmin4.db to 0o600 *after* SQLAlchemy/SQLite created it via
   db_upgrade(). With a typical 0o022 umask, the file existed at 0o644
   between SQLite's create and pgAdmin's chmod — a TOCTOU window.
   Practically narrow because the parent dir is 0o700, but easy to
   close: wrap the migration call in `os.umask(0o077)` so the file is
   born 0o600. Both code paths use try/finally so the prior umask is
   restored even on migration failure (a raise during db_upgrade no
   longer leaves the rest of the worker running with 0o077 in effect).
   The post-hoc chmod is kept as belt-and-suspenders for the case where
   the file already existed at a wider mode from an older install.

2. 0o700 for sensitive DATA_DIR subdirectories
   --------------------------------------------
   setup/data_directory.py previously chmod'd only SESSION_DB_PATH and
   the parent dir of SQLITE_PATH to 0o700. STORAGE_DIR (user uploads
   including saved cloud-deployment certs/keys), AZURE_CREDENTIAL_CACHE
   _DIR (MSAL token cache files — real Azure credentials), KERBEROS_
   CCACHE_DIR (Kerberos credential caches), and the directory holding
   pgadmin4.log (stack traces frequently capture sensitive context)
   were left at umask-default (typically 0o755 — directory listing
   readable by any local user).

   Refactor the create-loop to track which directories were newly
   created on this invocation, then apply chmod 0o700 uniformly to all
   sensitive dirs at once. Errors (e.g., chmod on a mounted volume in
   OpenShift) emit a WARNING but don't abort — same lenient pattern the
   existing SQLITE_PATH-dir chmod already used.

   pgadmin4.log itself is still 0o644 (umask-default) because Python's
   logging.FileHandler doesn't take a mode argument; tracked as a
   follow-up task to subclass the handler.
2026-05-01 16:38:49 +05:30
Ashesh Vashi 3b36dd3964 fix: encrypt session-file body (Fernet) for confidentiality at rest
The session file contains OAuth access/refresh tokens, AWS / Google /
Azure / BigAnimal cloud credentials, the Kerberos cache path, MFA OTP
material, and pass_enc_key — the symmetric KEK that decrypts the user's
saved Postgres server passwords. The HMAC header introduced earlier in
this branch protects integrity but not confidentiality: a leak of
sessions/<sid> alone exposes every secret in plaintext.

Wrap the pickle body in Fernet (AES-128-CBC + HMAC-SHA256, AEAD) before
the on-disk HMAC computation — encrypt-then-MAC, so pickle.loads is
unreachable on the read path until both the file HMAC verifies and
Fernet authenticates the ciphertext.

File format becomes:

    +----------------------------------------------------------+
    | _HMAC_HEX_LEN bytes : hex HMAC over the ciphertext      |
    +----------------------------------------------------------+
    | N bytes : Fernet(pickle((randval, digest, data)))       |
    +----------------------------------------------------------+

Fernet key is derived from SECRET_KEY via HKDF-SHA256 with a fixed,
versioned salt and info string (`pga-session-body-v1`,
`pgadmin session body encryption v1`) so multiple workers produce the
same key deterministically and a future format swap can derive a fresh
key without reusing bytes already in flight on disk.

Caveat (not theoretical): SECRET_KEY currently lives in pgadmin4.db in
the same DATA_DIR. A leak that includes BOTH sessions/ AND pgadmin4.db
recovers the derived Fernet key and decrypts session bodies. Closing
that gap requires moving SECRET_KEY out of DATA_DIR (e.g., into the OS
keychain via USE_OS_SECRET_STORAGE) — tracked as a Layer-2 follow-up.

Backwards compat: pre-Layer-1 session files (HMAC over plain pickle, no
Fernet) pass the HMAC check but raise InvalidToken on Fernet.decrypt.
That's caught and logged as "legacy unencrypted body"; users see a
one-time re-login on upgrade. (The same upgrade also flips
SESSION_DIGEST_METHOD's default from sha1 to sha256 and changes the
file format, so the re-login is unavoidable regardless.)

Three new tests:

* TestSessionBodyIsEncryptedOnDisk: place a sentinel in the session,
  read raw bytes, assert the sentinel does NOT appear on disk.
* TestSessionBodyRejectedWithDifferentSecret: write under SECRET_KEY=A,
  read under SECRET_KEY=B, confirm rejection (rules out hard-coded-key
  bugs).
* TestLegacyHmacOnlyFileRejected: pre-Layer-1 file is rejected with the
  expected "legacy unencrypted body" log message.

Pre-existing tests that built bodies directly (TestCorruptedHmacHeader,
TestCookieHmacMismatchWithValidFile) updated to wrap the body via the
new make_encrypted_body() helper so they exercise the realistic file
shape.
2026-05-01 16:38:49 +05:30
Ashesh Vashi bee80fe943 fix: write session files with mode 0o600 (was umask-default 0644)
$DATA_DIR/sessions/<sid> contains OAuth access_tokens / refresh_tokens
(via session['oauth2_token']), AWS / Google / Azure / BigAnimal cloud
credentials (via the cloud refactors earlier in this branch), the
Kerberos credential cache path, MFA OTP material, and pass_enc_key —
the symmetric KEK that decrypts the user's saved Postgres server
passwords.

The HMAC header added earlier in this branch protects integrity but
not confidentiality: anyone with read access to the file gets the
secrets. Default `open(path, 'wb')` uses the process umask, which on
typical systems leaves files 0o644 (world-readable). Switch
new_session() and put() to a new _open_session_file() helper that
opens with `os.open(... O_WRONLY | O_CREAT | O_TRUNC, 0o600)`, mirroring
the upload helper introduced in PR 1.

The directory itself is already 0o700, so this is defense-in-depth for
container scenarios where the data volume might be mounted under shared
uids, or for misconfigurations of the directory mode.

NB: Existing session files retain their old mode until next write, then
adopt 0o600. Operators who want to forcibly tighten existing files can
chmod the sessions directory recursively post-upgrade.

Adds a positive test asserting both put() and new_session() produce
0o600 files (skipped on Windows where POSIX mode bits are not
meaningful).
2026-05-01 16:38:49 +05:30
Ashesh Vashi ccfbd2c457 fix: SESSION_DIGEST_METHOD default to sha256, follow-up review polish
Default SESSION_DIGEST_METHOD from hashlib.sha1 to hashlib.sha256.
HMAC-SHA1 is still cryptographically acceptable for the cookie's
(sid, randval) signature, but SHA-256 is the modern default and aligns
with the file-HMAC header introduced earlier in this branch. The session
file format already invalidates all existing sessions on upgrade (the
new HMAC header is required), so flipping this default at the same time
is a free hardening rather than an additional break.

Test polish from the post-merge hostile review:

* Tighten the "no unsafe deserializer imported" assertion in the four
  cloud-module test files (RDS, Google, BigAnimal, Azure) to a regex
  anchored at line start with a word boundary, so it catches
  `from pickle import dumps, loads`, `import pickle as p`, and indented
  imports — not just bare `import pickle`.

* test_login.py: drop a sid-rotation assertion that would have given
  false confidence. Flask-Paranoid does NOT rotate the session id on
  login (it binds a `_paranoid_token` to UA+IP and validates per
  request), so an `assertNotEqual(pre_sid, post_sid)` would always fail
  for the wrong reason. Comment the limitation in the test for the next
  reviewer; stronger fixation testing is owed as a follow-up.

* docs/proposals: spec line numbers in §4.2 had drifted ~10 lines from
  the implemented branch (helper extraction, etc.); refresh them and
  the audit-summary table to point at HEAD-of-branch lines. Append a
  §1.6 entry enumerating the residual `pickle.loads` callsites in
  sqleditor / schema_diff / bgprocess that PR 7 / Phase 2 will close,
  so future reviewers see the surface.
2026-05-01 16:38:49 +05:30
Ashesh Vashi 1518b0828d fix: SERVER_MODE python-test path and two endpoint regressions
CI runs the python test suite with SERVER_MODE=False (DESKTOP) which
explicitly skips OAuth2 / LDAP / Kerberos / change-password / forgot-
password tests via runtime guards. Running the suite with
SERVER_MODE=True surfaced two real pgAdmin endpoint regressions and a
batch of stale test fixtures that this commit addresses.

Test-infra fixes (regression/python_test_utils/csrf_test_client.py):

* fetch_csrf was looking for <input id="csrf_token" ...> which the
  React SPA login no longer renders. The token is exposed as
  "csrfToken": "..." in a JSON config block embedded in /login HTML
  (camelCase) and as "csrf_token": "..." in JSON API responses
  (snake_case). Match both, fall back to the legacy hidden-input form.

* login() captured the CSRF from GET /login, but Flask-Paranoid
  regenerates the session on login (anti-fixation), which drops the
  csrf_token from the new session. Subsequent state-changing API calls
  failed with "The CSRF session token is missing." Refresh the token
  from a post-login GET /browser/ when SERVER_MODE is True.

Endpoint regressions (web/pgadmin/browser/__init__.py):

* change_password JSON path: bad_request(list(form.errors.values())[0][0])
  passed a Babel LazyString (LazyProxy) to the JSON encoder, producing
  500 with "Circular reference detected." Force resolution to plain
  str.

* forgot_password JSON path: default_render_json(form, include_user=
  False) used a Flask-Security API that no longer accepts include_user,
  yielding 500 on every JSON POST. Replace with pgAdmin's standard
  make_json_response/bad_request envelope.

Test-fixture maintenance:

* test_change_password.py / browser/tests/utils.py: send JSON to the
  JSON-only change_password endpoint (was sending form data, which the
  endpoint silently ignored); use DELETE /user_management/save/<id>
  for cleanup; update respdata strings to match current pgAdmin output.

* test_login.py, test_gravatar_image_display.py,
  test_ldap_with_mocking.py, test_kerberos_with_mocking.py,
  test_webserver_with_mocking.py: drop the
  'Gravatar image for X' assertion (server-rendered HTML no longer
  exists; React renders the gravatar client-side) and verify successful
  authentication via session._user_id instead.

* test_ldap_login.py: skip when LDAP config is template placeholders
  (no live LDAP server reachable in dev/CI).

* test_kerberos_with_mocking.py: skip when authenticate.kerberos_login
  is not registered (the blueprint loads only when KERBEROS is in
  AUTHENTICATION_SOURCES at app-init time).

* test_reset_password.py: switch to JSON POST against the JSON-only
  forgot_password endpoint; parametrize expected status; drop HTML
  pre-check.

* test_validate_email.py: drop pg@postgres.local (modern email-
  validator rejects RFC 6761 special-use TLDs); switch deliverability
  scenario to .invalid/.test domains for stable DNS.

Net result: 296 tests pass, 0 failures, 12 skipped (LDAP/Kerberos
require infra; 1 pgcrypto scenario; cloud-wizard real-credential
tests).
2026-05-01 16:38:49 +05:30
Ashesh Vashi 93206710f7 fix: drop live Azure instance from session, persist auth state only
The azure cloud-deployment module wrote a live Azure class instance
directly into session['azure']['azure_obj'] (no pickle.dumps -- it
relied on the session backend to serialize anything). That is an
implicit pickle dependency: any session-format change would crash, and
the live class instance carries Azure SDK credential objects with
mutable state.

Add Azure.to_state()/from_state() that round-trip the persistable
fields (tenant_id, session_token, use_interactive_credential,
authentication_record_json, region, subscription_id, availability_zone,
available_capabilities_list, azure_cache_name, azure_cache_location)
through a plain dict. Live SDK objects (_clients, _credentials,
_cli_credentials) are intentionally NOT in to_state -- they're rebuilt
lazily from authentication_record_json on first credential use.

from_state bypasses __init__ (which references current_user.username)
via cls.__new__(cls) so unit tests work without a Flask login context.

Module-level _get_azure_from_session()/_save_azure_to_session() helpers
replace the 18 session['azure']['azure_obj'] sites across 12 endpoints.

Worker-restart UX trade-off: today's behavior pickles the populated
Azure SDK client cache, surviving a worker recycle. After this change
the in-memory cache is gone on restart, but the persisted
authentication_record_json is sufficient for the SDK to silently rebuild
the credential without re-prompting for device code -- the class was
designed for this replay. Verification owed in PR review.

Seven new tests cover: round-trip of persistable fields, helper from
session state, lazy SDK objects after from_state, missing-session
graceful return, defaults for partial state, regression assertion that
no live instance leaks into 'azure_obj', and that the unsafe
deserializer is not used.
2026-05-01 16:38:49 +05:30
Ashesh Vashi 2b0d44a33f fix: drop pickled BigAnimal provider instance from session
The biganimal cloud-deployment module pickled the live BigAnimalProvider
across 15 sites (verify, polling-for-token, projects, providers,
regions, db_types, db_versions, instance_types, volume_types,
volume_properties, deploy). Same pattern, same risk as the other
cloud providers.

Add BigAnimalProvider.to_state()/from_state() and
_get_biganimal_from_session()/_save_biganimal_to_session() helpers.
from_state uses cls.__new__(cls) to bypass __init__'s
get_auth_provider() HTTP call -- the persisted `provider` dict already
holds that data.

Five new tests cover: round-trip preservation, helper construction,
defaults for partial state, missing-session graceful return, and a
regression assertion that pickle is gone.
2026-05-01 16:38:49 +05:30
Ashesh Vashi fffc3cc242 fix: drop pickled Google cloud instance from session
The google cloud-deployment module persisted the live Google class via
pickle.dumps/pickle.loads across 14 sites: the verify_credentials
handler, the OAuth2 callback, and every region/zone/version/instance-
type lookup. Each call paid a pickle round-trip and re-introduced the
deserialization risk inside session storage.

Add Google.to_state()/Google.from_state() that round-trip the
persistable fields (client_config, credentials_json, redirect_url,
project_id, regions, availability_zones, verification flags) through
a plain dict, and rebuild the google.oauth2.credentials.Credentials
SDK object lazily from credentials_json. Module-level
_get_google_from_session() / _save_google_to_session() helpers replace
the per-site pickle dance.

Six new tests cover: round-trip of all persistable fields, credential
rebuild from token dict, helper construction from session state,
helper returning None for missing/empty session, defaults for partial
state, and a regression assertion that pickle is gone from the module.
2026-05-01 16:38:48 +05:30
Ashesh Vashi adbb249604 fix: drop pickled RDS instance from session
verify_credentials previously stored pickle.dumps(rds, -1) under
session['aws']['aws_rds_obj']; subsequent endpoints unpickled it via
pickle.loads. That pickle round-trip was an in-session deserialization
vector and forced the session storage to carry binary blobs.

The RDS class is a thin wrapper over boto3 clients: all of its mutable
state is the credential dict already saved at session['aws']['secret'].
Drop the pickled blob and reconstruct RDS per request via a new
_get_rds_from_session() helper. boto3 client construction is cheap
(microseconds) and clients are cached on the per-request instance.

Also fixes a pre-existing latent bug in verify_credentials where status
was undefined when the cached creds matched the new ones (only the
inside of the cache-miss branch assigned it), now explicitly set to
True for the cache-hit path.

Five new tests cover: full creds, missing session_token, no aws key,
no secret key, and a regression assertion that the cloud.rds module
no longer imports the unsafe deserializer.
2026-05-01 16:38:48 +05:30
Ashesh Vashi 64a232bc85 fix: drop live AuthSourceManager from session, store provider name only
The OAuth2 login redirect flow stashed the live AuthSourceManager
instance in flask.session via session['auth_obj'] = auth_obj, requiring
the session storage to handle arbitrary Python objects. That coupled the
auth flow to the pickle-backed session format and presented a second-
order deserialization vector if any path could influence session
contents.

The only piece of state that genuinely needs to cross the OAuth2
provider redirect is the provider name. Persist that as
session['oauth2_current_client'] (a plain str) and reconstruct a fresh
AuthSourceManager in oauth_authorize from current_app's auth-source
registry. The OAuth2 source's per-instance oauth2_current_client is
restored before login() so client selection still works on the callback.

Defensive: oauth_authorize now redirects with a flash error when the
session-state key is missing (e.g., session expired between login and
callback), instead of KeyError'ing.

Two new scenarios in test_oauth2_with_mocking.py verify (a) post-redirect
session contains the new minimal state and not a live class instance,
and (b) the callback handles missing provider state without 500.
2026-05-01 16:38:48 +05:30
Ashesh Vashi 435752b83c fix: symlink-based path traversal in file_manager (CWE-61/CWE-22) (#9902)
CWE-61 / CWE-22 in file_manager: check_access_permission used
os.path.abspath, which resolves '..' but not symbolic links, while
the subsequent kernel write follows symlinks. An authenticated user
could plant a symlink inside their storage area pointing outside it
and write to any path the pgAdmin process could reach.

Fix: switch to os.path.realpath for both orig_path and in_dir, and
add a new _open_upload_target helper that opens with O_NOFOLLOW
(and mode 0o600) to close the leaf-component TOCTOU between the
access check and the open. Drops the redundant post-write
check_access_permission call in add().

Mode change for uploaded files (0o644 -> 0o600) is intentional
hardening; release notes will call this out.

Tests: 16 file-manager security tests covering realpath enforcement
on all five access-check consumers and O_NOFOLLOW leaf-symlink
rejection.

The shared design proposal lives in docs/proposals/ and was added
in the preceding pickle-RCE commit.

Reported-by: Fernando Bortotti <fernando.bortotti@bsd.com.br>
2026-05-01 16:38:48 +05:30
Ashesh Vashi 30a8903374 fix: pickle deserialization RCE in session manager (CWE-502) (#9901)
CWE-502 in FileBackedSessionManager.get(): pickle.load was called on
the session file before the HMAC integrity check. Any file dropped
in the sessions directory was deserialized unconditionally, allowing
an authenticated user with sessions-dir write access to achieve
OS-level RCE.

Fix: prepend a 64-byte hex SHA-256 HMAC header over the pickle body,
computed with the SECRET_KEY, and verify it via hmac.compare_digest
before any deserialization. Also raises (not asserts) on empty
SECRET_KEY so -O does not strip the check, and narrows the
post-deserialize except clause to surface programming errors instead
of masking them.

Tests: 13 new session-format tests covering round-trip, malicious-
pickle rejection, header tampering, empty/truncated files,
cookie-HMAC mismatch, unsafe sids, empty SECRET_KEY, MFA-shaped data,
and SERVER_MODE=False direct upload.

Includes docs/proposals/2026-04-30-eliminate-rce-and-symlink-escape.md,
the shared design doc covering this fix and the symlink-traversal fix
in the following commit.

Reported-by: Fernando Bortotti <fernando.bortotti@bsd.com.br>
2026-05-01 16:38:48 +05:30
Ashesh Vashi 24485fe964 fix: prevent LFI and SSRF in LLM API configuration endpoints (#9900)
User-supplied api_key_file and api_url preferences fed pgAdmin's LLM
provider clients without validation. An authenticated user could read
arbitrary server-side files (LFI) or coerce pgAdmin into requesting
internal targets such as 169.254.169.254 (SSRF) via the chat path and
model-list endpoints.

- validate_api_key_path() restricts user-supplied paths to the user's
  private storage directory in server mode (covering both old- and
  new-style names) or the home directory in desktop mode; resolves
  symlinks and rejects null bytes. Shared storage is intentionally
  excluded since API keys are per-user secrets.
- _read_api_key_from_file() caps reads at 1024 bytes and enforces a
  printable-ASCII no-whitespace key shape so it cannot be repurposed
  as an arbitrary file reader.
- validate_api_url() enforces config.ALLOWED_LLM_API_URLS by exact
  scheme://host:port match, applied at refresh endpoints, accessor
  fallbacks, and provider client constructors so the chat path is
  also covered. Logs a startup warning if the allowlist is empty.
- Adds test coverage for path validation, URL validation, refresh-
  endpoint rejection paths, and refresh-endpoint happy paths.

Reported-by: j3seer <jasserchebbi@outlook.com>
2026-05-01 16:38:48 +05:30
Ashesh Vashi 13badc62c3 fix: Prevent OS command injection in Import/Export query export (CWE-78) (#9899)
User-supplied input was interpolated directly into a psql \copy
metacommand template without sanitization. An authenticated pgAdmin
user could inject ") TO PROGRAM 'cmd'" to achieve RCE on the pgAdmin
server, or ") TO '/path'" for arbitrary file write, by breaking out
of the \copy (...) context.

Add _is_query_parens_balanced() which tracks parenthesis depth in the
query, modeled on psql's strtokx tokenizer used by parse_slash_copy.
Only single-quoted strings ('...' with '' and \' escaping) and
double-quoted identifiers ("..." with "" escaping) are recognized;
line/block comments and dollar-quoting are deliberately NOT skipped
because psql's \copy parser does not recognize them either, so any
unbalanced ')' inside those constructs must remain visible to the
validator.

Whitelist-validate format, on_error, and log_verbosity which were
also raw-interpolated, and write the normalized lowercase value back
to data so downstream template equality checks (e.g. data.format ==
'csv') match regardless of input case.

Reject queries containing null bytes (which could cause a C-string
truncation mismatch between Python validation and psql execution)
and normalize \r\n / \r / \n to spaces (required for Windows; also
prevents psql metacommand termination).

Type-check 'query' before string operations so malformed payloads
return a clean 400 instead of a 500. Tighten is_query_export gating
to 'is True' in both update_data_for_import_export and the validation
block so a client cannot exploit a truthy-but-not-True value to
bypass one check while satisfying the other.

Add unit tests covering the parens parser (balanced cases, RCE
attempts, escape edge cases) and route-level tests asserting
BatchProcess is not invoked for malicious payloads.

Reported-by: chungkn (Chung Kim), OneMount Group <kimngocchung.k2a@gmail.com>
2026-05-01 16:38:48 +05:30
Ashesh Vashi cf53953d9a fix: prevent SQL injection in Maintenance tool option values (#9898)
The Maintenance tool concatenated four user-supplied JSON fields
(buffer_usage_limit, vacuum_parallel, vacuum_index_cleanup,
reindex_tablespace) directly into the rendered VACUUM/ANALYZE/REINDEX
command, which was passed to psql --command. An authenticated user
with the tools_maintenance permission could break out of the option
syntax and execute arbitrary SQL on the connected server, escalating
to RCE on the database host via COPY ... TO PROGRAM.

Fix:
- Allow-list values server-side before rendering: vacuum_index_cleanup
  in {AUTO, ON, OFF}; vacuum_parallel a non-negative integer up to
  1024; buffer_usage_limit matching ^\d+\s*(kB|MB|GB|TB)$
  (case-insensitive). Reject with HTTP 400 on failure.
- Switch reindex_tablespace from manual double-quote wrapping to the
  qtIdent filter used elsewhere in the same template, which both
  escapes embedded quotes and only quotes when required.
- Type-guard against unhashable values so non-string payloads return
  400 instead of triggering a 500.

Reported-by: j3seer <jasserchebbi@outlook.com>
2026-05-01 16:38:48 +05:30
Ashesh Vashi d112dc3b96 fix: Fixed the resql tests - use <OWNER> placeholder instead of hardcoded 'postgres' role (#9873)
The reverse-engineered SQL tests hardcoded 'postgres' as the role name
in test JSON data and expected SQL files. This fails on systems where
the superuser is not 'postgres' (e.g. 'ashesh.vashi').

Changes:
- Replace hardcoded 'postgres' with '<OWNER>' placeholder in ~400 test
  JSON, SQL, and MSQL files across all browser module tests
- Extend test_resql.py to resolve '<OWNER>' in scenario data sent to
  API endpoints (not just in expected SQL)
- Handle identifier quoting for usernames containing special characters
  (dots, etc.) by normalizing both expected and actual SQL
- Fix test_domain_sql.py to use qtIdent for username in expected SQL
- Split Authlib version pin: 1.6.x for Python <=3.9, 1.7.x for >3.9
2026-04-24 21:18:38 +05:30
Ashesh Vashi b70d4c9857 fix: Bump runtime and development dependencies (#9870)
Python (requirements.txt):
- Authlib 1.6.9 -> 1.7.0
- Flask-Security-Too 5.7.* -> 5.8.* (py>3.9)
- certifi 2026.2.25 -> 2026.4.22
- Flask-WTF 1.2.* -> 1.3.*

Python test (web/regression/requirements.txt):
- selenium 4.41.0 -> 4.43.0
- testscenarios 0.5.0 -> 0.6.1
- testtools 2.8.7 -> 2.9.0

JavaScript web (web/package.json):
- @mui/material ^7.3.7 -> ^7.3.10
- @mui/x-date-pickers ^8.27.2 -> ^8.28.3
- @mui/icons-material ^7.3.6 -> ^7.3.10
- dompurify ^3.3.3 -> ^3.4.1
- axios ^1.13.5 -> ^1.15.2
- react ^19.2.3 -> ^19.2.5
- react-dom ^19.2.3 -> ^19.2.5
- typescript ^5.9.2 -> ^6.0.3
- marked ^17.0.1 -> ^18.0.2
- react-checkbox-tree ^1.7.2 -> ^2.0.1
- eslint ^9.39.2 -> ^9.39.4
- and ~60 more minor/patch bumps across build, test, and runtime deps

JavaScript runtime (runtime/package.json):
- electron 41.2.0 -> 41.3.0
- axios ^1.15.0 -> ^1.15.2
- eslint ^10.2.0 -> ^10.2.1
- globals ^17.4.0 -> ^17.5.0
2026-04-24 14:28:36 +05:30
Ashesh Vashi 3294e74197 fix: prevent XSS via innerHTML in browser tree and explain visualizer (#9865)
Use textContent instead of innerHTML when setting DOM element text
to prevent stored XSS through crafted PostgreSQL object names.
2026-04-23 14:21:04 +05:30
Aditya Toshniwal 79e490c5fa Updated yarn.lock file to use yarn v4.14.0 and fix the builds 2026-04-17 17:13:19 +05:30
Ashesh Vashi e4edcf2253 fix: SharedServer feature parity columns and write guards (#9835)
Add passexec_cmd, passexec_expiration, kerberos_conn, tags, and
post_connection_sql to SharedServer so non-owners get their own
per-user values instead of inheriting the owner's.  Drop the unused
db_res column which was never overlaid or writable by non-owners.

Key changes:
- New Alembic migration (sharedserver_feature_parity) adds 5 columns,
  drops db_res, cleans up orphaned records.  All operations idempotent.
- Overlay copies new fields from SharedServer instead of suppressing
- _owner_only_fields guard blocks non-owners from setting passexec_cmd,
  passexec_expiration, db_res, db_res_type via API
- Non-owners can set post_connection_sql (runs under their own creds)
- update_tags and flag_modified use sharedserver for non-owners
- update() response returns sharedserver tags for non-owners
- ServerManager passexec suppression with config.SERVER_MODE guard
- UI: post_connection_sql editable for non-owners (readonly only when
  connected, not when shared)
- SCHEMA_VERSION bumped to 51
- Comprehensive unit tests for overlay, write guards, and tag deltas
2026-04-13 15:03:31 +05:30
Ashesh Vashi 4ddb16f47a fix: customize container user permissions using PUID and PGID. #9657 (#9833)
Add support for custom container user permissions via PUID and PGID
environment variables. When the container is started as root
(--user root), the pgadmin user is reassigned to the requested UID/GID
and all initialization runs under that user via su-exec, ensuring
files are created with correct ownership from the start.

Key changes:
- Dockerfile: add su-exec package, add chmod g=u for /run/pgadmin
  (fixes OpenShift random UID access)
- entrypoint.sh: add PUID/PGID validation and privilege dropping
  before initialization (not after), preserving OpenShift compatibility

Three modes supported:
- Default (USER 5050): unchanged behavior
- Custom UID (--user root -e PUID=N -e PGID=N): drops to target user
  before any init
- OpenShift (random UID, GID 0): passwd fixup + group permissions
2026-04-13 14:34:18 +05:30
Ashesh Vashi a3a0537277 fix: Bump runtime dependencies and upgrade ESLint to v10 (#9834)
- electron: 41.0.2 → 41.2.0
- eslint: ^9.39.2 → ^10.2.0
- axios: ^1.13.5 → ^1.15.0
- electron-context-menu: ^4.1.0 → ^4.1.2
- Added @eslint/js and globals as explicit devDependencies (required by ESLint 10)
- Fixed no-useless-assignment lint errors in downloader.js and misc.js
2026-04-09 21:31:17 +05:30
Ashesh Vashi 9a76ed80bb fix: enforce data isolation and harden shared servers in server mode (#9830)
pgAdmin 4 in server mode had no data isolation between users — any
authenticated user could access other users' private servers,
background processes, and debugger state by guessing object IDs.
The shared server feature had 21 vulnerabilities including credential
leaks, privilege escalation via passexec_cmd, and owner data
corruption via SQLAlchemy session mutations.

Centralized access control:
- New server_access.py with get_server(), get_server_group(),
  get_user_server_query() replacing ~20 unfiltered queries
- connection_manager() raises ObjectGone (HTTP 410) in server mode
  when access is denied — fixes 155+ unguarded callers
- UserScopedMixin.for_user() on 10 models replaces scattered
  user_id filters

Shared server isolation (all 21 audit issues):
- Expunge server from session before property merge to prevent
  owner data corruption
- Suppress passexec_cmd, post_connection_sql for non-owners in
  merge, API response, and ServerManager
- Override all 6 SSL/passfile connection_params keys from
  SharedServer; strip owner-only keys; sanitize on creation
- _is_non_owner() helper centralises 15+ inline ownership checks
- SharedServer lookup uses (osid, user_id) not name
- Unique constraint on SharedServer(osid, user_id)
- Tunnel/DB password save, change_password, clear_saved_password,
  clear_sshtunnel_password all branch on ownership
- Only owner can unshare (delete_shared_server guard)
- Session restore includes shared servers
- tunnel_port/tunnel_keep_alive copied from owner, not hardcoded

Tool/module hardening:
- All tool endpoints use get_server()
- Debugger function arguments scoped by user_id
- Background processes use Process.for_user()
- Workspace adhoc servers scoped to current user

Migration (schema version 49 -> 50):
- Add user_id to debugger_function_arguments composite PK
- Add indexes on server, sharedserver, servergroup
- Add unique constraint on sharedserver(osid, user_id)
2026-04-09 18:32:59 +05:30
Ashesh Vashi 872d5ac0b3 fix: Bump python & javascript dependencies (#9827)
* Update Python dependencies:
   - google-auth-oauthlib 1.3.0 → 1.3.1
   - sphinxcontrib-youtube 1.4.1 → 1.5.0
   - fixtures 4.3.1 → 4.3.2
   - Add missing newline at end of requirements files

* Update JavaScript dependencies:
   - Replace deprecated @babel/plugin-proposal-* packages with @babel/plugin-transform-* equivalents
   - Remove unused @types/classnames dependency
   - Update yarn.lock
   - Pin the 'react-frame-component' to '~5.2.6'

* fix: Use PostGIS 36 for EPAS 18 in CI workflow
   - EPAS 18 ships with edb-as18-postgis36 instead of postgis34.
   - Add a postgisver matrix variable to support mixed versions.
   - Add exclude+include for EPAS 18 PostGIS matrix to fix empty runs-on

* Moving '@babel/plugin-transform-class-properties', '@babel/preset-react' to devDependencies section
2026-04-08 17:31:56 +05:30
rztrainlocalandrztrainlocal d59fcf3459 fix(9656): Use absolute paths for a2enmod, a2enconf for debain setup script (#9815)
Reason: On debian, it does not have `/usr/sbin` in the path environment variable anymore.

Co-authored-by: rztrainlocal <ke@KE-U758.HOME>
2026-04-06 21:48:19 +05:30
Akshay Joshi d8a078af53 Updated version for release v9.14 2026-03-30 17:44:32 +05:30
Dave PageandClaude Opus 4.6 9bb96360dd Support /v1/responses for OpenAI models. #9795
* Support /v1/responses for OpenAI models. #9795

* Address CodeRabbit review feedback on OpenAI provider.

- Preserve exception chains with 'raise ... from e' in all
  exception handlers for better debugging tracebacks.
- Use f-string !s conversion instead of str() calls.
- Extract duplicated max_tokens error handling into a shared
  _raise_max_tokens_error() helper method.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Validate api_url and use incomplete_details from Responses API.

- Strip known endpoint suffixes (/chat/completions, /responses) from
  api_url in __init__ to prevent doubled paths if a user provides a
  full endpoint URL instead of a base URL.
- Use incomplete_details.reason from the Responses API to properly
  distinguish between max_output_tokens and content_filter when the
  response status is 'incomplete', in both the non-streaming and
  streaming parsers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 14:16:22 +05:30
Libor M. 2228575564 Czech translation for version 9.14 2026-03-30 12:43:22 +05:30
Khushboo Vashi 5dd0de9e0f Revert "Update JavaScript dependencies."
This reverts commit 92bb092718.
2026-03-30 12:29:48 +05:30
Khushboo Vashi aa3dc389e4 Update Release Notes. 2026-03-27 14:27:15 +05:30
Akshay Joshi 92bb092718 Update JavaScript dependencies. 2026-03-27 13:23:18 +05:30
Anil Sahoo 3598840203 Fixed an issue where ALT+F5 for executing a query in the Query Tool shows a crosshair cursor icon for rectangular selection. #9570 2026-03-27 12:59:35 +05:30
Domenico Sgarbossa 792441ab95 Updated Italian translation for v9.14. 2026-03-27 11:30:59 +05:30
Anil Sahoo 2c626d1181 Fixed Geometry Viewer not auto-updating on first query execution after View/Edit Data to Query Tool promotion. #9392 2026-03-27 11:23:47 +05:30
Pravesh Sharma 278e812293 Fixed an issue where the default fillfactor value for B-tree indexes was incorrect. #9648 2026-03-27 11:20:40 +05:30
Pravesh Sharma 1d51a1a943 Fixed an issue where the Query tool kept prompting for a password when using a shared server. #9789 2026-03-27 11:03:15 +05:30
Dave Page bf649420b7 Fix a couple of related issues in the Query Tool layout.
- Hide the AI Assistant tab if AI is disabled or unconfigured. #9696
- Ensure the AI Assistant tab is not the first one shown.
2026-03-27 11:01:42 +05:30
Anil Sahoo 1c93f93a6a Fixed Python & feature test failures caused by Werkzeug 3.1.7 rejecting empty Host header in CSRF token generation. 2026-03-26 17:59:58 +05:30
Akshay Joshi 3ba887001a Updated message catalogs for v9.14 2026-03-26 17:06:45 +05:30
Akshay Joshi 257654849b Updated Javascript and Python dependencies. 2026-03-17 12:12:00 +05:30
Dave Page d7f462c22c Clarify where the LLM API key files should be. #9758 2026-03-17 11:44:17 +05:30
Dave Page 6296016b67 Fix an issue where LLM responses are not streamed or rendered properly in the AI Assistant. #9734
* Address CodeRabbit review feedback for streaming and SQL extraction.

- Anthropic: preserve separators between text blocks in streaming to
  match _parse_response() behavior.
- Docker: validate that the API URL points to a loopback address to
  constrain the request surface.
- Docker/OpenAI: raise LLMClientError on empty streams instead of
  yielding blank LLMResponse objects, matching non-streaming behavior.
- SQL extraction: strip trailing semicolons before joining blocks to
  avoid double semicolons in output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address remaining CodeRabbit review feedback for streaming and rendering.

- Use distinct 3-tuple ('complete', text, messages) for completion events
  to avoid ambiguity with ('tool_use', [...]) 2-tuples in chat streaming.
- Pass conversation history from request into chat_with_database_stream()
  so follow-up NLQ turns retain context.
- Add re.IGNORECASE to SQL fence regex for case-insensitive matching.
- Render MarkdownContent as block element instead of span to avoid
  invalid DOM when response contains paragraphs, lists, or tables.
- Keep stop notice as a separate message instead of appending to partial
  markdown, preventing it from being swallowed by open code fences.
- Snapshot streamingIdRef before setMessages in error handler to avoid
  race condition where ref is cleared before React executes the updater.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Address CodeRabbit review feedback for streaming providers and history.

- Fix critical NameError: use self._api_url instead of undefined API_URL
  in anthropic and openai streaming _process_stream() methods.
- Match sync path auth handling: conditionally set API key headers in
  streaming paths for both anthropic and openai providers.
- Remove unconditional temperature from openai streaming payload to
  match sync path compatibility approach.
- Add URL scheme validation to OllamaClient.__init__ to prevent unsafe
  local/resource access via non-http schemes.
- Guard ollama streaming finalizer: raise error when stream drops
  without a done frame and no content was received.
- Update chat.py type hint and docstring for 3-tuple completion event.
- Serialize and return filtered conversation history in the complete
  SSE event so the client can round-trip it on follow-up turns.
- Store and send conversation history from NLQChatPanel, clear on
  conversation reset.
- Fix JSON-fallback SQL render path: clear content when SQL was
  extracted without fenced blocks so ChatMessage uses sql-only renderer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix missing closing brace in NLQChatPanel switch statement.

Adding block scoping to the error case introduced an unmatched brace
that prevented the switch statement from closing properly, causing
an eslint parse error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix missing compaction module and SQL extraction test.

- Replace compaction module imports with inline history deserialization
  and filtering since compaction.py is on a different branch.
- Add rstrip(';') to SQL extraction test to match production code,
  fixing double-semicolon assertion failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix SQL extraction test expected values after rstrip(';') change.

The rstrip(';') applied to each block before joining means single
blocks and the last block in multi-block joins no longer have
trailing semicolons. Update expected values to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Strictly guard Ollama stream: raise if no terminal done frame received.

Truncated content from a dropped connection should not be treated as
a complete response, even if partial text was streamed. Always raise
when final_data is None, matching CodeRabbit's recommendation.
2026-03-17 11:41:57 +05:30
Akshay Joshi e3472103bb Updated release note. 2026-03-16 19:20:18 +05:30
Dave Page 990b4f5e54 Fix an issue where the AI Assistant was not retaining conversation context between messages, with chat history compaction to manage token budgets.
* Address CodeRabbit review feedback for chat context and compaction.

- Track tool-use turns as groups instead of one-to-one pairs, so
  multi-tool assistant messages don't leave orphaned results.
- Add fallback to shrink the recent window when protected messages
  alone exceed the token budget, preventing compaction no-ops.
- Fix low-value test fixtures to keep transient messages short so
  they actually classify as low-importance.
- Guard Clear button against in-flight stream race conditions by
  adding a clearedRef flag and cancelling active streams.
- Assert that conversation history is actually passed through to
  chat_with_database in the "With History" test.

* Address remaining CodeRabbit review feedback for compaction module.

- Expand protected set to cover full tool groups, preventing orphaned
  tool call/result messages when a turn straddles the recent window.
- Add input validation in deserialize_history() for non-list/non-dict data.
- Strengthen test assertion for preserved recent window tail.


* Fix CI test failures in compaction and NLQ chat tests.

- Lower max_tokens budget in test_drops_low_value to reliably force
  compaction (500 was borderline, use 200).
- Consume SSE response data before asserting mock calls in NLQ chat
  test, since Flask's streaming generator only executes on iteration.


* Clarify mock patch target in NLQ chat test.

Add comment explaining why we patch the source module rather than the
use site: the endpoint uses a local import inside the function body,
so there is no module-level binding to patch.
2026-03-16 19:02:36 +05:30
Rohit Bhati 74b1a3bcc4 Fixed Process Watcher garbled text on Windows with non-UTF-8 locales. #9457 2026-03-16 19:00:41 +05:30
Anil Sahoo 01c2d12b51 Fixed an issue where selecting all in the Query Tool's Messages tab would select the entire page content. #8992 2026-03-16 14:39:42 +05:30
Dave Page e5cf456fae Fixed an issue where AI Reports are grayed out after setting an API key by auto-selecting the default provider. #9694
* Don't let auto-selection override an explicit default_provider choice.

If the same save payload includes a default_provider update (including
setting it to empty/disabled), skip the auto-selection logic so the
user's explicit choice is respected.
2026-03-16 14:24:09 +05:30
Dave Page da55da4d32 Replace misleading AI thinking messages with fun elephant-themed ones. #9702
The previous messages like "Vacuuming the catalog..." and "Analyzing
table statistics..." could be mistaken for actual database operations.
Replace them with clearly whimsical elephant-themed messages, expand
the pool to 32 messages, and consolidate them into a single shared
module with gettext() support.
2026-03-16 14:23:05 +05:30
Akshay Joshi b301bdcecd Updated release note 2026-03-16 12:43:29 +05:30
Dave Page 23bd3359c1 Fixed an issue where AI features are visible in the UI even when LLM_ENABLED is set to False. 2026-03-16 12:35:16 +05:30
Dave Page 0bc4edbab5 Improve the AI Assistant user prompt to be more descriptive of the actual functionality. 2026-03-16 12:34:44 +05:30
Dave Page 34aa360830 Allow copying of text from the AI Assistant chat panel. #9738 2026-03-16 12:16:45 +05:30
Dave PageandClaude Opus 4.6 7e39fc8477 Fix flakey SchemaDialogView 'change text' JS test
Add a wait for the FormView autofocus timer (200ms) to complete before
typing, preventing a race condition where the autofocus moves focus away
from the target field on slow CI machines. This matches the pattern
already used by simulateValidData in the same test file.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 12:14:10 +05:30
Dave PageandClaude Opus 4.6 8f74b2e6d4 Fixed an issue where AI Reports fail with OpenAI models that do not support the temperature parameter. #9719
Removed the temperature parameter from all LLM provider clients and
pipeline calls, allowing each model to use its default. This fixes
compatibility with GPT-5-mini/nano and future models that don't
support user-configurable temperature.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 12:13:04 +05:30
Dave PageandClaude Opus 4.6 c8cb744ffd Fix model tool prompt
* Fix NLQ system prompt to work with models that prioritize text instructions over tool calls.

The previous prompt told the model to "Return ONLY the JSON object, nothing else"
while also providing tool definitions. Models like Qwen 3.5 would follow the text
instruction and never use tools. The updated prompt clearly separates the tool-use
phase from the final JSON response phase, and explicitly instructs the model to
call tools directly rather than describing them in text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update release notes for NLQ prompt fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix issue number in release notes for NLQ prompt fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 12:10:02 +05:30
Dave PageandClaude Opus 4.6 b3aa78c0a5 Added support for custom LLM provider URLs for OpenAI and Anthropic, allowing use of OpenAI-compatible providers such as LM Studio, EXO, and LiteLLM. #9703
- Add configurable API URL fields for OpenAI and Anthropic providers
- Make API keys optional when using custom URLs (for local providers)
- Auto-clear model dropdown when provider settings change
- Refresh button uses current unsaved form values
- Update documentation and release notes

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 12:08:55 +05:30
Dave Page 78e3b67eb1 Fix an issue where the AI Assistant input textbox sometimes swallows the first character of input. #9740 2026-03-16 12:06:47 +05:30
Muhammad Aqeel c8bd75c9a8 Fix apparmor_parser not found error in desktop postinst script (#9728)
Add a runtime guard in the postinst so apparmor_parser is only called
  when available. Previously, packages built on Ubuntu 24+ would fail to
  install on headless servers or systems without AppArmor tools. A warning
  is printed when the profile load is skipped to aid debugging.
2026-03-10 15:29:40 +00:00
Akshay Joshi a0e6da0772 1) Updated JavaScripts and Python dependecies.
2) Updated release note.
2026-03-10 13:41:19 +05:30
Rohit Bhati 8ca765478f Fixed an issue where permissions page is not completely accessible on full scroll. #9721 2026-03-10 12:59:42 +05:30
Pravesh Sharma f49c967bfd Added support to download binary data from result grid. #4011 2026-03-10 12:25:21 +05:30
Khushboo Vashi 0a539c32d9 Fixed an issue where OAuth2 authentication fails with 'object has no attribute' if OAUTH2_AUTO_CREATE_USER is False. #9279 2026-03-10 12:24:55 +05:30
Anil Sahoo 395ff36322 Ensure that the Geometry Viewer refreshes when re-running queries or switching geometry columns, preventing stale data from being displayed. #9392 2026-03-09 16:08:14 +05:30
Florian 00a44a5d76 fix TypeError Exception in session.py
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2026-03-09 12:21:36 +05:30
Daniel NylanderandDaniel Nylander 3ecdc57a59 i18n: Complete Swedish translation (3625/3625 strings)
- Translate all 357 previously untranslated strings
- Fix 1 duplicate entry ('on' with empty msgstr)
- Fix format string mismatch (%(provider)s)
- Total: 3625 translated strings (100% coverage)

Co-authored-by: Daniel Nylander <daniel@danielnylander.se>
2026-03-09 12:21:18 +05:30
Akshay Joshi 08b0f6fe7b 1) Updated Python and Javascripts dependencies.
2) Created new release note files for v9.14
2026-03-05 17:56:33 +05:30
Khushboo Vashi 6d18741f1a Ensure the Kerberos mock test handles OS errors correctly. 2026-03-05 17:44:12 +05:30
Akshay Joshi dc47bf48f7 Remove PG/EPAS 13 from the github runners. 2026-03-05 16:52:41 +05:30
Khushboo Vashi 5b231ddd3f Revert "Customize container user permissions using PUID and PGID. #9657"
This reverts commit 97c90f1e69.
2026-03-02 18:05:26 +05:30
Akshay Joshi 3e48c21fe5 Remove #9657 from release notes due to revert. 2026-03-02 17:06:53 +05:30
Akshay Joshi 4cc74c13f7 Updated version for release v9.13 2026-03-02 14:11:36 +05:30
Akshay Joshi 2cf5d39a01 Fixed one translation issue. 2026-03-02 14:09:25 +05:30
Akshay Joshi f4112c9a0a Updated yarn.lock 2026-03-02 14:08:49 +05:30
Domenico Sgarbossa 6f69120af5 Updated Italian translation for v9.13 2026-03-02 11:48:06 +05:30
Libor M. cf6d7f3b89 Czech translation for version 9.13. 2026-03-02 11:38:56 +05:30
Aditya Toshniwal b78fc884e3 Fixed an issue in file dialog where rename was not working. #9651 2026-02-27 18:24:54 +05:30
Rohit Bhati caafc6bb1b Fix broken checkbox selection in backup dialog objects tree. #9649 2026-02-27 17:01:11 +05:30
Akshay Joshi e9e52905cc Updated message catalogs for v9.13 2026-02-26 18:56:11 +05:30
Akshay Joshi cae00a1dc6 1) Fixed one issue in session.py
2) Updated release note.
2026-02-25 14:26:30 +05:30
Pravesh Sharma 5dd63ca16b Fixed an issue where modifying a shared server incorrectly updated the original server details. #9258 2026-02-25 11:22:44 +05:30
kon-foo 97c90f1e69 Customize container user permissions using PUID and PGID. #9657 2026-02-24 16:49:43 +05:30
Khushboo Vashi bcd48a92b9 Load predefined users from a JSON file through command line. #9229 2026-02-24 16:46:31 +05:30
Lance J.andChristian P. 6d0d387f53 ERD Tool: Insert table with relations via drag-and-drop. #5578 #8198
* Add preference for insert with relations

Co-authored-by: Christian P. <pirnichristian@gmail.com>

* Insert tables with relations on drag and drop

Co-authored-by: Christian P. <pirnichristian@gmail.com>

* Fix test mock not returning Erd Supported Data

Co-authored-by: Christian P. <pirnichristian@gmail.com>

---------

Co-authored-by: Christian P. <pirnichristian@gmail.com>
2026-02-24 12:15:54 +05:30
Rohit Bhati 3995ba9a95 Add support for 'ONLY' in Index creation dialog. #6386 2026-02-24 12:14:36 +05:30
Akshay Joshi 4bb6b72377 Updated Python and JavaScript dependencies. 2026-02-24 11:54:19 +05:30
balodis 080fcc1cfb Fix column ordering in unique/primary key constraint properties
The fromRaw formatter for the Columns field in unique constraint and
primary key properties used _.filter(allOptions, ...), which preserved
the order of allOptions (table column position) rather than the
constraint-defined column order from backendVal. Replaced with _.find
mapped over backendVal to preserve the correct constraint column order.

Added unit tests for cell and type formatter functions to verify
column ordering is preserved.
2026-02-23 17:11:38 +05:30
Dave Page 93e2f23a48 Update the release notes to properly describe the new AI features. 2026-02-23 15:07:36 +05:30
Akshay Joshi 965a27db39 typer[all]==0.24.* drop support for Python 3.9. Added conditional requirement. 2026-02-18 17:41:13 +05:30
Akshay Joshi 7b377ff3da Pin ESLint to 9.39.2 as 10.0.0 not working. 2026-02-18 17:19:05 +05:30
Akshay Joshi 2dedb6e69a Updated Python and Javascript dependencies. 2026-02-18 13:02:46 +05:30
kon-foo b15f15960f Fix misleading error when _FILE secret is not readable. 2026-02-18 12:10:23 +05:30
Khushboo Vashi 50f78bf210 Fix translation compilation. #9583 2026-02-18 11:09:06 +05:30
Akshay Joshi 0842f36f16 Updated release note 2026-02-17 17:41:42 +05:30
Khushboo Vashi 5002419611 Fixed an issue where the 'Quote strings only' configuration was ignored when downloading the result set. #7578 2026-02-17 17:19:41 +05:30
Dave Page f2756a3dcf Core LLM integration infrastructure to allow pgAdmin to connect to AI providers. #9641
* Core infrastructure for LLM integration.
* Add support for a number of different AI generated reports on security, performance, and schema design on servers, databases, and schemas, as appropriate.
* Add a Natural Language AI assistant to the Query Tool.
* Add an AI Insights panel to the EXPLAIN tool in the Query Tool, to analyse and report on issues in query plans.
2026-02-17 17:16:06 +05:30
Khushboo Vashi 2715932464 Update Release Notes. 2026-02-10 14:39:34 +05:30
Murtuza Zabuawala b366d210dc Allow users to customize "OF TYPE" columns during table creation. #229 2026-02-10 14:33:22 +05:30
Simon Randby fbbc12a0a3 Fix an issue where deployment of helm chart crashing with operation not permitted. #9572 2026-02-09 17:13:43 +05:30
Pravesh Sharma 9039080214 Fixed an issue where a long name in ERD table node was not breaking into multiple lines. 2026-02-09 14:53:51 +05:30
Pravesh Sharma f52584eef8 Fixed an issue where column comments were not displayed in the SQL tab for materialised views. #9486 2026-02-09 14:46:15 +05:30
Pravesh Sharma 5abd9e3c5b Fixed an issue where tools settings changed by the users were not restored on application relaunch. #8988 2026-02-09 10:54:53 +05:30
Akshay Joshi 3e05f7a14f Updated Javascript and Python dependencies. 2026-02-06 13:30:09 +05:30
Aditya Toshniwal 9464e44887 Update windows installer side image as the current one gets flipped. 2026-02-04 14:46:48 +05:30
Aditya Toshniwal 18c73d00ce Update version in Helm for release v9.12 2026-02-02 15:17:06 +05:30
Aditya Toshniwal aa33a99b0d - Updated release notes.
- Fix translation compilation.
2026-02-02 15:13:14 +05:30
Anil Sahoo 6000cc0fb4 Fixed an issue where pgAdmin4 app on macOS cannot auto-update while running on a read-only volume even if present in the Applications folder. #9402 2026-02-02 15:11:58 +05:30
Akshay Joshi ef1b69bd5a Update version for release v9.12 2026-02-02 12:24:51 +05:30
Akshay Joshi 5103c47fdf Updated release note. 2026-01-30 17:04:23 +05:30
Libor M. b539740d8e Czech translation for version 9.12 2026-01-30 16:56:52 +05:30
Rohit Bhati 0cb6738343 Fix pgAdmin fails when performing Backup/Restore on a PostgreSQL connection defined exclusively via pg_service.conf. #9553 2026-01-30 16:54:48 +05:30
Domenico Sgarbossa eee9989dde Updated Italian translation for v9.12
update translations
2026-01-30 16:52:33 +05:30
Yogesh Mahajan 596b14a0f4 Ensure the container deployment supports boolean values in yaml format. #9522 2026-01-30 12:10:36 +05:30
Aditya Toshniwal a46d4c22d8 Updated message catalogs for v9.12 2026-01-30 12:04:47 +05:30
David Aparicio c47c79c3dc Enables deployment strategy configuration within the Helm chart. 2026-01-29 14:03:02 +05:30
Yogesh Mahajan c45502dc01 Update menu bar documentation. #9567 2026-01-29 13:14:12 +05:30
MrMonkeyface 5edab2f621 Fix typo in note about file path specifications. 2026-01-28 11:42:08 +05:30
Akshay Joshi 8f39f8a6bc Update JavaScript Dependencies. 2026-01-27 12:25:34 +05:30
dependabot[bot] 26ea03f421 Javascript dependency: Bump lodash from 4.17.21 to 4.17.23 in /web 2026-01-23 11:49:46 +05:30
dependabot[bot] 63e3ad8d33 Javascript dependency: Bump lodash-es from 4.17.22 to 4.17.23 in /web (#9556) 2026-01-23 11:49:29 +05:30
Akshay Joshi 31d1dbc697 Ensure that the tooltip is disabled for a password cell in a dialog grid. #9552 2026-01-22 15:15:13 +05:30
Anil Sahoo 0cac007970 Add new options like INHERIT and SET to the Role's membership tab. #6451 2026-01-20 11:53:32 +05:30
Akshay Joshi 93605c0baa Updated Python and Javascript dependencies. 2026-01-19 17:46:22 +05:30
Akshay Joshi 3d1cd444b0 Updated release note 2026-01-19 17:16:56 +05:30
Paul BOURHISandPaul Bourhis a500939532 Enhance OAUTH2 and OIDC authentication support with improved claims handling and configuration options
Change logging level from exception to error for OIDC profile data issues.
Refactor debug logging in OAuth2 authentication to improve clarity and consistency
Add error handling for missing OAuth2 provider and enhance claims processing logic
Enhance OIDC ID token handling by implementing JWT parsing and updating tests to mock claims extraction
Refactor ID token claims extraction for OIDC providers and update tests to mock userinfo handling
Refactor OAuth2 configuration to use get method for optional URLs
Enhance OAuth2 documentation and implement PKCE support for public clients in authentication logic
Fix typo in OAUTH2 authentication documentation
Implement Azure Entra ID Workload Identity authentication support and add corresponding tests

Co-authored-by: Paul Bourhis <paul.bourhis@bhs-consulting.com>
2026-01-19 16:06:25 +05:30
Aditya Toshniwal 3dab883d88 Update yarn.lock to fix few security vulnerabilities in upstream. 2026-01-16 10:55:45 +05:30
Aditya Toshniwal 7c36eaba90 Warn user of unsaved data output edits before page navigation. #8916 2026-01-14 18:20:52 +05:30
Akshay Joshi e0ba1dd68e Updated JavaScript dependencies. 2026-01-14 12:44:30 +05:30
Rohit Bhati 90be555ec8 Fix pg_restore logs to distinguish UI sync issues from actual failures. #9074 2026-01-14 12:32:12 +05:30
Jørgen H. Fjeld 402ddafa2a Fix sslsni boolean to integer conversion for libpq compatibility
The sslsni connection parameter was being stored as a boolean (true/false)
in the database, but libpq expects integer values (1/0) for this parameter.
When a boolean value was passed to libpq, it resulted in SSL connection
failures with 'unexpected eof while reading' errors.

This fix converts boolean connection parameters (sslcompression, sslsni) to
integers when creating the connection string in server_manager.py, ensuring
libpq receives the expected integer format.

Fixes connection issues when using PGSSLSNI=1 with PostgreSQL 14+ servers.
2026-01-14 12:31:36 +05:30
Akshay Joshi 74c8b2d0a1 Updated release note. 2026-01-08 18:45:02 +05:30
Yogesh Mahajan bef374d023 Upgrade ID column in the database table to BigInteger to support large OID values. #9223 2026-01-08 18:38:02 +05:30
Pravesh Sharma a9d56402a1 Fixed an issue where connection parameters were using localized values instead of literal values, causing connection failures. #9500 2026-01-08 18:10:15 +05:30
Akshay Joshi 62e2d18b02 Mask the secret key for restrict option in the process watcher when restoring plain SQL file. #9518
Fixed coderabbit review comment.
2026-01-08 11:39:01 +05:30
Rohit Bhati 3b184dbeee Fixed an issue where the dashboard freezes on initial render when there is a high number of locks. #9285 2026-01-07 17:10:35 +05:30
Khushboo Vashi 71920c2903 Fixed the SSL certificate issue while the CA file is not configured. 2026-01-06 17:07:45 +05:30
Rohit Bhati d30fd5d67e Disable Parameters and Membership fields when object is not new for Login and group roles. #9350 2026-01-06 16:45:07 +05:30
Akshay Joshi d26aa0a8de 1) Added --no-cache-dir for OSX while installing requirements.txt
2) Updated release note.
2026-01-06 12:25:35 +05:30
Guiorgy f63f8f7342 Removed redundant constraint for Python version. 2026-01-06 12:06:12 +05:30
Yogesh Mahajan 1301e5d2ff Ensure saved shared server passwords are re-encrypted on password change. #9258 2026-01-06 11:29:42 +05:30
Aditya Toshniwal cc0377fb59 Code changes required by latest react-virtualized-auto-sizer package which was updated in the previous commit. 2026-01-05 17:10:28 +05:30
Akshay Joshi 79de352636 Updated Javascript dependencies. 2026-01-05 16:42:52 +05:30
Rohit Bhati 965a6ad886 Fix Query Tool state restoration for new connections and queries. #8987 2026-01-05 16:32:49 +05:30
Aditya Toshniwal c6551145a3 Optimize checkbox selection logic in backup dialog objects tree. #9110 2026-01-05 16:18:57 +05:30
Aditya Toshniwal 04583fe8f0 Fixed an issue where data filter dialog removes newline character when sending SQL to the query tool. #9260 2026-01-05 15:01:54 +05:30
Akshay Joshi a1d2308dc9 Updated certifi package 2026-01-05 13:34:16 +05:30
Akshay Joshi 52daa56a04 Copyright updated for 2026 2026-01-05 13:33:45 +05:30
Akshay Joshi 19532f534f Updated Python and Javascript dependencies. 2026-01-05 13:23:38 +05:30
Khushboo Vashi 7462a6a91f Fixed the SSL certificate issue while checking for the upgrade. #9293 2026-01-05 13:01:02 +05:30
Guiorgy c3da32f0f7 Correct erroneous comment in Dockerfile 2026-01-05 12:59:50 +05:30
Degit22 01e8d0c7e9 Update Russian translation. 2026-01-05 12:59:16 +05:30
Guiorgy 30f032b7d8 Optimisations in the Dockerfile
* mount .git from host instead of copying & removing
* use tmpfs for build cache in Dockerfile
* avoid rerunning apk when requirements.txt changes
2026-01-01 14:00:49 +05:30
Pravesh Sharma a9bf4c23bc Fixed a sorting issue in the system stats memory usage table. #9332 2026-01-01 11:28:33 +05:30
Dave Page a5e9315b89 Add warning about the new Windows cert to the release notes. 2025-12-23 12:37:54 +00:00
Dave Page cf123a3290 Update Windows code signing to work with new hardware key (or none) (#9483) 2025-12-23 12:32:03 +00:00
Akshay Joshi 44434948e2 Updated Python and Javascript dependencies. 2025-12-15 16:58:07 +05:30
Aditya Toshniwal a2ea57fc2e Fixed an issue where 'View/Edit Data' shortcut opened 'First 100 rows' instead of 'All Rows'. #9235 2025-12-15 13:47:54 +05:30
Aditya Toshniwal 5dd07d5d4b Fixed an issue where double click to open a file in the file manager is not working. #9196 2025-12-15 13:28:00 +05:30
Aditya Toshniwal 491fbe8a36 Add a new button in the query tool data output toolbar to get entire range of data. #8890 2025-12-12 15:38:01 +05:30
Aditya Toshniwal 63397a003a Fixed an issue where the Query History panel would auto-scroll to the top and did not preserve the scroll bar position for the selected entry. #9380 2025-12-12 11:27:39 +05:30
Domenico Sgarbossa 10669d60a6 Italian translation updated 2025-12-11 16:20:03 +05:30
Akshay Joshi 1457f08343 Updated Python and Javascript dependencies. 2025-12-11 16:02:44 +05:30
Khushboo Vashi dc801e362e Update version for release v9.11 2025-12-08 15:15:10 +05:30
Anil Sahoo 55a332be5b Fix an issue with the type casting of default string function/procedure arguments in the debugger tool. #9036 2025-12-08 14:24:00 +05:30
Akshay Joshi 51bd866a3a 1) Updated urllib3 version.
2) Updated release note.
2025-12-08 11:30:36 +05:30
Akshay Joshi d5a909f14c Plain SQL restore runs with '\restrict' option to prevent harmful psql meta-commands. #9368 2025-12-08 11:02:10 +05:30
Libor M. 5129905307 Czech translation for version 9.11 2025-12-08 11:01:08 +05:30
Khushboo Vashi 6f5c69a4c8 Specify the correct hostname placeholder for the Password Exec command. 2025-12-05 14:40:47 +05:30
Aditya Toshniwal dd51290d06 Upgrade React version to latest as upstream has fixed a security bug 2025-12-04 18:21:58 +05:30
Aditya Toshniwal d46bce2d7b Fixed an issue where copying highlighted text in the query tool data output cell editor would copy the complete string. #9372
Fixed an issue where copying a single cell should not add quoting. #9373
2025-12-04 16:58:39 +05:30
Khushboo Vashi e50d839ac0 Updated message catalogs for v9.11 2025-12-04 15:50:27 +05:30
Khushboo Vashi d26888c65c Update release notes. 2025-12-03 12:08:17 +05:30
Tobi 2ef67a5fa2 Ensure the proper handling of extra volume mount configurations in the Helm deployment template by correcting the configuration value references. #9408 2025-12-03 12:06:13 +05:30
Akshay Joshi c6406dbaa4 Fixed Javascript test cases. 2025-12-02 16:37:10 +05:30
Jun f55fcf7baf fix: copyright updater skips folders like 'modules' 2025-12-02 12:05:47 +05:30
Akshay Joshi 71719db3ab sphinx==9.0.0 required Python version >= 3.11. Back to version 7.4.7 2025-12-02 11:55:26 +05:30
Akshay Joshi 086fa9bf87 psycopg[c] v3.3.0 requires Python version 3.10 and above. 2025-12-02 11:32:17 +05:30
Akshay Joshi dc7873c2b2 Update Python and JavaScript dependecies. 2025-12-01 19:13:04 +05:30
Koren Peretz 56040aa116 Use -i instead of -I in the helm deployment YAML for best minimal practice. 2025-12-01 16:27:46 +05:30
Dave Page 9c8df1ad3b React string cleanup. 2025-12-01 16:18:27 +05:30
Dave Page e7db3510e1 Javascript string cleanup (#9405)
* Javascript string cleanup

* Further JS string fixes.
2025-12-01 10:17:30 +00:00
Guiorgy 0132ce2001 fixed .git copy destination inside Dockerfile 2025-12-01 12:17:22 +05:30
Dave Page 70e4435b04 Python string cleanup
* Python string cleanup
* Fix line lengths
* Further Python string fixes.
2025-12-01 11:06:21 +05:30
Dave Page ec3b508ba6 Cleanup a bunch of documentation issues. 2025-11-28 15:40:48 +00:00
Khushboo Vashi 7885ed7f87 Update release notes. 2025-11-27 11:03:40 +05:30
Koren Peretz d1b25c99cc Fix the Helm chart server definition and change the app version. #9393 2025-11-27 10:59:28 +05:30
Guiorgy c7909958da Optimised Dockerfile.
* Move the installation of dependencies to the top of the stage.
* Upgrade all system packages.
* Combine copy layers where possible.
2025-11-26 14:20:06 +05:30
Aditya Toshniwal 8c153be335 Fixed an issue where connection is failing via Query Tool/PSQL Tool workspaces. #9354 2025-11-25 12:05:22 +05:30
Akshay Joshi 65860fc12c Updated Python and JavaScript dependencies. 2025-11-24 18:25:11 +05:30
Akshay Joshi fc04e2eb1a Remove __gpg_sign_cmd as it is failing on Fedora 43 due to some obsolete options. 2025-11-24 13:58:18 +05:30
Guiorgy d481507569 Removed the RUN rm -rf layer from the Dockerfile app-builder stage; exclusions are now handled by updating .dockerignore. 2025-11-24 11:37:49 +05:30
Guiorgy 6d01a247db Ensure LF line endings on text files. 2025-11-21 12:16:35 +05:30
Nicolas Boulard 27779370d8 Add conditional TLS support for the Ingress in the Helm chart. #9345 2025-11-21 12:08:22 +05:30
Akshay Joshi 482549b951 Updated release note. 2025-11-19 12:35:17 +05:30
Rohit Bhati f06668aed9 Add support of DEPENDS/NO DEPENDS ON EXTENSION for INDEX. #6388 2025-11-19 12:32:35 +05:30
Guiorgy 4129d38410 Fix pkg_resources deprecation warning by migrating Docker base image to python:3-alpine from alpine:latest. #9155 2025-11-19 10:59:27 +05:30
Akshay Joshi c03b55042c Updated Python and Javascript dependencies. 2025-11-18 16:33:36 +05:30
Akshay Joshi f22aa1597f Updated release note 2025-11-18 16:21:13 +05:30
Rohit Bhati 467e496599 Add support of DEPENDS/NO DEPENDS ON EXTENSION for MATERIALIZED VIEW. #6390 2025-11-18 16:14:00 +05:30
Khushboo Vashi 7b85a09603 Fix Debian builds by executing sudo apt update. 2025-11-18 12:48:13 +05:30
Khushboo Vashi 57f88cf5af Improved Debian build process by ensuring package lists are refreshed during the build workflow. 2025-11-18 12:18:14 +05:30
Anil Sahoo d6b2379fbe Fixed an issue where EXPLAIN should run on query under cursor if no text is selected. #9297 2025-11-18 12:17:46 +05:30
Dave Page 39c2f397f7 Create .coderabbit.yaml 2025-11-14 10:46:12 +00:00
Khushboo Vashi 63ee81202b Updated version for v9.10. 2025-11-13 12:28:13 +05:30
Anil Sahoo 14dec7e5ed Add support for showing the column data type beside column name in the object explorer. #8968 2025-11-12 19:50:28 +05:30
Akshay Joshi 5e472ec42a Updated Python and Javascript dependencies. 2025-11-12 18:14:25 +05:30
Akshay Joshi 60c56b3285 Fixed API test case for restore. 2025-11-10 13:26:42 +05:30
Akshay Joshi f251fdfdb1 Updated version for release v9.10 2025-11-10 11:40:12 +05:30
Khushboo Vashi 09d2b7eeb0 1) Fixed LDAP authentication flow vulnerable to TLS certificate verification bypass (CVE-2025-12765). #9324
2) Fixed LDAP injection vulnerability in LDAP authentication flow (CVE-2025-12764). #9325
2025-11-10 11:27:56 +05:30
Yogesh Mahajan e374edc692 Fixed Command injection vulnerability allowing arbitrary command execution on Windows (CVE-2025-12763). #9323 2025-11-10 11:27:56 +05:30
Akshay Joshi 1d397395f7 Fixed remote code execution vulnerability when restoring PLAIN-format SQL dumps in server mode (CVE-2025-12762). #9320 2025-11-10 11:27:55 +05:30
Libor M. ed52a44b17 Updated Czech translation. 2025-11-10 11:13:33 +05:30
Muhammad Aqeel 06fb5a45c1 pgAdmin4 debian packages were not having release build number and platform distribution name. 2025-11-07 17:33:14 +05:30
Khushboo Vashi d1805c5640 Ensure that the Password Change and Application Restore features comply with FIPS standards. 2025-11-07 17:32:51 +05:30
Domenico Sgarbossa c04b9198c8 Updated Italian translation 2025-11-07 10:09:26 +05:30
Aditya Toshniwal ec63acc4c5 Updated message catalogs for v9.10 2025-11-06 15:55:36 +05:30
Aditya Toshniwal f906326003 Fixed an issue where the last used storage directory was reset to blank, leading to access denied errors during backup or restore operations. #9281 2025-11-06 15:43:06 +05:30
Aditya Toshniwal 39b6f40482 Fix following issues:
1. Debian builds are failing after commiting changes for #9240.
2. Object explorer context menu shows "No objects selected" on preferences change.
2025-11-04 16:49:12 +05:30
Akshay Joshi e6f6a90d2f Updated Python and JavaScript dependencies. 2025-11-03 18:39:10 +05:30
Akshay Joshi d80cc6f8f9 Updated release note. 2025-10-31 18:20:00 +05:30
Rohit Bhati 38ebb43bac Add support of DEPENDS/NO DEPENDS ON EXTENSION for PROCEDURE. #6391 2025-10-31 18:17:04 +05:30
Pravesh Sharma 48b4a8d448 Fixed an issue where the Select All option on the columns tab of import/export data was not working in languages other than English. #9233 2025-10-31 16:25:51 +05:30
Koren Peretz d9b3f950ad Fixed indentation issue in the extraEnvVars field, preserved the serverDefinitions template, corrected a typo in README.md, and increased resource allocations in the Helm chart. 2025-10-31 12:40:47 +05:30
Aditya Toshniwal 986ba41ba9 Added the ability to search for tables and automatically bring them into view in the ERD tool. #4306 2025-10-31 12:19:27 +05:30
Pravesh Sharma 08379d6ae0 Fixed an issue where the 2FA window redirected to the login page after session expiration. #9132 2025-10-31 11:17:04 +05:30
Yogesh Mahajan 7251c1f628 Fix github actions for pg17 for windows platform broken due to changes in github runner image. 2025-10-31 11:05:05 +05:30
LiZixin 061450c8d7 Fixed an issue where the Debian build process failed with a "Sphinx module not found" error when using a Python virtual environment. #9240 2025-10-31 10:38:04 +05:30
Akshay Joshi b62212b8e5 Fixed an issue that prevented assigning multiple users to an RLS policy. #9304 2025-10-30 18:54:20 +05:30
Anil Sahoo 282a956f4f Reverted changes in Python tests for EPAS/PG YAML files that were added to support Subscription RESQL test cases and adjust streaming values for PG v16+. 2025-10-30 12:50:33 +05:30
Aditya Toshniwal 00dbe58125 Add support for formatting .pgerd ERD project file. #8912 2025-10-29 16:22:37 +05:30
Aditya Toshniwal abdcd983f6 Add support for setting image download resolution in the ERD tool. #6698 2025-10-29 14:57:20 +05:30
Akshay Joshi c7a6056ee3 Fixed an issue where Schema Diff does not ignore Tablespace for indexes. #9117 2025-10-28 16:31:39 +05:30
Akshay Joshi 55c5e86fe0 1) Updated version of Authlib, psycopg[c], typer[all] and selenium.
2) Updated release note.
2025-10-28 11:45:57 +05:30
Royston Shufflebotham 8d55e9a445 Add support for displaying detailed Citus query plans instead of 'Custom Scan' placeholder. #7885 2025-10-28 10:47:55 +05:30
Akshay Joshi c1da777fba 1) Updated Russian translation.
2) Updated electron version.
3) Updated release note
2025-10-27 11:26:08 +05:30
AKY 9e35c32ef2 Updated Korean translation 2025-10-27 11:17:45 +05:30
Aditya Toshniwal 37a164cccb - Rebase pgadmin-org/react-data-grid fork to latest.
- Fixed an issue where data output column resize is not sticking in Safari. #8504
2025-10-27 10:45:36 +05:30
Aditya Toshniwal c20d035f1b Update Python and JS packages. 2025-10-24 08:37:55 +05:30
Aditya Toshniwal 9a44fee646 Update release notes. 2025-10-24 08:29:01 +05:30
Aditya Toshniwal 0a14db01be Migrate pgAdmin UI to use React 19. #8676 2025-10-24 08:26:10 +05:30
Anil Sahoo 53a994ba1e Fixed an issue where Python tests for EPAS github actions were failing due to a port mismatch with RESQL test case files for Subscription. 2025-10-22 18:14:12 +05:30
Aditya Toshniwal 91ad54d17b Fixed an issue where query tool shortcut would trigger the callback twice. #9157 2025-10-14 14:33:17 +05:30
Akshay Joshi ea5ee74205 Update version for release v9.9 2025-10-13 20:51:55 +05:30
Anil Sahoo 5f22b4e2d2 Fixed following issues related to auto-update:
1. Fixed the issue where auto-update was not working for macOS x64 arch machines as pgadmin4 zip file name has x86_64 in it.
2. Improved error handling in the /upgrade_check API by replacing the static “Failed to check for update” message for Windows users with a dynamic error message.
3. Fixed the CSS issue affecting the close icon in the warning notifier.
4. Removed trailing periods from helper texts and notifier messages in the app’s auto-update workflow. #9133
2025-10-13 17:25:14 +05:30
Rohit Bhati 758bbc7272 Enable depends on extension option on for functions. 2025-10-13 11:39:37 +05:30
rlbxku1r 618e03b65f Update Japanese translation for v9.9 2025-10-13 11:38:35 +05:30
Akshay Joshi e9b97d61f8 1) Pin pywinpty due to https://github.com/andfoy/pywinpty/issues/545
2) Remove scram_client_key and scram_server_key from the connection string parameter
   as it is not meant to be specified directly by users or client applications.
2025-10-10 13:07:32 +05:30
Domenico Sgarbossa 83377d4a50 Italian translation updated for v9.9 2025-10-10 13:06:34 +05:30
Aditya Toshniwal c03d21899b Updated message catalogs for v9.9 2025-10-10 10:07:42 +05:30
Koren Peretz d03a0cdce6 Add support for initializing the pgAdmin4 Kubernetes Helm chart. #9225 2025-10-08 14:41:00 +05:30
Akshay Joshi f3567518d3 Added option to skip the password dialog when using an identity file. #6996
1. Don't ask for the SSH Tunnel or Database server password if it is already saved.
2. Modified the generic error message from the SSH Tunnel library.
2025-10-07 13:13:37 +05:30
Akshay Joshi b30520adc0 Updated Python and Javascript dependencies. 2025-10-06 17:33:12 +05:30
Yogesh Mahajan c6af61afd3 Implement pre and post scenario SQL execution in subscription ResQL tests to manage publication lifecycle. #8932 2025-10-03 12:12:15 +05:30
Akshay Joshi 04024c7f61 Updated Python and Javascript dependencies. 2025-10-01 15:27:23 +05:30
Rohit Bhati 9e6ab295df Add support of DEPENDS/NO DEPENDS ON EXTENSION for ALTER FUNCTION. #6385 2025-10-01 13:49:03 +05:30
Akshay Joshi 5c9acc1c3d 1) Added PG 18 in the github runner.
2) Added PG 18 in the docker container file.
2025-09-29 13:40:03 +05:30
Anil Sahoo c75bba03c2 1) Added 'failover' parameter support in CREATE and ALTER SUBSCRIPTION for PostgreSQL v17+. #8932
2) Added 'two_phase' parameter support for ALTER SUBSCRIPTION for PostgreSQL v18+.
3) Updated versioned_template_loader.py to prioritize v18+ templates.
4) Updated the default value of the streaming parameter in CREATE SUBSCRIPTION to 'parallel' in PG v18 (previously false).
2025-09-29 12:52:47 +05:30
Aditya Toshniwal 93bbadb994 Fixed an issue where the query tool displayed 'default' instead of 'null' for null text data in the data output. #9098 2025-09-29 12:37:25 +05:30
Akshay Joshi 42ff67c845 Updated Python and Javascript dependencies. 2025-09-25 15:50:14 +05:30
Yogesh Mahajan 5376d88444 Update the PATH variable with the newly installed EPAS directory to use the correct pg_config. 2025-09-24 15:55:10 +05:30
Yogesh Mahajan f733d124bd Update the PATH variable with the newly installed PostgreSQL directory to use the correct pg_config. 2025-09-24 14:55:56 +05:30
Aditya Toshniwal 7ff3c4bbeb Fixed an issue where shortcuts are not working as expected on multiple keyboard layouts. #9157 2025-09-17 11:34:15 +05:30
Aditya Toshniwal e5590797c0 Fixed an issue where saving the newly changed preferences was not reflecting on the preferences tab. #9158 2025-09-16 14:09:11 +05:30
Akshay Joshi 8576a5e7e6 Updated Javascript dependencies. 2025-09-15 09:45:05 +02:00
Akshay Joshi 3e66b6f7be 1) Added "MULTIRANGE_TYPE_NAME" option while creating a Range Type. #6394
2) Added "SUBSCRIPT" option while creating an External Type. #6395
2025-09-11 14:27:27 +05:30
Akshay Joshi 2fd2a144f1 Updated Python and Javascript dependecies. 2025-09-08 16:34:56 +05:30
Pravesh Sharma 500a5c7101 Fixed an issue where the pgAdmin configuration database wasn't being created on a fresh install when an external database was used for the configuration. #9125 2025-09-02 19:25:04 +05:30
Khushboo Vashi 5498682934 Update version for release v9.8 2025-09-01 16:39:08 +05:30
Khushboo Vashi cdeb18fcbb Fixed Cross-Origin Opener Policy (COOP) vulnerability in the OAuth 2.0 authentication flow (CVE-2025-9636). #9114 2025-09-01 16:34:18 +05:30
Aditya Toshniwal 0de635cc4c Fixed an issue where editor shortcuts fail when using Option key combinations on macOS, due to macOS treating Option+Key as a different key input. #9116 2025-09-01 16:21:03 +05:30
Akshay Joshi e09af0a0e9 Updated release note. 2025-08-28 16:18:38 +05:30
Akshay Joshi ade57d4ec4 Updated Python and JavaScript dependencies. 2025-08-28 12:56:20 +05:30
Rohit Bhati fc41c795f1 Add menu items for truncating foreign tables. #6396 2025-08-28 10:23:46 +05:30
Pravesh Sharma 7caaf2de82 Fixed an issue where pgAdmin config migration was failing while upgrading to v9.7 #9095 2025-08-28 10:15:35 +05:30
Akshay Joshi 09bc544e83 Pin Paramiko to version 3.5.1 to fix the DSSKey error introduced in the latest release. #9090 2025-08-25 18:09:45 +05:30
Domenico Sgarbossa 42a47126aa Italian translation update 2025-08-25 18:08:23 +05:30
Aditya Toshniwal db87df8f82 Remove usage of BrowserFS as it is deprecated. #7448 2025-08-25 14:34:33 +05:30
Aditya Toshniwal 15c212a2df Change the default pgAdmin theme to System. #9093 2025-08-25 12:33:27 +05:30
Yogesh Mahajan 68627ab44e Allow user to configure security related gunicorn parameters. #8891 2025-08-25 11:52:07 +05:30
Pravesh Sharma 800fdce316 Unpin Electron version 35.x as the GTK crash issue has been resolved upstream. 2025-08-22 11:32:58 +05:30
Akshay Joshi a5557b466c Updated Python and Javascript dependencies. 2025-08-21 13:54:07 +05:30
3573 changed files with 157386 additions and 128654 deletions

No files matched your search

+186
View File
@@ -0,0 +1,186 @@
language: en-US
tone_instructions: ''
early_access: false
enable_free_tier: true
reviews:
profile: chill
request_changes_workflow: false
high_level_summary: true
high_level_summary_placeholder: '@coderabbitai summary'
high_level_summary_in_walkthrough: false
auto_title_placeholder: '@coderabbitai'
auto_title_instructions: ''
review_status: true
commit_status: true
fail_commit_status: false
collapse_walkthrough: false
changed_files_summary: true
sequence_diagrams: true
estimate_code_review_effort: true
assess_linked_issues: true
related_issues: true
related_prs: true
suggested_labels: true
labeling_instructions: []
auto_apply_labels: false
suggested_reviewers: true
auto_assign_reviewers: false
in_progress_fortune: true
poem: false
path_filters: []
path_instructions: []
abort_on_close: true
disable_cache: false
auto_review:
enabled: true
auto_incremental_review: true
ignore_title_keywords: []
labels: []
drafts: false
base_branches: []
ignore_usernames: []
finishing_touches:
docstrings:
enabled: true
unit_tests:
enabled: true
pre_merge_checks:
docstrings:
mode: warning
threshold: 80
title:
mode: warning
requirements: ''
description:
mode: warning
issue_assessment:
mode: warning
custom_checks: []
tools:
ast-grep:
rule_dirs: []
util_dirs: []
essential_rules: true
packages: []
shellcheck:
enabled: true
ruff:
enabled: true
markdownlint:
enabled: true
github-checks:
enabled: true
timeout_ms: 900000
languagetool:
enabled: true
enabled_rules: []
disabled_rules: []
enabled_categories: []
disabled_categories: []
enabled_only: false
level: default
biome:
enabled: true
hadolint:
enabled: true
swiftlint:
enabled: true
phpstan:
enabled: true
level: default
phpmd:
enabled: true
phpcs:
enabled: true
golangci-lint:
enabled: true
yamllint:
enabled: true
gitleaks:
enabled: true
checkov:
enabled: true
detekt:
enabled: true
eslint:
enabled: true
flake8:
enabled: true
rubocop:
enabled: true
buf:
enabled: true
regal:
enabled: true
actionlint:
enabled: true
pmd:
enabled: true
clang:
enabled: true
cppcheck:
enabled: true
semgrep:
enabled: true
circleci:
enabled: true
clippy:
enabled: true
sqlfluff:
enabled: true
prismaLint:
enabled: true
pylint:
enabled: true
oxc:
enabled: true
shopifyThemeCheck:
enabled: true
luacheck:
enabled: true
brakeman:
enabled: true
dotenvLint:
enabled: true
htmlhint:
enabled: true
checkmake:
enabled: true
osvScanner:
enabled: true
chat:
art: true
auto_reply: true
integrations:
jira:
usage: auto
linear:
usage: auto
knowledge_base:
opt_out: false
web_search:
enabled: true
code_guidelines:
enabled: true
filePatterns: []
learnings:
scope: auto
issues:
scope: auto
jira:
usage: auto
project_keys: []
linear:
usage: auto
team_keys: []
pull_requests:
scope: auto
mcp:
usage: auto
disabled_servers: []
code_generation:
docstrings:
language: en-US
path_instructions: []
unit_tests:
path_instructions: []
+1
View File
@@ -1,4 +1,5 @@
web/node_modules
web/config_*.py
web/*.log
web/regression
web/**/tests/
+2
View File
@@ -0,0 +1,2 @@
# Auto detect text files and normalize end of lines to LF (\n)
* text=auto eol=lf
+82
View File
@@ -1,4 +1,11 @@
version: 2
# A note on the "groups" blocks below: minor and patch updates are batched into
# a single PR per manifest per week, whilst major updates continue to arrive
# individually. Majors are where the breakage lives and each one wants its own
# review, whereas a dozen separate PRs for patch bumps is pure overhead.
# Grouping applies to version updates only, so Dependabot security updates are
# unaffected and still arrive as their own PRs.
updates:
- package-ecosystem: "docker"
directory: "/"
@@ -17,6 +24,20 @@ updates:
- "Dependencies"
commit-message:
prefix: "Python dependency"
groups:
python-minor-and-patch:
patterns:
- "*"
update-types:
- "minor"
- "patch"
# paramiko 5.0 removed DSSKey, which sshtunnel 0.4.0 still references in
# SSHTunnelForwarder.get_keys(); a major bump therefore breaks every SSH
# tunnelled connection at construction time. sshtunnel has had no release
# since 2019, so this stays put until it is fixed or replaced. See #9927.
ignore:
- dependency-name: "paramiko"
update-types: ["version-update:semver-major"]
- package-ecosystem: "pip"
directory: "/tools"
@@ -26,7 +47,17 @@ updates:
- "Dependencies"
commit-message:
prefix: "Python dependency"
groups:
tools-python-minor-and-patch:
patterns:
- "*"
update-types:
- "minor"
- "patch"
# Note that web/regression/requirements.txt begins with
# "-r ../../requirements.txt", so this entry also sees everything pinned in
# the root file and the paramiko exclusion has to be repeated here.
- package-ecosystem: "pip"
directory: "/web/regression"
schedule:
@@ -35,6 +66,16 @@ updates:
- "Dependencies"
commit-message:
prefix: "Python dependency"
groups:
regression-python-minor-and-patch:
patterns:
- "*"
update-types:
- "minor"
- "patch"
ignore:
- dependency-name: "paramiko"
update-types: ["version-update:semver-major"]
- package-ecosystem: "npm"
directory: "/runtime"
@@ -44,6 +85,13 @@ updates:
- "Dependencies"
commit-message:
prefix: "Javascript dependency"
groups:
runtime-javascript-minor-and-patch:
patterns:
- "*"
update-types:
- "minor"
- "patch"
- package-ecosystem: "npm"
directory: "/web"
@@ -53,3 +101,37 @@ updates:
- "Dependencies"
commit-message:
prefix: "Javascript dependency"
groups:
web-javascript-minor-and-patch:
patterns:
- "*"
update-types:
- "minor"
- "patch"
ignore:
# @testing-library/jest-dom 7 requires Node >= 22, whilst Node 20 is the
# minimum we support and what most of the buildfarm runs. Revisit once
# the buildfarm moves to Node 22. See #10271 and #10210.
- dependency-name: "@testing-library/jest-dom"
update-types: ["version-update:semver-major"]
# Babel 8 requires Node ^22.18.0 || >=24.11.0, and every 8.x package
# peer-depends on @babel/core ^8.0.0 whilst we are on 7.x. Taking any of
# them individually leaves an unsatisfiable peer requirement and a mixed
# 7/8 tree, so Babel 8 needs a coordinated migration across all ten
# @babel/* packages once we are on Node 22. See #10283, #10284, #10285.
- dependency-name: "@babel/*"
update-types: ["version-update:semver-major"]
# pickr 1.10 switched to a dual CJS/ESM build whose CommonJS entry point
# exports a namespace object rather than the class, so Babel's interop
# leaves _pickr.default as an object and every dialog with a colour field
# dies on mount. That took master's feature tests down for three days
# (#10264, reverted in #10278). Note 1.9 -> 1.10 is a *minor* bump, so it
# otherwise rejoins the grouped PR and comes back around; minor updates
# are excluded here as well as majors until withColorPicker.js is adapted
# to the new export shape.
- dependency-name: "@simonwep/pickr"
update-types:
- "version-update:semver-major"
- "version-update:semver-minor"
+5 -4
View File
@@ -19,10 +19,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Upgrade yarn
run: |
yarn set version berry
yarn set version 4
# Corepack reads packageManager from web/package.json and provisions
# exactly that Yarn: a single pinned download, with nothing written
# into the checkout.
- name: Enable Corepack
run: corepack enable
- name: Install Node modules
run: |
+6 -5
View File
@@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pgver: [13, 14, 15, 16, 17]
pgver: [14, 15, 16, 17, 18]
runs-on: ubuntu-22.04
@@ -141,10 +141,11 @@ jobs:
}
EOF
- name: Upgrade yarn
run: |
yarn set version berry
yarn set version 4
# Corepack reads packageManager from web/package.json and provisions
# exactly that Yarn: a single pinned download, with nothing written
# into the checkout.
- name: Enable Corepack
run: corepack enable
- name: Build the JS bundle
run: |
+6 -5
View File
@@ -20,7 +20,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pgver: [13, 14, 15, 16, 17]
pgver: [14, 15, 16, 17, 18]
runs-on: ubuntu-22.04
@@ -151,10 +151,11 @@ jobs:
}
EOF
- name: Upgrade yarn
run: |
yarn set version berry
yarn set version 4
# Corepack reads packageManager from web/package.json and provisions
# exactly that Yarn: a single pinned download, with nothing written
# into the checkout.
- name: Enable Corepack
run: corepack enable
- name: Build the JS bundle
run: |
+5 -4
View File
@@ -28,10 +28,11 @@ jobs:
- uses: actions/checkout@v4
- name: Upgrade yarn
run: |
yarn set version berry
yarn set version 4
# Corepack reads packageManager from web/package.json and provisions
# exactly that Yarn: a single pinned download, with nothing written
# into the checkout.
- name: Enable Corepack
run: corepack enable
- name: Install Node modules
run: |
+21 -2
View File
@@ -32,7 +32,18 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-22.04, windows-latest]
pgver: [13, 14, 15, 16, 17]
pgver: [14, 15, 16, 17, 18]
postgisver: [34]
exclude:
- pgver: 18
postgisver: 34
include:
- os: ubuntu-22.04
pgver: 18
postgisver: 36
- os: windows-latest
pgver: 18
postgisver: 36
runs-on: ${{ matrix.os }}
@@ -53,7 +64,7 @@ jobs:
if: ${{ matrix.os == 'ubuntu-22.04' }}
run: |
sudo apt update
sudo apt install -y libpq-dev libffi-dev libssl-dev libkrb5-dev zlib1g-dev edb-as${{ matrix.pgver }}-server edb-as${{ matrix.pgver }}-server-pldebugger edb-as${{ matrix.pgver }}-postgis34
sudo apt install -y libpq-dev libffi-dev libssl-dev libkrb5-dev zlib1g-dev edb-as${{ matrix.pgver }}-server edb-as${{ matrix.pgver }}-server-pldebugger edb-as${{ matrix.pgver }}-postgis${{ matrix.postgisver }}
- name: Install pgagent on Linux
if: ${{ matrix.os == 'ubuntu-22.04' && matrix.pgver <= 16 }}
@@ -73,6 +84,14 @@ jobs:
IF %ERRORLEVEL% EQU 3010 cmd /c "exit /b 0"
shell: cmd
- name: Add pg_config path to PATH
shell: pwsh
run: |
$pgPath = "C:\\EPAS\\${{ matrix.pgver }}\\bin"
Write-Output $pgPath
Add-Content -Path $env:GITHUB_PATH -Value $pgPath
pg_config --version
- name: Create the tablespace directory on Linux
if: ${{ matrix.os == 'ubuntu-22.04' }}
run: |
+11 -2
View File
@@ -21,7 +21,7 @@ jobs:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-22.04, windows-latest]
pgver: [13, 14, 15, 16, 17]
pgver: [14, 15, 16, 17, 18]
runs-on: ${{ matrix.os }}
@@ -64,6 +64,7 @@ jobs:
brew install postgresql@${{ matrix.pgver }}
echo "/opt/homebrew/opt/postgresql@${{ matrix.pgver }}/bin" >> $GITHUB_PATH
- name: Uninstall PostgreSQL if already present on windows
if: ${{ matrix.os == 'windows-latest' }}
run: |
@@ -78,13 +79,21 @@ jobs:
run: |
FOR /f "delims=" %%F IN ('python tools\get_sb_package.py "https://www.postgresql.org/applications-v2.xml" "postgresql_${{ matrix.pgver }}" "windows-x64"') DO SET INSTALLER_EXE=%%F
ECHO Running %INSTALLER_EXE%...
%INSTALLER_EXE% --prefix C:\PostgreSQL\${{ matrix.pgver }} --datadir C:\PostgreSQL\${{ matrix.pgver }}\data --serverport 59${{ matrix.pgver }} --superpassword postgres --install_runtimes 0 --mode unattended --unattendedmodeui none --disable-components pgAdmin,stackbuilder --enable-components server,commandlinetools"
%INSTALLER_EXE% --prefix C:\PostgreSQL_custom\${{ matrix.pgver }} --datadir C:\PostgreSQL_custom\${{ matrix.pgver }}\data --serverport 59${{ matrix.pgver }} --superpassword postgres --install_runtimes 0 --mode unattended --unattendedmodeui none --disable-components pgAdmin,stackbuilder --enable-components server,commandlinetools"
choco install -y mitkerberos
REM Ignore error 3010 (reboot required)
IF %ERRORLEVEL% EQU 3010 cmd /c "exit /b 0"
shell: cmd
- name: Add pg_config path to PATH
shell: pwsh
run: |
$pgPath = "C:\\PostgreSQL_custom\\${{ matrix.pgver }}\\bin"
Write-Output $pgPath
Add-Content -Path $env:GITHUB_PATH -Value $pgPath
pg_config --version
- name: Create the tablespace directory on Linux
if: ${{ matrix.os == 'ubuntu-22.04' }}
run: |
@@ -0,0 +1,204 @@
name: Run Python tests in server mode (data isolation)
# This workflow runs the data-isolation tests with SERVER_MODE=True.
# The main run-python-tests-pg.yml workflow runs everything with
# SERVER_MODE=False, so any test that gates itself on `config.SERVER_MODE`
# (notably web/pgadmin/browser/server_groups/tests/test_sg_data_isolation.py
# and web/pgadmin/browser/server_groups/servers/tests/test_server_data_isolation.py)
# skips itself in CI. That gap allowed the admin-bypass regression in
# 9a76ed8 to ship; this workflow exists so regressions to the centralized
# access-control helpers in web/pgadmin/utils/server_access.py are caught.
#
# Scope is intentionally narrow: one OS, one PG version, only the
# data-isolation modules. Keep it cheap to run on every PR.
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
workflow_dispatch:
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
run-python-tests-server-mode:
runs-on: ubuntu-24.04
env:
PG_VER: 18
steps:
- uses: actions/checkout@v4
with:
# We don't run any authenticated git operations after checkout
# (no push, no submodule fetch, no comment back to the PR), so
# persisting the token in the local git config only widens the
# blast radius of an unrelated step compromise.
persist-credentials: false
- name: Update python version
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Setup the PGDG APT repo
# apt-key is deprecated on Ubuntu 24.04+; install the key into
# /etc/apt/keyrings/ and reference it via signed-by= in the
# sources.list entry instead.
run: |
sudo install -d -m 0755 /etc/apt/keyrings
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc \
| sudo gpg --dearmor -o /etc/apt/keyrings/pgdg.gpg
sudo sh -c 'echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
- name: Uninstall PostgreSQL if already present
run: |
if [ -n "$(ls /etc/postgresql/*/*/postgresql.conf 2>/dev/null)" ]; then
installed_pg_version=$( pg_config --version | cut -d ' ' -f 2 | cut -d '.' -f 1 )
echo "Installed PostgreSQL version: $installed_pg_version"
if [ $installed_pg_version != ${PG_VER} ]; then
sudo pg_dropcluster $installed_pg_version main --stop
sudo apt-get -y remove "postgresql-${installed_pg_version}"
fi
fi
- name: Install platform dependencies
run: |
sudo apt update
sudo apt install -y libpq-dev libffi-dev libssl-dev libkrb5-dev zlib1g-dev postgresql-${PG_VER}
- name: Start PostgreSQL
run: |
sudo su -c "echo local all all trust > /etc/postgresql/${PG_VER}/main/pg_hba.conf"
sudo sed -i "s/port = 543[0-9]/port = 59${PG_VER}/g" /etc/postgresql/${PG_VER}/main/postgresql.conf
sudo su - postgres -c "/usr/lib/postgresql/${PG_VER}/bin/postgres -D /var/lib/postgresql/${PG_VER}/main -c config_file=/etc/postgresql/${PG_VER}/main/postgresql.conf &"
# Bounded readiness wait: 60 attempts × 2s = 120s cap. If PG
# never becomes ready, fail fast with logs rather than hanging
# until the job timeout.
for attempt in $(seq 1 60); do
if sudo runuser -l postgres -c "pg_isready -p 59${PG_VER}" 2>/dev/null; then
break
fi
>&2 echo "Postgres is unavailable (attempt ${attempt}/60) - sleeping for 2 seconds"
sleep 2
if [ "$attempt" -eq 60 ]; then
>&2 echo "Postgres failed to become ready within 120 seconds"
sudo tail -n 200 /var/log/postgresql/postgresql-${PG_VER}-main.log || true
exit 1
fi
done
- name: Install Python dependencies
run: make install-python-testing
- name: Create the test configuration (SERVER_MODE=True)
run: |
mkdir -p var
cat <<EOF > web/config_local.py
from config import *
# Debug mode
DEBUG = True
# App mode — this is the whole point of this workflow.
SERVER_MODE = True
# Log
CONSOLE_LOG_LEVEL = DEBUG
FILE_LOG_LEVEL = DEBUG
DEFAULT_SERVER = '127.0.0.1'
UPGRADE_CHECK_ENABLED = False
LOG_FILE = "$(pwd)/var/pgadmin4.log"
SESSION_DB_PATH = "$(pwd)/var/sessions"
STORAGE_DIR = "$(pwd)/var/storage"
SQLITE_PATH = "$(pwd)/var/pgadmin4.db"
TEST_SQLITE_PATH = "$(pwd)/var/pgadmin4.db"
AZURE_CREDENTIAL_CACHE_DIR = "$(pwd)/var/azurecredentialcache"
EOF
cat <<EOF > web/regression/test_config.json
{
"pgAdmin4_login_credentials": {
"new_password": "NEWPASSWORD",
"login_password": "PASSWORD",
"login_username": "USER@EXAMPLE.COM"
},
"pgAdmin4_test_user_credentials": {
"new_password": "NEWPASSWORD",
"login_password": "PASSWORD",
"login_username": "USER2@EXAMPLE.COM"
},
"pgAdmin4_test_non_admin_credentials": {
"new_password": "NEWPASSWORD",
"login_password": "PASSWORD",
"login_username": "USER3@EXAMPLE.COM"
},
"server_group": 1,
"server_credentials": [
{
"name": "PostgreSQL ${{ env.PG_VER }}",
"comment": "PostgreSQL ${{ env.PG_VER }} Server",
"db_username": "postgres",
"host": "/var/run/postgresql",
"db_password": "postgres",
"db_port": 59${{ env.PG_VER }},
"maintenance_db": "postgres",
"sslmode": "prefer",
"tablespace_path": "/var/lib/postgresql/tablespaces/${{ env.PG_VER }}",
"enabled": true,
"default_binary_paths": {
"pg": "/usr/lib/postgresql/${{ env.PG_VER }}/bin",
"ppas": ""
}
}
],
"server_update_data": [
{
"comment": "This is test update comment"
}
]
}
EOF
- name: Run server-group data isolation tests
run: |
. venv/bin/activate
python web/regression/runtests.py --pkg browser.server_groups.tests --modules test_sg_data_isolation
- name: Run server data isolation tests
run: |
. venv/bin/activate
python web/regression/runtests.py --pkg browser.server_groups.servers.tests --modules test_server_data_isolation
- name: Run SQL Editor unauth-pickle regression test
# Regression guard for the CSRF-only-no-auth chain on
# /sqleditor/close and /sqleditor/.../update_connection: both
# endpoints reach pickle.loads via close_sqleditor_session() /
# check_transaction_status(), so they must require an
# authenticated session. This test is a no-op in DESKTOP mode
# (auto-re-authentication) so it can only run here.
run: |
. venv/bin/activate
python web/regression/runtests.py --pkg tools.sqleditor --modules test_close_requires_auth
- name: Archive server log
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: server-log-server-mode
path: var/pgadmin4.log
- name: Archive regression log
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: regression-log-server-mode
path: web/regression/regression.log
+2 -2
View File
@@ -43,7 +43,7 @@ jobs:
sonar.test.inclusions=**/tests/**, web/regression
# Python compatibility
sonar.python.version=3.7, 3.8, 3.9, 3.10, 3.11
sonar.python.version=3.9, 3.10, 3.11, 3.12, 3.13, 3.14
EOF
APP_RELEASE=`grep "^APP_RELEASE" web/config.py | cut -d"=" -f2 | sed 's/ //g'`
@@ -52,7 +52,7 @@ jobs:
sed -i "s/%VERSION%/${APP_LONG_VERSION}/g" sonar-project.properties
- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
uses: sonarsource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8.2.1
env:
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST }}
+1
View File
@@ -59,5 +59,6 @@ auditpy.txt
web/coverage/
web/.coverage
web/regression/.coverage
.worktree/
web/regression/covhtml/
web/regression/htmlcov/
+341
View File
@@ -0,0 +1,341 @@
# Contributing to pgAdmin 4
Thank you for your interest in contributing to pgAdmin 4.
## Contents
- Getting Started
- Reporting Issues
- Making Changes
- Pull Requests
- AI-assisted Contributions
- Development Setup
- Database Migrations
- Security Issues
- Community and Support
- License
## Getting Started
Before starting work, please review the project documentation and familiarize yourself with the architecture and development workflow.
### Architecture
pgAdmin 4 is written as a web application with Python (Flask) on the server side and ReactJS, HTML5 with CSS for the client side processing and UI.
Although developed using web technologies, pgAdmin 4 can be deployed either on a web server using a browser, or standalone on a workstation. The [runtime/](runtime/) subdirectory contains an Electron-based runtime application intended to allow this, which will fork a Python server process and display the UI.
For a fuller introduction to the codebase, see the [code overview](docs/en_US/code_overview.rst).
If you are new to the codebase, consider starting with smaller bug fixes or documentation improvements before working on larger changes.
The Sphinx documentation also includes contributor guidance for [coding standards](docs/en_US/coding_standards.rst), [submitting pull requests](docs/en_US/submitting_pull_requests.rst), [code review](docs/en_US/code_review.rst), and [translations](docs/en_US/translations.rst).
## Reporting Issues
When reporting a bug:
- Search existing issues first.
- If a similar issue exists, add any additional information or context you have. Do not create a duplicate issue.
- Provide clear steps to reproduce the issue.
- Include relevant environment and version information.
- Attach screenshots, logs, or error messages when appropriate.
For feature requests, describe the problem being solved and the expected behavior.
## Making Changes
When working on a contribution:
- Keep changes focused and easy to review.
- Follow existing coding patterns and conventions, including the [coding standards](docs/en_US/coding_standards.rst).
- Avoid unrelated refactoring and changes in the same commit.
- Keep commit messages clear and descriptive.
- Update tests or documentation when necessary.
## Pull Requests
Before opening a pull request:
- Ensure your changes build successfully.
- Test your changes locally.
- Review your code before submission.
- Update your branch with the latest changes from `master` upstream when appropriate.
When opening a pull request:
- Clearly describe the change.
- Explain the reason for the change.
- Include screenshots or recordings for UI-related changes when applicable.
- Submit any changes as Pull Requests against the `master` branch of the [pgadmin-org/pgadmin4](https://github.com/pgadmin-org/pgadmin4) repository.
Use a clear pull request title that describes the change. Short prefixes such as `feat:`, `fix:`, `docs:`, `test:`, `refactor:`, and `chore:` are useful when they match the change being made. Branch names are contributor-local and do not need to follow a project-specific naming convention.
For more detail, see the [pull request submission guide](docs/en_US/submitting_pull_requests.rst) and [code review guide](docs/en_US/code_review.rst).
## AI-assisted Contributions
Contributors are responsible for understanding, testing, and standing behind everything they submit, whether or not AI tools were used. Please do not open large numbers of auto-generated issues or pull requests without first discussing that work with the maintainers.
## Development Setup
In the following documentation and examples, `$PGADMIN4_SRC` is used to denote the top-level directory of your copy of the pgAdmin source tree (either from a tarball or a git checkout).
### Prerequisites
1. Install Node.js 20 and above (https://nodejs.org/en/download)
2. yarn (https://yarnpkg.com/getting-started/install)
3. Python 3.9 and above (https://www.python.org/downloads/)
4. PostgreSQL server (https://www.postgresql.org/download)
Start by enabling Corepack, if it isn't already; this will add the yarn binary to your PATH:
```bash
corepack enable
```
### Building Web Assets
pgAdmin is dependent on a number of third-party JavaScript libraries. These, along with its own JavaScript code, CSS code and images must be compiled into a "bundle" which is transferred to the browser for execution and rendering. This is far more efficient than simply requesting each asset as it is needed by the client.
To create the bundle, you will need the 'yarn' package management tool to be installed. Then, you can run the following commands on a *nix system to download the required packages and build the bundle:
```bash
$ cd $PGADMIN4_SRC
$ make install-node
$ make bundle
```
On Windows systems (where "make" is not available), the following commands can be used:
```bat
C:\> cd $PGADMIN4_SRC\web
C:\$PGADMIN4_SRC\web> yarn install
C:\$PGADMIN4_SRC\web> yarn run bundle
```
### Python Environment
In order to run the Python code, a suitable runtime environment is required. Python version 3.9 and later are currently supported. It is recommended that a Python virtual environment is set up for this purpose, rather than using the system Python environment. On Linux and Mac systems, the process is fairly simple - adapt as required for your distribution:
1. Create a virtual environment in an appropriate directory. The last argument is the name of the environment; that can be changed as desired:
```bash
$ python3 -m venv venv
```
2. Now activate the virtual environment:
```bash
$ source venv/bin/activate
```
On Windows:
```bash
venv\Scripts\activate
```
3. Some of the components used by pgAdmin require a very recent version of *pip*, so update that to the latest:
```bash
(venv) $ pip install --upgrade pip
```
4. Ensure that a PostgreSQL installation's bin/ directory is in the path (so pg_config can be found for building psycopg3), and install the required packages:
```bash
(venv) $ PATH=$PATH:/usr/local/pgsql/bin pip install -r $PGADMIN4_SRC/requirements.txt
```
If you are planning to run the regression tests, you also need to install additional requirements from `web/regression/requirements.txt`:
```bash
(venv) $ pip install -r $PGADMIN4_SRC/web/regression/requirements.txt
```
### Configuration
Create a local configuration file for pgAdmin. Edit `$PGADMIN4_SRC/web/config_local.py` and add any desired configuration options (use the `config.py` file as a reference - any settings duplicated in `config_local.py` will override those in `config.py`). A typical development configuration may look like:
```python
import os
import logging
# Change pgAdmin data directory
DATA_DIR = '/Users/myuser/.pgadmin_dev'
# Change pgAdmin server and port
DEFAULT_SERVER = '127.0.0.1'
DEFAULT_SERVER_PORT = 5051
# Switch between server and desktop mode
SERVER_MODE = True
# Change pgAdmin config DB path in case an external DB is used.
CONFIG_DATABASE_URI="postgresql://postgres:postgres@localhost:5436/pgadmin"
# Set up SMTP
MAIL_SERVER = 'smtp.gmail.com'
MAIL_PORT = 465
MAIL_USE_SSL = True
MAIL_USERNAME = 'user@gmail.com'
MAIL_PASSWORD = 'xxxxxxxxxx'
# Change log level
CONSOLE_LOG_LEVEL = logging.INFO
FILE_LOG_LEVEL = logging.INFO
# Use a different config DB for each server mode.
if SERVER_MODE == False:
SQLITE_PATH = os.path.join(
DATA_DIR,
'pgadmin4-desktop.db'
)
else:
SQLITE_PATH = os.path.join(
DATA_DIR,
'pgadmin4-server.db'
)
```
This configuration allows easy switching between server and desktop modes for testing.
### Running pgAdmin
The initial setup of the configuration database is interactive in server mode, and non-interactive in desktop mode. You can run it either by running:
```bash
(venv) $ python3 $PGADMIN4_SRC/web/setup.py
```
or by starting pgAdmin 4:
```bash
(venv) $ python3 $PGADMIN4_SRC/web/pgAdmin4.py
```
Whilst it is possible to automatically run setup in desktop mode by running the runtime, that will not work in server mode as the runtime doesn't allow command line interaction with the setup program.
At this point you will be able to run pgAdmin 4 from the command line in either server or desktop mode, and access it from a web browser using the URL shown in the terminal once pgAdmin has started up.
Setup of an environment on Windows is somewhat more complicated unfortunately, please see `pkg/win32/README.md` for complete details.
### Building the documentation
In order to build the docs, an additional Python package is required in the virtual environment. This can be installed with the pip package manager:
```bash
$ source venv/bin/activate
(venv) $ pip install Sphinx
(venv) $ pip install sphinxcontrib-youtube
```
The docs can then be built using the Makefile in `$PGADMIN4_SRC`, e.g.
```bash
(venv) $ make docs
```
The output can be found in `$PGADMIN4_SRC/docs/en_US/_build/html/index.html`
### Runtime Development
For Electron runtime development:
1. Navigate to the `runtime` directory.
2. Install dependencies:
```bash
yarn install
```
3. Copy:
```text
dev_config.json.in
```
to:
```text
dev_config.json
```
4. Update the paths to your Python executable and `pgAdmin4.py`.
Start the runtime:
```bash
yarn run start
```
### Building packages
Most packages can be built using the Makefile in `$PGADMIN4_SRC`, provided all the setup and configuration above has been completed.
To build a source tarball:
```bash
(venv) $ make src
```
To build a PIP Wheel, activate either a Python 3 virtual environment, configured with all the required packages, and then run:
```bash
(venv) $ make pip
```
To build the macOS AppBundle, please see `pkg/mac/README.md`.
To build the Windows installer, please see `pkg/win32/README.md`.
## Database Migrations
In order to make changes to the SQLite DB, navigate to the 'web' directory:
```bash
(venv) $ cd $PGADMIN4_SRC/web
```
Create a migration file with the following command:
```bash
(venv) $ FLASK_APP=pgAdmin4.py flask db revision
```
This will create a file in `$PGADMIN4_SRC/web/migrations/versions/`. Add any changes to the `upgrade` function.
When making schema changes, update the `SCHEMA_VERSION` value in:
```text
web/pgadmin/model/__init__.py
```
There is no need to increment the `SETTINGS_SCHEMA_VERSION`.
## Security Issues
**Do not report security vulnerabilities through GitHub issues.**
Potential security issues should be reported to:
`security@pgadmin.org`.
Note that this **address should only be used for reporting security issues** that you believe you've found in the design or code of pgAdmin, pgAgent, and the pgAdmin website. It **should not be used to ask security questions**.
If you are unsure whether an issue is a security vulnerability, please lean on the side of caution and report it to the above address. The pgAdmin team will review the report and respond as appropriate.
## Community and Support
For development discussions, use:
`pgadmin-hackers@postgresql.org`
For support options, visit the [pgAdmin support page](https://www.pgadmin.org/support/).
Use the [GitHub issue tracker](https://github.com/pgadmin-org/pgadmin4/issues) for reporting bugs and requesting features, but please follow the guidelines outlined in the "Reporting Issues" section above.
## License
By contributing to pgAdmin 4, you agree that your contributions will be licensed under the project's existing and future license terms.
+48 -60
View File
@@ -2,7 +2,7 @@
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
# Copyright (C) 2013 - 2026, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
#########################################################################
@@ -31,51 +31,36 @@ RUN apk add --no-cache \
yarn \
zlib-dev
COPY .git .git
# Create the /pgadmin4 directory and copy the source into it. Explicitly
# remove the node_modules directory as we'll recreate a clean version, as well
# as various other files we don't want
# Create the /pgadmin4 directory and copy the source into it
COPY web /pgadmin4/web
RUN rm -rf /pgadmin4/web/*.log \
/pgadmin4/web/config_*.py \
/pgadmin4/web/node_modules \
/pgadmin4/web/regression \
`find /pgadmin4/web -type d -name tests` \
`find /pgadmin4/web -type f -name .DS_Store`
WORKDIR /pgadmin4/web
# Build the JS vendor code in the app-builder, and then remove the vendor source.
RUN export CPPFLAGS="-DPNG_ARM_NEON_OPT=0" && \
RUN --mount=type=bind,source=.git,target=/pgadmin4/.git \
--mount=type=tmpfs,target=node_modules \
--mount=type=tmpfs,target=pgadmin/static/js/generated/.cache \
export CPPFLAGS="-DPNG_ARM_NEON_OPT=0" && \
npm install -g corepack && \
corepack enable && \
yarn set version berry && \
yarn set version 4 && \
yarn install && \
yarn run bundle && \
rm -rf node_modules \
yarn.lock \
rm -rf yarn.lock \
package.json \
.[^.]* \
babel.cfg \
webpack.* \
jest.config.js \
babel.* \
./pgadmin/static/js/generated/.cache \
/pgadmin4/.git
babel.*
#########################################################################
# Next, create the base environment for Python
#########################################################################
FROM alpine:latest AS env-builder
FROM python:3-alpine AS env-builder
# Install dependencies
COPY requirements.txt /
RUN apk add --no-cache \
make \
python3 \
py3-pip && \
make && \
apk add --no-cache --virtual build-deps \
build-base \
openssl-dev \
@@ -86,9 +71,9 @@ RUN apk add --no-cache \
cargo \
zlib-dev \
libjpeg-turbo-dev \
libpng-dev \
python3-dev && \
python3 -m venv --system-site-packages --without-pip /venv && \
libpng-dev
COPY requirements.txt /
RUN python3 -m venv --system-site-packages --without-pip /venv && \
/venv/bin/python3 -m pip install --no-cache-dir -r requirements.txt && \
apk del --no-cache build-deps
@@ -120,21 +105,16 @@ RUN rm -rf /pgadmin4/docs/en_US/_build/html/_static/*.png
# Create additional builders to get all of the PostgreSQL utilities
#########################################################################
FROM postgres:12-alpine AS pg12-builder
FROM postgres:13-alpine AS pg13-builder
FROM postgres:14-alpine AS pg14-builder
FROM postgres:15-alpine AS pg15-builder
FROM postgres:16-alpine AS pg16-builder
FROM postgres:17-alpine AS pg17-builder
FROM postgres:18-alpine AS pg18-builder
FROM alpine:latest AS tool-builder
# Copy the PG binaries
COPY --from=pg12-builder /usr/local/bin/pg_dump /usr/local/pgsql/pgsql-12/
COPY --from=pg12-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-12/
COPY --from=pg12-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-12/
COPY --from=pg12-builder /usr/local/bin/psql /usr/local/pgsql/pgsql-12/
COPY --from=pg13-builder /usr/local/bin/pg_dump /usr/local/pgsql/pgsql-13/
COPY --from=pg13-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-13/
COPY --from=pg13-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-13/
@@ -160,24 +140,43 @@ COPY --from=pg17-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-17/
COPY --from=pg17-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-17/
COPY --from=pg17-builder /usr/local/bin/psql /usr/local/pgsql/pgsql-17/
COPY --from=pg18-builder /usr/local/bin/pg_dump /usr/local/pgsql/pgsql-18/
COPY --from=pg18-builder /usr/local/bin/pg_dumpall /usr/local/pgsql/pgsql-18/
COPY --from=pg18-builder /usr/local/bin/pg_restore /usr/local/pgsql/pgsql-18/
COPY --from=pg18-builder /usr/local/bin/psql /usr/local/pgsql/pgsql-18/
#########################################################################
# Assemble everything into the final container.
#########################################################################
FROM alpine:latest
FROM python:3-alpine
# Install runtime dependencies
RUN apk update && apk upgrade && \
apk add \
bash \
postfix \
krb5-libs \
libcurl \
libjpeg-turbo \
shadow \
sudo \
tzdata \
libedit \
libldap \
libcap \
su-exec && \
rm -rf /var/cache/apk/*
# Copy in the Python packages
COPY --from=env-builder /venv /venv
# Copy in the tools
COPY --from=tool-builder /usr/local/pgsql /usr/local/
COPY --from=pg17-builder /usr/local/lib/libpq.so.5.17 /usr/lib/
COPY --from=pg17-builder /usr/lib/libzstd.so.1.5.7 /usr/lib/
COPY --from=pg17-builder /usr/lib/liblz4.so.1.10.0 /usr/lib/
COPY --from=pg18-builder /usr/local/lib/libpq.so.5.18 /usr/local/lib/libpq-oauth-18.so /usr/lib/liblz4.so.1.10.0 /usr/lib/
RUN ln -s libpq.so.5.17 /usr/lib/libpq.so.5 && \
ln -s libpq.so.5.17 /usr/lib/libpq.so && \
ln -s libzstd.so.1.5.7 /usr/lib/libzstd.so.1 && \
RUN ln -s libpq.so.5.18 /usr/lib/libpq.so.5 && \
ln -s libpq.so.5.18 /usr/lib/libpq.so && \
ln -s liblz4.so.1.10.0 /usr/lib/liblz4.so.1
WORKDIR /pgadmin4
@@ -186,38 +185,27 @@ ENV PYTHONPATH=/pgadmin4
# Copy in the code and docs
COPY --from=app-builder /pgadmin4/web /pgadmin4
COPY --from=docs-builder /pgadmin4/docs/en_US/_build/html/ /pgadmin4/docs
COPY pkg/docker/run_pgadmin.py /pgadmin4
COPY pkg/docker/gunicorn_config.py /pgadmin4
COPY pkg/docker/run_pgadmin.py pkg/docker/gunicorn_config.py /pgadmin4/
COPY pkg/docker/entrypoint.sh /entrypoint.sh
# License files
COPY LICENSE /pgadmin4/LICENSE
# Install runtime dependencies and configure everything in one RUN step
RUN apk add --no-cache \
python3 \
bash \
py3-pip \
postfix \
krb5-libs \
libjpeg-turbo \
shadow \
sudo \
tzdata \
libedit \
libldap \
libcap && \
/venv/bin/python3 -m pip install --no-cache-dir gunicorn==23.0.0 && \
# Configure everything in one RUN step
RUN /venv/bin/python3 -m pip install --no-cache-dir gunicorn==23.0.0 && \
find / -type d -name '__pycache__' -exec rm -rf {} + && \
useradd -r -u 5050 -g root -s /sbin/nologin pgadmin && \
mkdir -p /run/pgadmin /var/lib/pgadmin && \
chown pgadmin:root /run/pgadmin /var/lib/pgadmin && \
chmod g=u /var/lib/pgadmin && \
chmod g=u /run/pgadmin /var/lib/pgadmin && \
touch /pgadmin4/config_distro.py && \
chown pgadmin:root /pgadmin4/config_distro.py && \
chmod g=u /pgadmin4/config_distro.py && \
chmod g=u /etc/passwd && \
setcap CAP_NET_BIND_SERVICE=+eip /usr/bin/python3.12 && \
PYBIN="$(ls /usr/local/bin/python3.[0-9][0-9] 2>/dev/null | head -n1)" && \
cp "$PYBIN" /usr/local/bin/python3-cap && \
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/python3-cap && \
ln -s /usr/local/bin/python3-cap /venv/bin/python3-cap && \
echo "pgadmin ALL = NOPASSWD: /usr/sbin/postfix start" > /etc/sudoers.d/postfix && \
echo "pgadminr ALL = NOPASSWD: /usr/sbin/postfix start" >> /etc/sudoers.d/postfix
+1 -1
View File
@@ -4,7 +4,7 @@ pgAdmin 4
This software is released under the PostgreSQL licence.
-------------------------------------------------------------------------------
Copyright (C) 2013 - 2025, The pgAdmin Development Team
Copyright (C) 2013 - 2026, The pgAdmin Development Team
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose, without fee, and without a written agreement is
+64 -21
View File
@@ -52,13 +52,14 @@ REM Main build sequence Ends
:SET_ENVIRONMENT
ECHO Configuring the environment...
IF "%PGADMIN_PYTHON_DIR%" == "" SET "PGADMIN_PYTHON_DIR=C:\Python313"
IF "%PGADMIN_PYTHON_DIR%" == "" SET "PGADMIN_PYTHON_DIR=C:\Python314"
IF "%PGADMIN_KRB5_DIR%" == "" SET "PGADMIN_KRB5_DIR=C:\Program Files\MIT\Kerberos"
IF "%PGADMIN_POSTGRES_DIR%" == "" SET "PGADMIN_POSTGRES_DIR=C:\Program Files\PostgreSQL\17"
IF "%PGADMIN_INNOTOOL_DIR%" == "" SET "PGADMIN_INNOTOOL_DIR=C:\Program Files (x86)\Inno Setup 6"
IF "%PGADMIN_VCREDIST_DIR%" == "" SET "PGADMIN_VCREDIST_DIR=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.40.33807"
IF "%PGADMIN_VCREDIST_FILE%" == "" SET "PGADMIN_VCREDIST_FILE=vc_redist.x64.exe"
IF "%PGADMIN_SIGNTOOL_DIR%" == "" SET "PGADMIN_SIGNTOOL_DIR=C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64"
IF "%PGADMIN_WINDOWS_CSC%" == "" SET "PGADMIN_WINDOWS_CSC="
REM Set additional variables we need
FOR /F "tokens=3" %%a IN ('findstr /C:"APP_RELEASE =" %WD%\web\version.py') DO SET APP_MAJOR=%%a
@@ -99,6 +100,11 @@ REM Main build sequence Ends
ECHO VC++ redist file: %PGADMIN_VCREDIST_FILE%
ECHO InnoTool directory: %PGADMIN_INNOTOOL_DIR%
ECHO signtool directory: %PGADMIN_SIGNTOOL_DIR%
IF "%PGADMIN_WINDOWS_CSC%" == "" (
ECHO Code signing certificate: [NONE - Signing disabled]
) ELSE (
ECHO Code signing certificate: %PGADMIN_WINDOWS_CSC%
)
ECHO.
ECHO App version: %APP_VERSION%
ECHO App version suffix: %APP_VERSION_SUFFIX%
@@ -155,10 +161,14 @@ REM Main build sequence Ends
CD "%TMPDIR%"
REM Note that we must use virtualenv.exe here, as the venv module doesn't allow python.exe to relocate.
"%PGADMIN_PYTHON_DIR%\Scripts\virtualenv.exe" venv
"%PGADMIN_PYTHON_DIR%\Scripts\virtualenv.exe" venv || EXIT /B 1
XCOPY /S /I /E /H /Y "%PGADMIN_PYTHON_DIR%\DLLs" "%TMPDIR%\venv\DLLs" > nul || EXIT /B 1
XCOPY /S /I /E /H /Y "%PGADMIN_PYTHON_DIR%\Lib" "%TMPDIR%\venv\Lib" > nul || EXIT /B 1
REM Copy the standard library, but NOT site-packages: the venv already has its
REM own seeded pip there, and overwriting only the files the system Python also
REM has leaves a mix of two pip versions behind.
ROBOCOPY /E /R:3 /W:5 /NFL /NDL /NP "%PGADMIN_PYTHON_DIR%\Lib" "%TMPDIR%\venv\Lib" /XD site-packages
CALL :CHECK_ROBOCOPY_ERROR || EXIT /B 1
ECHO Activating virtual environment - %TMPDIR%\venv...
CALL "%TMPDIR%\venv\Scripts\activate" || EXIT /B 1
@@ -210,13 +220,18 @@ REM Main build sequence Ends
RD /Q /S "%WD%\web\pgadmin\static\js\generated\.cache" 1> nul 2>&1
ECHO Copying web directory...
ROBOCOPY /S "%WD%\web" "%BUILDROOT%\web" > nul
CALL :CHECK_ROBOCOPY_ERROR
ROBOCOPY /S /NFL /NDL /NP "%WD%\web" "%BUILDROOT%\web"
CALL :CHECK_ROBOCOPY_ERROR || EXIT /B 1
ECHO Installing javascript dependencies...
CD "%BUILDROOT%\web"
CALL yarn set version berry || EXIT /B 1
CALL yarn set version 4 || EXIT /B 1
SET "YARN_VERSION="
FOR /f "delims=" %%v IN ('node -p "require('./package.json').packageManager.split('@')[1]"') DO SET "YARN_VERSION=%%v"
IF "%YARN_VERSION%"=="" (
ECHO ERROR: Could not determine Yarn version from package.json packageManager field.
EXIT /B 1
)
CALL yarn set version %YARN_VERSION% || EXIT /B 1
CALL yarn install || EXIT /B 1
CALL npm rebuild || EXIT /B 1
@@ -231,8 +246,18 @@ REM Main build sequence Ends
RD /Q /S "%BUILDROOT%\web\regression" 1> nul 2>&1
ECHO Removing tools...
RD /Q /S "%BUILDROOT%\web\tools" 1> nul 2>&1
ECHO Removing yarn cache...
ECHO Removing the JavaScript build configuration...
RD /Q /S "%BUILDROOT%\web\.yarn" 1> nul 2>&1
DEL /q "%BUILDROOT%\web\yarn.lock" 1> nul 2>&1
DEL /q "%BUILDROOT%\web\.yarnrc.yml" 1> nul 2>&1
DEL /q "%BUILDROOT%\web\package.json" 1> nul 2>&1
DEL /q "%BUILDROOT%\web\jest.config.js" 1> nul 2>&1
DEL /q "%BUILDROOT%\web\babel.cfg" 1> nul 2>&1
DEL /q "%BUILDROOT%\web\babel.config.json" 1> nul 2>&1
DEL /q "%BUILDROOT%\web\webpack.config.js" 1> nul 2>&1
DEL /q "%BUILDROOT%\web\webpack.shim.js" 1> nul 2>&1
DEL /q "%BUILDROOT%\web\.eslintrc.js" 1> nul 2>&1
DEL /q "%BUILDROOT%\web\.editorconfig" 1> nul 2>&1
ECHO Removing any existing configurations...
DEL /q "%BUILDROOT%\web\pgadmin4.db" 1> nul 2>&1
DEL /q "%BUILDROOT%\web\config_local.py" 1> nul 2>&1
@@ -270,8 +295,13 @@ REM Main build sequence Ends
CD "%BUILDROOT%\runtime\resources\app\"
CALL yarn set version berry || EXIT /B 1
CALL yarn set version 4 || EXIT /B 1
SET "YARN_VERSION="
FOR /f "delims=" %%v IN ('node -p "require('./package.json').packageManager.split('@')[1]"') DO SET "YARN_VERSION=%%v"
IF "%YARN_VERSION%"=="" (
ECHO ERROR: Could not determine Yarn version from package.json packageManager field.
EXIT /B 1
)
CALL yarn set version %YARN_VERSION% || EXIT /B 1
CALL yarn workspaces focus --production || EXIT /B 1
ECHO Removing yarn cache...
@@ -282,7 +312,7 @@ REM Main build sequence Ends
REM WGET
FOR /f "tokens=*" %%i IN ('npm info electron version') DO SET "ELECTRON_VERSION=%%i"
SET ELECTRON_VERSION="35.4.0"
:GET_NW
wget https://github.com/electron/electron/releases/download/v%ELECTRON_VERSION%/electron-v%ELECTRON_VERSION%-win32-x64.zip -O "%TMPDIR%\electron-v%ELECTRON_VERSION%-win32-x64.zip"
IF %ERRORLEVEL% NEQ 0 GOTO GET_NW
@@ -304,16 +334,20 @@ REM Main build sequence Ends
%TMPDIR%\rcedit-x64.exe "%BUILDROOT%\runtime\pgAdmin4.exe" --set-icon "%WD%\pkg\win32\Resources\pgAdmin4.ico"
%TMPDIR%\rcedit-x64.exe "%BUILDROOT%\runtime\pgAdmin4.exe" --set-version-string "FileDescription" "%APP_NAME%"
%TMPDIR%\rcedit-x64.exe "%BUILDROOT%\runtime\pgAdmin4.exe" --set-version-string "ProductName" "%APP_NAME%"
%TMPDIR%\rcedit-x64.exe "%BUILDROOT%\runtime\pgAdmin4.exe" --set-product-version "%APP_VERSION%""
%TMPDIR%\rcedit-x64.exe "%BUILDROOT%\runtime\pgAdmin4.exe" --set-product-version "%APP_VERSION%"
ECHO Attempting to sign the pgAdmin4.exe...
CALL "%PGADMIN_SIGNTOOL_DIR%\signtool.exe" sign /fd certHash /tr http://timestamp.digicert.com /td SHA256 "%BUILDROOT%\runtime\pgAdmin4.exe"
IF %ERRORLEVEL% NEQ 0 (
ECHO.
ECHO ************************************************************
ECHO * Failed to sign the pgAdmin4.exe
ECHO ************************************************************
PAUSE
IF NOT "%PGADMIN_WINDOWS_CSC%" == "" (
ECHO Attempting to sign the pgAdmin4.exe...
CALL "%PGADMIN_SIGNTOOL_DIR%\signtool.exe" sign /sm /n "%PGADMIN_WINDOWS_CSC%" /tr http://timestamp.digicert.com /td sha256 /fd sha1 /v "%BUILDROOT%\runtime\pgAdmin4.exe"
IF %ERRORLEVEL% NEQ 0 (
ECHO.
ECHO ************************************************************
ECHO * Failed to sign the pgAdmin4.exe
ECHO ************************************************************
PAUSE
)
) ELSE (
ECHO Skipping code signing ^(PGADMIN_WINDOWS_CSC is not set^)...
)
ECHO Staging PostgreSQL components...
@@ -356,7 +390,11 @@ REM Main build sequence Ends
DEL /s "%WD%\pkg\win32\installer.iss.in_stage*" > nul
ECHO Creating windows installer using INNO tool...
CALL "%PGADMIN_INNOTOOL_DIR%\ISCC.exe" "%WD%\pkg\win32\installer.iss" "/SpgAdminSigntool=%PGADMIN_SIGNTOOL_DIR%\signtool.exe sign /fd certHash /tr http://timestamp.digicert.com /td SHA256 $f" || EXIT /B 1
IF NOT "%PGADMIN_WINDOWS_CSC%" == "" (
CALL "%PGADMIN_INNOTOOL_DIR%\ISCC.exe" "%WD%\pkg\win32\installer.iss" "/SpgAdminSigntool=%PGADMIN_SIGNTOOL_DIR%\signtool.exe sign /sm /n $q%PGADMIN_WINDOWS_CSC%$q /tr http://timestamp.digicert.com /td sha256 /fd sha1 /v $f" || EXIT /B 1
) ELSE (
CALL "%PGADMIN_INNOTOOL_DIR%\ISCC.exe" "%WD%\pkg\win32\installer.iss" || EXIT /B 1
)
ECHO Renaming installer...
MOVE "%WD%\pkg\win32\Output\pgadmin4-setup.exe" "%DISTROOT%\%INSTALLERNAME%" > nul || EXIT /B 1
@@ -374,6 +412,11 @@ REM Main build sequence Ends
EXIT /B 0
:VERIFY_SIGNATURE
IF "%PGADMIN_WINDOWS_CSC%" == "" (
ECHO Skipping signature verification ^(PGADMIN_WINDOWS_CSC is not set^)...
EXIT /B 0
)
ECHO Verifying the installer signature...
CALL "%PGADMIN_SIGNTOOL_DIR%\signtool.exe" verify /pa /v "%DISTROOT%\%INSTALLERNAME%"
+1 -1
View File
@@ -2,7 +2,7 @@
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
# Copyright (C) 2013 - 2026, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
#########################################################################
+12 -12
View File
@@ -9,12 +9,12 @@ tarball or a git checkout.
## Architecture
pgAdmin 4 is written as a web application with Python(Flask) on the server side
pgAdmin 4 is written as a web application with Python (Flask) on the server side
and ReactJS, HTML5 with CSS for the client side processing and UI.
Although developed using web technologies, pgAdmin 4 can be deployed either on
a web server using a browser, or standalone on a workstation. The runtime/
subdirectory contains an Electron based runtime application intended to allow this,
subdirectory contains an Electron-based runtime application intended to allow this,
which will fork a Python server process and display the UI.
## Prerequisites
@@ -31,11 +31,11 @@ corepack enable
# Building the Web Assets
pgAdmin is dependent on a number of third party Javascript libraries. These,
along with it's own Javascript code, CSS code and images must be
pgAdmin is dependent on a number of third-party JavaScript libraries. These,
along with its own JavaScript code, CSS code and images must be
compiled into a "bundle" which is transferred to the browser for execution
and rendering. This is far more efficient than simply requesting each
asset as it's needed by the client.
asset as it is needed by the client.
To create the bundle, you will need the 'yarn' package management tool to be
installed. Then, you can run the following commands on a *nix system to
@@ -60,7 +60,7 @@ C:\$PGADMIN4_SRC\web> yarn run bundle
In order to run the Python code, a suitable runtime environment is required.
Python version 3.9 and later are currently supported. It is recommended that a
Python Virtual Environment is setup for this purpose, rather than using the
Python virtual environment is set up for this purpose, rather than using the
system Python environment. On Linux and Mac systems, the process is fairly
simple - adapt as required for your distribution:
@@ -112,17 +112,17 @@ simple - adapt as required for your distribution:
# Change pgAdmin data directory
DATA_DIR = '/Users/myuser/.pgadmin_dev'
#Change pgAdmin server and port
# Change pgAdmin server and port
DEFAULT_SERVER = '127.0.0.1'
DEFAULT_SERVER_PORT = 5051
# Switch between server and desktop mode
SERVER_MODE = True
#Change pgAdmin config DB path in case external DB is used.
# Change pgAdmin config DB path in case an external DB is used.
CONFIG_DATABASE_URI="postgresql://postgres:postgres@localhost:5436/pgadmin"
#Setup SMTP
# Set up SMTP
MAIL_SERVER = 'smtp.gmail.com'
MAIL_PORT = 465
MAIL_USE_SSL = True
@@ -172,7 +172,7 @@ server or desktop mode, and access it from a web browser using the URL shown in
the terminal once pgAdmin has started up.
Setup of an environment on Windows is somewhat more complicated unfortunately,
please see *pkg/win32/README.txt* for complete details.
please see *pkg/win32/README.md* for complete details.
# Building the documentation
@@ -226,9 +226,9 @@ with all the required packages, and then run:
(venv) $ make pip
```
To build the macOS AppBundle, please see *pkg/mac/README*.
To build the macOS AppBundle, please see *pkg/mac/README.md*.
To build the Windows installer, please see *pkg/win32/README.txt*.
To build the Windows installer, please see *pkg/win32/README.md*.
# Create Database Migrations
In order to make changes to the SQLite DB, navigate to the 'web' directory:
+281
View File
@@ -0,0 +1,281 @@
.. _ai_tools:
*******************
`AI Reports`:index:
*******************
**AI Reports** is a feature that provides AI-powered database analysis and insights
using Large Language Models (LLMs). Use the *Tools → AI Reports* menu to access
the various AI-powered reports.
The AI Reports feature allows you to:
* Generate security reports to identify potential security vulnerabilities and configuration issues.
* Create performance reports with optimization recommendations for queries and configurations.
* Perform design reviews to analyze database schema structure and suggest improvements.
**Prerequisites:**
Before using AI Reports, you must:
1. Ensure AI features are enabled in the server configuration (set ``LLM_ENABLED`` to ``True`` in ``config.py``).
2. Configure an LLM provider in :ref:`Preferences → AI <preferences>`.
**Note:**
* AI Reports using cloud providers (Anthropic, OpenAI) require an active internet connection.
Local providers (Ollama, Docker Model Runner) do not require internet access.
* API usage may incur costs depending on your LLM provider's pricing model.
Local providers (Ollama, Docker Model Runner) are free to use.
* The quality and accuracy of reports depend on the LLM provider and model configured.
Configuring AI Reports
**********************
To configure AI Reports, navigate to *File → Preferences → AI* (or click the *Settings*
button and select *AI*).
.. image:: images/preferences_ai.png
:alt: AI preferences
:align: center
Select your preferred LLM provider from the dropdown:
**Anthropic**
Use Claude models from Anthropic, or any Anthropic-compatible API provider.
* **API URL**: Custom API endpoint URL (leave empty for default: https://api.anthropic.com/v1).
* **API Key File**: Path to a file containing your Anthropic API key (obtain from https://console.anthropic.com/). This path refers to the filesystem where the pgAdmin server is running (e.g., inside the container if using Docker). The ``~`` prefix is expanded to the home directory of the user running the pgAdmin server process. Optional when using a custom URL with a provider that does not require authentication.
* **Model**: Select from available Claude models (e.g., claude-sonnet-4-20250514).
**OpenAI**
Use GPT models from OpenAI, or any OpenAI-compatible API provider (e.g.,
LiteLLM, LM Studio, EXO, or other local inference servers).
* **API URL**: Custom API endpoint URL (leave empty for default: https://api.openai.com/v1). Include the ``/v1`` path prefix if required by your provider.
* **API Key File**: Path to a file containing your OpenAI API key (obtain from https://platform.openai.com/). This path refers to the filesystem where the pgAdmin server is running (e.g., inside the container if using Docker). The ``~`` prefix is expanded to the home directory of the user running the pgAdmin server process. Optional when using a custom URL with a provider that does not require authentication.
* **Model**: Select from available GPT models (e.g., gpt-4).
**Ollama**
Use locally-hosted open-source models via Ollama. Requires a running Ollama instance.
* **API URL**: The URL of your Ollama server (default: http://localhost:11434).
* **Model**: Enter the name of the Ollama model to use (e.g., llama2, mistral).
**Docker Model Runner**
Use models running in Docker Desktop's built-in model runner (available in Docker Desktop 4.40+).
No API key is required.
* **API URL**: The URL of the Docker Model Runner API (default: http://localhost:12434).
* **Model**: Select from available models or enter a custom model name.
.. note:: You can also use the *OpenAI* provider with a custom API URL for any
OpenAI-compatible endpoint, including Docker Model Runner and other local
inference servers.
After configuring your provider, click *Save* to apply the changes.
.. _ai_data_handling:
Data Handling and Provider Selection
************************************
When a cloud LLM provider is configured, pgAdmin transmits information about
your database to that provider's API. Depending on the feature in use, this may
include schema definitions such as table, column, index and constraint names
and data types; server and database configuration settings read from
``pg_settings``; query text; and EXPLAIN plan output. The *AI Assistant* in the
Query Tool is also able to run queries against your database, within a read-only
transaction and limited to 1000 rows, so row data may be included where the
assistant determines it is needed to answer a question.
None of this is transmitted unless you invoke an AI feature, and none of it is
transmitted at all when no provider has been configured, which is the default;
when AI features have been disabled by the administrator through the
``LLM_ENABLED`` setting; or when a locally hosted provider such as Ollama or
Docker Model Runner is configured.
Each provider processes this data under its own terms of service, privacy policy
and legal jurisdiction, and operates its infrastructure in locations of its own
choosing. If you work in an environment subject to data residency requirements,
procurement policies, or sector-specific rules governing the handling of
database metadata, review both the provider's terms and your own organisation's
policies before enabling a cloud provider.
The providers listed in pgAdmin reflect the APIs that pgAdmin is able to
communicate with. Their inclusion is not a recommendation, and no provider is
enabled by default.
Security Reports
****************
Security Reports analyze your PostgreSQL server, database, or schema for potential
security vulnerabilities and configuration issues.
To generate a security report:
1. In the *Browser* tree, select a server, database, or schema.
2. Choose *Tools → AI Reports → Security* from the menu, or right-click the
object and select *Security* from the context menu.
3. The report will be generated and displayed in a new tab.
.. image:: images/ai_security_report.png
:alt: AI security report
:align: center
**Security Report Scope:**
* **Server Level**: Analyzes server configuration, authentication settings, roles, and permissions.
* **Database Level**: Reviews database-specific security settings, roles with database access, and object permissions.
* **Schema Level**: Examines schema permissions, object ownership, and access controls.
Each report includes:
* **Security Findings**: Identified vulnerabilities or security concerns.
* **Risk Assessment**: Severity levels for each finding (Critical, High, Medium, Low).
* **Recommendations**: Specific actions to remediate security issues.
* **Best Practices**: General security recommendations for PostgreSQL.
Performance Reports
*******************
Performance Reports analyze query performance, configuration settings, and provide
optimization recommendations.
To generate a performance report:
1. In the *Browser* tree, select a server or database.
2. Choose *Tools → AI Reports → Performance* from the menu, or right-click the
object and select *Performance* from the context menu.
3. The report will be generated and displayed in a new tab.
**Performance Report Scope:**
* **Server Level**: Analyzes server configuration parameters, resource utilization, and overall server performance metrics.
* **Database Level**: Reviews database-specific configuration, query performance, index usage, and table statistics.
Each report includes:
* **Performance Metrics**: Key performance indicators and statistics.
* **Configuration Analysis**: Review of relevant configuration parameters.
* **Query Optimization**: Recommendations for improving slow queries.
* **Index Recommendations**: Suggestions for adding, removing, or modifying indexes.
* **Capacity Planning**: Resource utilization trends and recommendations.
Design Review Reports
*********************
Design Review Reports analyze your database schema structure and suggest
improvements for normalization, naming conventions, and best practices.
To generate a design review report:
1. In the *Browser* tree, select a database or schema.
2. Choose *Tools → AI Reports → Design* from the menu, or right-click the
object and select *Design* from the context menu.
3. The report will be generated and displayed in a new tab.
**Design Review Scope:**
* **Database Level**: Reviews overall database structure, schema organization, and cross-schema dependencies.
* **Schema Level**: Analyzes tables, views, functions, and other objects within the schema.
Each report includes:
* **Schema Structure Analysis**: Review of table structures, relationships, and constraints.
* **Normalization Review**: Recommendations for database normalization (1NF, 2NF, 3NF, etc.).
* **Naming Conventions**: Suggestions for consistent naming patterns.
* **Data Type Usage**: Review of data type choices and recommendations.
* **Index Design**: Analysis of indexing strategy.
* **Best Practices**: General PostgreSQL schema design recommendations.
Working with Reports
********************
All AI reports are displayed in a dedicated panel with the following features:
**Report Display**
Reports are formatted as Markdown and rendered with syntax highlighting for SQL code.
**Toolbar Actions**
* **Stop** - Cancel the current report generation. This is useful if the report
is taking too long or if you want to change parameters.
* **Regenerate** - Generate a new report for the same object. Useful when you
want to get a fresh analysis or if data has changed.
* **Download** - Download the report as a Markdown (.md) file. The filename
includes the report type, object name, and date for easy identification.
**Multiple Reports**
You can generate and view multiple reports simultaneously. Each report opens in
a new tab, allowing you to compare reports across different servers, databases,
or schemas.
**Report Management**
Each report tab can be closed individually by clicking the *X* in the tab.
Panel titles show the object name and report type for easy identification.
**Copying Content**
You can select and copy text from reports to use in documentation or share with
your team.
Troubleshooting
***************
**"AI features are disabled in the server configuration"**
The administrator has disabled AI features on the server. Contact your
pgAdmin administrator to enable the ``LLM_ENABLED`` configuration option.
**"Please configure an LLM provider in Preferences"**
You need to configure an LLM provider before using AI Reports. See *Configuring AI Reports* above.
**"Please connect to the server/database first"**
You must establish a connection to the server or database before generating reports.
**API Connection Errors**
* Verify your API key is correct (for Anthropic and OpenAI).
* Check your internet connection (for cloud providers).
* For Ollama, ensure the Ollama server is running and accessible.
* For Docker Model Runner, ensure Docker Desktop 4.40+ is running with the model runner enabled.
* Check that your firewall allows connections to the LLM provider's API.
**Report Generation Fails**
* Check the pgAdmin logs for detailed error messages.
* Verify the database connection is still active.
* Ensure the selected model is available for your account/subscription.
+33 -33
View File
@@ -77,13 +77,13 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg
* Move switches in the **Sections** field box to select a portion of the object
that will be backed up.
* Move the switch next to *Pre-data* towards right position to include all
* Move the switch next to *Pre-data* to the *Yes* position to include all
data definition items not included in the data or post-data item lists.
* Move the switch next to *Data* towards right position to backup actual table
* Move the switch next to *Data* to the *Yes* position to backup actual table
data, large-object contents, and sequence values.
* Move the switch next to *Post-data* towards right position to include
* Move the switch next to *Post-data* to the *Yes* position to include
definitions of indexes, triggers, rules, and constraints other than
validated check constraints.
@@ -94,13 +94,13 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg
* Move switches in the **Type of objects** field box to specify details about
the type of objects that will be backed up.
* Move the switch next to *Only data* towards right position to limit the back
* Move the switch next to *Only data* to the *Yes* position to limit the back
up to data.
* Move the switch next to *Only schemas* to limit the back up to schema-level
database objects.
* Move the switch next to *Blobs* towards left position to exclude large
* Move the switch next to *Blobs* to the *No* position to exclude large
objects in the backup.
.. image:: images/backup_do_not_save.png
@@ -110,36 +110,36 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg
* Move switches in the **Do not save** field box to select the objects that will
not be included in the backup.
* Move the switch next to *Owner* towards right position to exclude commands
* Move the switch next to *Owner* to the *Yes* position to exclude commands
that set object ownership.
* Move the switch next to *Privileges* towards right position to exclude
* Move the switch next to *Privileges* to the *Yes* position to exclude
commands that create access privileges.
* Move the switch next to *Tablespaces* towards right position to exclude
* Move the switch next to *Tablespaces* to the *Yes* position to exclude
tablespaces.
* Move the switch next to *Unlogged table data* towards right position to
* Move the switch next to *Unlogged table data* to the *Yes* position to
exclude the contents of unlogged tables.
* Move the switch next to *Comments* towards right position to exclude
* Move the switch next to *Comments* to the *Yes* position to exclude
commands that set the comments. **Note:** This option is visible only for
database server greater than or equal to 11.
* Move the switch next to *Publications* towards right position to exclude
* Move the switch next to *Publications* to the *Yes* position to exclude
publications.
* Move the switch next to *Subscriptions* towards right position to exclude
* Move the switch next to *Subscriptions* to the *Yes* position to exclude
subscriptions.
* Move the switch next to *Security labels* towards right position to exclude
* Move the switch next to *Security labels* to the *Yes* position to exclude
Security labels.
* Move the switch next to *Toast compressions* towards right position to exclude
* Move the switch next to *Toast compressions* to the *Yes* position to exclude
Toast compressions. **Note:** This option is visible only for
database server greater than or equal to 14.
* Move the switch next to *Table access methods* towards right position to exclude
* Move the switch next to *Table access methods* to the *Yes* position to exclude
Table access methods. **Note:** This option is visible only for
database server greater than or equal to 15.
@@ -150,7 +150,7 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg
Click the *Query Options* tab to continue. Use these additional fields to specify
the type of statements that should be included in the backup.
* Move the switch next to *Use INSERT commands* towards right position to
* Move the switch next to *Use INSERT commands* to the *Yes* position to
dump the data in the form of INSERT statements rather than using a COPY
command. Please note: this may make restoration from backup slow.
@@ -165,16 +165,16 @@ the type of statements that should be included in the backup.
**Note:** This option is visible only for database server greater than or
equal to 12.
* Move the switch next to *Include CREATE DATABASE statement* towards right
* Move the switch next to *Include CREATE DATABASE statement* to the *Yes*
position to include a command in the backup that creates a new database
when restoring the backup.
* Move the switch next to *Include DROP DATABASE statement* towards right
* Move the switch next to *Include DROP DATABASE statement* to the *Yes*
position to include a command in the backup that will drop any existing
database object with the same name before recreating the object during a
backup.
* Move the switch next to *Include IF EXISTS clause* towards right
* Move the switch next to *Include IF EXISTS clause* to the *Yes*
position to add an IF EXISTS clause to drop databases and other objects.
This option is not valid unless *Include DROP DATABASE statement* is also set.
@@ -185,17 +185,17 @@ the type of statements that should be included in the backup.
Click the *Table Options* tab to continue. Use the fields in the *Table Options*
tab related to tables that should be included in the backup.
* Move the switch next to *Use Column INSERTS* towards right position to dump
* Move the switch next to *Use Column INSERTS* to the *Yes* position to dump
the data in the form of INSERT statements and include explicit column
names. Please note: this may make restoration from backup slow.
* Move the switch next to *Load via partition root* towards right position,
* Move the switch next to *Load via partition root* to the *Yes* position,
so when dumping a COPY or INSERT statement for a partitioned table, target
the root of the partitioning hierarchy which contains it rather than the
partition itself. **Note:** This option is visible only for database server
greater than or equal to 11.
* Move the switch next to *Enable row security* towards right position to
* Move the switch next to *Enable row security* to the *Yes* position to
set row_security to on instead, allowing the user to dump the parts of the
contents of the table that they have access to. This option is relevant
only when dumping the contents of a table which has row security.
@@ -235,10 +235,10 @@ tab to provide other backup options.
that should be excluded from the backup.
* Move the switch next to *Triggers* (active when creating a data-only backup)
towards right position to include commands that will disable triggers on the
to the *Yes* position to include commands that will disable triggers on the
target table while the data is being loaded.
* Move the switch next to *$ quoting* towards right position to enable dollar
* Move the switch next to *$ quoting* to the *Yes* position to enable dollar
quoting within function bodies; if disabled, the function body will be
quoted using SQL standard string syntax.
@@ -249,13 +249,13 @@ tab to provide other backup options.
* Move switches in the **Miscellaneous** field box to specify miscellaneous
backup options.
* Move the switch next to *Verbose messages* towards left position to instruct
* Move the switch next to *Verbose messages* to the *No* position to instruct
*pg_dump* to exclude verbose messages.
* Move the switch next to *Force double quotes on identifiers* towards right
* Move the switch next to *Force double quotes on identifiers* to the *Yes*
position to force the quoting of all identifiers.
* Move the switch next to *Use SET SESSION AUTHORIZATION* towards right
* Move the switch next to *Use SET SESSION AUTHORIZATION* to the *Yes*
position to include a statement that will use a SET SESSION AUTHORIZATION
command to determine object ownership (instead of an ALTER OWNER command).
@@ -265,9 +265,9 @@ tab to provide other backup options.
* Use the *Extra float digits* field to use the specified value when dumping
floating-point data, instead of the maximum available precision.
* Use the *Lock wait timeout* field to do not wait forever to acquire shared
table locks at the beginning of the dump. Instead, fail if unable to lock a
table within the specified timeout.
* Use the *Lock wait timeout* field to specify a timeout rather than waiting
indefinitely to acquire shared table locks at the beginning of the dump.
If unable to lock a table within the specified timeout, the operation will fail.
Click the *Objects* tab to continue.
@@ -287,6 +287,6 @@ command:
* Click the *Cancel* button to exit without saving work.
pgAdmin will run the backup process in background. You can view all the background
process with there running status and logs on the :ref:`Processes <processes>`
tab
pgAdmin will run the backup process in the background. You can view all the background
processes with their running status and logs on the :ref:`Processes <processes>`
tab.
+3 -3
View File
@@ -34,6 +34,6 @@ statements that should be included in the backup.
Click the *Backup* button to build and execute a command based on your
selections; click the *Cancel* button to exit without saving work.
pgAdmin will run the backup process in background. You can view all the background
process with there running status and logs on the :ref:`Processes <processes>`
tab
pgAdmin will run the backup process in the background. You can view all the background
processes with their running status and logs on the :ref:`Processes <processes>`
tab.
+28 -28
View File
@@ -34,7 +34,7 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg
* Move switches in the **Type of objects** field box to specify details about
the type of objects that will be backed up.
* Move the switch next to *Only data* towards right position to limit the back
* Move the switch next to *Only data* to the *Yes* position to limit the back
up to data.
* Move the switch next to *Only schemas* to limit the back up to schema-level
@@ -52,39 +52,39 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg
* Move switches in the **Do not save** field box to select the objects that will
not be included in the backup.
* Move the switch next to *Owner* towards right position to exclude commands
* Move the switch next to *Owner* to the *Yes* position to exclude commands
that set object ownership.
* Move the switch next to *Role passwords* towards right position to exclude
* Move the switch next to *Role passwords* to the *Yes* position to exclude
passwords for roles.
* Move the switch next to *Privileges* towards right position to exclude
* Move the switch next to *Privileges* to the *Yes* position to exclude
commands that create access privileges.
* Move the switch next to *Tablespaces* towards right position to exclude
* Move the switch next to *Tablespaces* to the *Yes* position to exclude
tablespaces.
* Move the switch next to *Unlogged table data* towards right position to
* Move the switch next to *Unlogged table data* to the *Yes* position to
exclude the contents of unlogged tables.
* Move the switch next to *Comments* towards right position to exclude
* Move the switch next to *Comments* to the *Yes* position to exclude
commands that set the comments. **Note:** This option is visible only for
database server greater than or equal to 11.
* Move the switch next to *Publications* towards right position to exclude
* Move the switch next to *Publications* to the *Yes* position to exclude
publications.
* Move the switch next to *Subscriptions* towards right position to exclude
* Move the switch next to *Subscriptions* to the *Yes* position to exclude
subscriptions.
* Move the switch next to *Security labels* towards right position to exclude
* Move the switch next to *Security labels* to the *Yes* position to exclude
Security labels.
* Move the switch next to *Toast compressions* towards right position to exclude
* Move the switch next to *Toast compressions* to the *Yes* position to exclude
Toast compressions. **Note:** This option is visible only for
database server greater than or equal to 14.
* Move the switch next to *Table access methods* towards right position to exclude
* Move the switch next to *Table access methods* to the *Yes* position to exclude
Table access methods. **Note:** This option is visible only for
database server greater than or equal to 15.
@@ -95,7 +95,7 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg
Click the *Query Options* tab to continue. Use these additional fields to specify
the type of statements that should be included in the backup.
* Move the switch next to *Use INSERT commands* towards right position to
* Move the switch next to *Use INSERT commands* to the *Yes* position to
dump the data in the form of INSERT statements rather than using a COPY
command. Please note: this may make restoration from backup slow.
@@ -110,12 +110,12 @@ the type of statements that should be included in the backup.
**Note:** This option is visible only for database server greater than or
equal to 12.
* Move the switch next to *Include DROP DATABASE statement* towards right
* Move the switch next to *Include DROP DATABASE statement* to the *Yes*
position to include a command in the backup that will drop any existing
database object with the same name before recreating the object during a
backup.
* Move the switch next to *Include IF EXISTS clause* towards right
* Move the switch next to *Include IF EXISTS clause* to the *Yes*
position to add an IF EXISTS clause to drop databases and other objects.
This option is not valid unless *Include DROP DATABASE statement* is also set.
@@ -126,11 +126,11 @@ the type of statements that should be included in the backup.
Click the *Table Options* tab to continue. Use the fields in the *Table Options*
tab related to tables that should be included in the backup.
* Move the switch next to *Use Column INSERTS* towards right position to dump
* Move the switch next to *Use Column INSERTS* to the *Yes* position to dump
the data in the form of INSERT statements and include explicit column
names. Please note: this may make restoration from backup slow.
* Move the switch next to *Load via partition root* towards right position,
* Move the switch next to *Load via partition root* to the *Yes* position,
so when dumping a COPY or INSERT statement for a partitioned table, target
the root of the partitioning hierarchy which contains it rather than the
partition itself. **Note:** This option is visible only for database server
@@ -147,10 +147,10 @@ tab to provide other backup options.
that should be excluded from the backup.
* Move the switch next to *Triggers* (active when creating a data-only backup)
towards right position to include commands that will disable triggers on the
to the *Yes* position to include commands that will disable triggers on the
target table while the data is being loaded.
* Move the switch next to *$ quoting* towards right position to enable dollar
* Move the switch next to *$ quoting* to the *Yes* position to enable dollar
quoting within function bodies; if disabled, the function body will be
quoted using SQL standard string syntax.
@@ -161,13 +161,13 @@ tab to provide other backup options.
* Move switches in the **Miscellaneous** field box to specify miscellaneous
backup options.
* Move the switch next to *Verbose messages* towards left position to instruct
* Move the switch next to *Verbose messages* to the *No* position to instruct
*pg_dumpall* to exclude verbose messages.
* Move the switch next to *Force double quotes on identifiers* towards right
* Move the switch next to *Force double quotes on identifiers* to the *Yes*
position to force the quoting of all identifiers.
* Move the switch next to *Use SET SESSION AUTHORIZATION* towards right
* Move the switch next to *Use SET SESSION AUTHORIZATION* to the *Yes*
position to include a statement that will use a SET SESSION AUTHORIZATION
command to determine object ownership (instead of an ALTER OWNER command).
@@ -177,9 +177,9 @@ tab to provide other backup options.
* Use the *Extra float digits* field to use the specified value when dumping
floating-point data, instead of the maximum available precision.
* Use the *Lock wait timeout* field to do not wait forever to acquire shared
table locks at the beginning of the dump. Instead, fail if unable to lock a
table within the specified timeout.
* Use the *Lock wait timeout* field to specify a timeout rather than waiting
indefinitely to acquire shared table locks at the beginning of the dump.
If unable to lock a table within the specified timeout, the operation will fail.
When you’ve specified the details that will be incorporated into the pg_dumpall
command:
@@ -189,6 +189,6 @@ command:
* Click the *Cancel* button to exit without saving work.
pgAdmin will run the backup process in background. You can view all the background
process with there running status and logs on the :ref:`Processes <processes>`
tab
pgAdmin will run the backup process in the background. You can view all the background
processes with their running status and logs on the :ref:`Processes <processes>`
tab.
+2 -2
View File
@@ -91,8 +91,8 @@ button to deploy the instance on Amazon RDS.
Once you click on the finish, one background process will start which will
deploy the instance in the cloud and monitor the progress of the deployment.
You can view all the background process with there running status and logs
on the :ref:`Processes <processes>` tab
You can view all the background processes with their running status and logs
on the :ref:`Processes <processes>` tab.
The Server will be added to the tree with the cloud deployment icon. Once the
deployment is done, the server details will be updated.
+2 -2
View File
@@ -107,8 +107,8 @@ button to deploy the instance on Azure Database.
Once you click on the finish, one background process will start which will
deploy the instance in the cloud and monitor the progress of the deployment.
You can view all the background process with there running status and logs
on the :ref:`Processes <processes>` tab
You can view all the background processes with their running status and logs
on the :ref:`Processes <processes>` tab.
.. image:: images/cloud_azure_bg_process_watcher.png
:alt: Cloud Deployment
Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

+3 -4
View File
@@ -4,9 +4,9 @@
`PostgreSQL Cloud Deployment`:index:
*************************************
A PostgreSQL server can be deployed on the Amazon, EDB BigAnimal, Azure,
Google cloud using this module. In future more cloud provider options will
be available.
A PostgreSQL server can be deployed on the Amazon, Azure, Google cloud
using this module. In future more cloud provider options will be
available.
To launch the *Cloud Deployment...* tool, right click on the *Server Group* or
*Server* of the tree control, and select *Deploy a Cloud Instance* from the
@@ -16,6 +16,5 @@ To launch the *Cloud Deployment...* tool, right click on the *Server Group* or
:maxdepth: 2
cloud_aws_rds
cloud_edb_biganimal
cloud_azure_database
cloud_google_cloud_sql
-110
View File
@@ -1,110 +0,0 @@
.. _cloud_edb_biganimal:
******************************************
`EDB BigAnimal Cloud Deployment`:index:
******************************************
To deploy a PostgreSQL server on the EDB BigAnimal cloud, follow the below steps.
.. image:: images/cloud_provider_for_postgresql.png
:alt: Cloud Deployment Provider
:align: center
Once you launch the tool, select the EDB BigAnimal option.
Click on the *Next* button to proceed further.
.. image:: images/cloud_biganimal_credentials.png
:alt: Cloud Deployment Provider
:align: center
The next steps is to authenticate the user to EDB BigAninal.
Click the given button to authenticate, by clicking the button, the user
will be redirected to the new tab for the verification.
Once you confirm the one time code, the pgAdmin will automatically detect it
and the next button will be enabled. To proceed further, click on the next button.
.. image:: images/cloud_biganimal_cluster.png
:alt: Cloud Deployment Provider
:align: center
* Use the *Project* field to choose a project in your Biganimal account.
* Use the *Cluster type* field to choose a cluster type.
* Use the *No. of Standby Replicas* field to specify the replicas if you have selected the High Availability cluster.
* Use the *Cluster provider* field to choose the provider.
.. image:: images/cloud_biganimal_instance.png
:alt: Cloud Deployment Provider
:align: center
Use the fields from the Instance Specification tab to specify the Instance
details.
* Use the *Cluster name* field to add a cluster name for the PostgreSQL
server; the name specified will be displayed in the *Object Explorer* too.
* Use the *Region* field to select the region.
* Use the *Public IP range* field to specify the IP Address range for permitting the
inbound traffic. Leave it blank for 0.0.0.0/0
* Use the *Instance type* field to select the instance type.
* Use the *Instance series* field to select the instance series.
* Use the *Instance size* field to allocate the computational, network, and
memory capacity required by planned workload of this DB instance.
* Use the *Volume type* field to select the instance storage type.
* Use the *Volume properties* field to specify the storage capacity. This field is specific to Azure.
* Use the *Volume size* field to specify the storage size. This field is specific to AWS.
* Use the *Volume IOPS* field to specify the storage IOPS. This field is specific to AWS.
* Use the *Disk throughput* field to specify the disk throughput. This field is specific to AWS.
.. image:: images/cloud_biganimal_database.png
:alt: Cloud Deployment Provider
:align: center
Use the fields from the Database Details tab to specify the Instance details.
* Use the drop-down list box in the *Server group* field to select the parent
node for the server; the server will be displayed in the *Object Explorer*
within the specified group.
* Use the *Database type* field to specify the PostgreSQL
type, EnterpriseDB PostgreSQL Advanced Server or PostgreSQL.
* Use the *PostgreSQL version* field to select the database version.
* Use the *Database Password* field to provide a password that will be supplied when
authenticating with the server.
* Use the *Confirm password* field to repeat the password.
.. image:: images/cloud_biganimal_review.png
:alt: Cloud Deployment Provider
:align: center
At the end, review the Cluster details that you provided. Click on Finish
button to deploy the instance on EDB BigAnimal.
.. image:: images/cloud_deployment_tree.png
:alt: Cloud Deployment Provider
:align: center
Once you click on the finish, one background process will start which will
deploy the instance in the cloud and monitor the progress of the deployment.
You can view all the background process with there running status and logs
on the :ref:`Processes <processes>` tab
The Server will be added to the tree with the cloud deployment icon. Once the
deployment is done, the server details will be updated.
+2 -2
View File
@@ -105,8 +105,8 @@ button to deploy the instance on Azure PostgreSQL.
Once you click on the finish, one background process will start which will
deploy the instance in the cloud and monitor the progress of the deployment.
You can view all the background process with there running status and logs
on the :ref:`Processes <processes>` tab
You can view all the background processes with their running status and logs
on the :ref:`Processes <processes>` tab.
The Server will be added to the tree with the cloud deployment icon. Once the
+1 -1
View File
@@ -16,7 +16,7 @@ review process for any changes submitted for inclusion in pgAdmin.
bulk updates every year). The start date should always be 2013, and the end year
the current year, e.g.
Copyright (C) 2013 - 2025, The pgAdmin Development Team
Copyright (C) 2013 - 2026, The pgAdmin Development Team
* Ensure there's a blank line immediately following any copyright headers.
+1 -1
View File
@@ -120,7 +120,7 @@ with the required file header is shown below::
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
# Copyright (C) 2013 - 2026, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
+4 -4
View File
@@ -57,7 +57,7 @@ settings:
deterministic comparisons. By default, this option is set to true. In a
deterministic comparison, strings that are not byte-wise equal are considered
unequal, even if they are considered logically equal in the comparison.
* Use the *Rules* field to specify a rules for customizing the behavior of the collation.
* Use the *Rules* field to specify rules for customizing the behavior of the collation.
It includes considerations such as character ordering, case sensitivity, and accent
sensitivity.
* Use the *Version* field to specify version string to store with the collation object.
@@ -65,8 +65,8 @@ settings:
Click the *SQL* tab to continue.
Your entries in the *Collation* dialog generate a SQL command (see an example b
elow). Use the *SQL* tab for review; revisit or switch tabs to make any changes
Your entries in the *Collation* dialog generate a SQL command (see an example
below). Use the *SQL* tab for review; revisit or switch tabs to make any changes
to the SQL command.
Example
@@ -80,7 +80,7 @@ the *Collation* dialog:
:align: center
The example shown demonstrates creating a collation named *french* that uses the
rules specified for the locale, *fr-BI-x-icu. The collation is owned by
rules specified for the locale, *fr-BI-x-icu*. The collation is owned by
*postgres*.
* Click the *Info* button (i) to access online help. For more information about
+1 -1
View File
@@ -144,7 +144,7 @@ the *Add* icon (+) to add each security label selection:
* Specify a security label provider in the *Provider* field. The named provider
must be loaded and must consent to the proposed labeling operation.
* Specify a a security label in the *Security Label* field. The meaning of a
* Specify a security label in the *Security Label* field. The meaning of a
given label is at the discretion of the label provider. PostgreSQL places no
restrictions on whether or how a label provider must interpret security
labels; it merely provides a mechanism for storing them.
+8
View File
@@ -239,6 +239,14 @@ latex_documents = [
# https://groups.google.com/forum/#!topic/sphinx-users/ZlQ95_QikXo
latex_elements = {
'fncychap': r'\usepackage[Sonny]{fncychap}',
# pdflatex's default utf8 inputenc covers Latin-1/Latin-Extended-A but
# not the Vietnamese precomposed code points that appear in CVE
# reporter names. Declare the specific characters we use rather than
# switching engines.
'preamble': r'''
\DeclareUnicodeCharacter{1EA1}{\d{a}}
\DeclareUnicodeCharacter{1EC1}{\`{\^e}}
''',
}
+123 -2
View File
@@ -53,6 +53,7 @@ The default binary paths set in the container are as follows:
.. code-block:: bash
DEFAULT_BINARY_PATHS = {
'pg-18': '/usr/local/pgsql-18',
'pg-17': '/usr/local/pgsql-17',
'pg-16': '/usr/local/pgsql-16',
'pg-15': '/usr/local/pgsql-15',
@@ -162,8 +163,9 @@ suffix will be ignored.
**PGPASS_FILE**
*Default: <null>*
This varible should be set to if you want to pass password using pgpass
file for the servers added in pgadmin.
Set this variable to the path of a pgpass file if you want to supply passwords
for the servers added in pgAdmin. On startup, the file is copied into the
container and used as the ``.pgpass`` file.
**GUNICORN_ACCESS_LOGFILE**
@@ -319,6 +321,44 @@ Run a TLS secured container using a shared config/storage directory in
-e 'PGADMIN_ENABLE_TLS=True' \
-d dpage/pgadmin4
Restricted Security Contexts (OpenShift, ``--cap-drop=ALL``)
************************************************************
Some platforms refuse to honor Linux file capabilities. The two situations
the pgAdmin container handles automatically are:
- ``--cap-drop=ALL`` (or an equivalent restricted Kubernetes SecurityContext
such as OpenShift's ``restricted-v2`` SCC), which zeros the bounding set
and removes ``CAP_NET_BIND_SERVICE``. Exec of a capability-tagged binary
then fails with ``Operation not permitted``.
- ``--security-opt=no-new-privileges`` (or
``allowPrivilegeEscalation: false``), which causes the kernel to silently
strip file capabilities on exec. The binary runs, but a subsequent
``bind()`` to a port below 1024 fails with ``EPERM``.
The container's entrypoint reads ``/proc/self/status`` at startup, detects
either condition, switches gunicorn to the non-capability python
interpreter, and (when *PGADMIN_LISTEN_PORT* is not set) defaults the
listen port to **8080** for plain HTTP and **8443** for TLS instead of
80/443. A message is logged so the choice is visible.
In practice this means a typical OpenShift deployment requires no special
build, no setcap, and no custom configuration — only a Service / Route
that targets the chosen non-privileged port:
.. code-block:: bash
docker run --rm -p 8080:8080 \
--security-opt=no-new-privileges \
--cap-drop=ALL \
-e 'PGADMIN_DEFAULT_EMAIL=user@domain.com' \
-e 'PGADMIN_DEFAULT_PASSWORD=SuperSecret' \
dpage/pgadmin4
If you explicitly set *PGADMIN_LISTEN_PORT*, that value is honored in both
the restricted and unrestricted paths.
Reverse Proxying
****************
@@ -525,3 +565,84 @@ the container is typically launched per the example below:
-e "SCRIPT_NAME=/pgadmin4" \
-l "traefik.frontend.pgadmin4.rule=Host(`host.example.com`) && PathPrefix(`/pgadmin4`)" \
-d dpage/pgadmin4
Sharing the data volume across containers (Kubernetes init containers)
----------------------------------------------------------------------
A common Kubernetes pattern is to bootstrap a pgAdmin deployment with one or
more init containers that share the ``/var/lib/pgadmin`` volume with the main
pgAdmin container — for example, an init container that runs
``setup.py load-servers`` to import a ``servers.json`` definition file at
each rollout.
**The image tag of any such sidecar/init container must NOT be newer than
the main pgAdmin container's tag.** pgAdmin's configuration database
(``pgadmin4.db``) is migrated forward only — destructive operations such as
dropping a column are part of normal upgrades. If an init container running
``pgadmin4:latest`` (or any newer tag) executes ``setup.py`` against a
``pgadmin4.db`` that the main, older pgAdmin will then read, the init
container's migrations will advance the schema past what the main container's
ORM understands, and the main container will fail at runtime with errors
like ``no such column: <table>.<column>``.
The safe configuration is to pin **both** the main container and every
sidecar/init container that touches the data volume to the **same**
explicit version tag, and upgrade them together:
.. code-block:: yaml
# Deployment manifest (spec.template.spec): pin every container that
# touches the data volume to the same explicit tag. Do NOT use :latest.
spec:
template:
spec:
initContainers:
- name: import-servers-json
image: docker.io/dpage/pgadmin4:9.14 # match the main container
# ... rest of the init container spec ...
containers:
- name: pgadmin
image: docker.io/dpage/pgadmin4:9.14 # main pgAdmin container
If you deploy pgAdmin with a Helm chart, set the equivalent image-tag values
for both the main container and every init/sidecar container rather than the
raw fields shown above. When upgrading pgAdmin, change every reference to the
image tag at the same time so that the main container and any sidecar/init
containers move forward together.
Use ``strategy: Recreate``, not ``RollingUpdate``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Even when every container's image tag is pinned correctly, **a rolling
upgrade across pgAdmin versions is unsafe** when the old and new pods share
a data volume. Kubernetes' default ``RollingUpdate`` strategy starts the
new pod, waits for it to become healthy, and only *then* terminates the
old pod. During that overlap window:
1. The new pod's startup runs ``db_upgrade()`` and applies any pending
migrations to the shared ``pgadmin4.db`` — including destructive ones
(column drops, table renames, type changes).
2. The new pod becomes healthy. The old pod is still alive and still has
``pgadmin4.db`` open with its older ORM schema metadata.
3. Until Kubernetes terminates the old pod, requests routed to it
generate queries that reference the pre-migration schema. Those
queries now fail at runtime against the migrated database — for
example, ``no such column: <table>.<column>`` when a column was
dropped in this release.
Because pgAdmin's migrations are forward-only with single-step destructive
operations (a column dropped in release N+1 is gone immediately, not after
a deprecation cycle), there is no safe way for an N-version pod to keep
serving against a migrated N+1 schema. Set the deployment strategy to
``Recreate``, which terminates the old pod *before* starting the new one:
.. code-block:: yaml
spec:
strategy:
type: Recreate # not RollingUpdate
The trade-off is a few seconds of downtime during each upgrade, in
exchange for not serving traffic from a pod whose ORM is misaligned with
the database schema underneath it. This is the supported upgrade
strategy for pgAdmin K8s deployments today.
+4 -4
View File
@@ -63,7 +63,7 @@ Use the *Definition* tab to set properties for the database:
* Specify a connection limit in the *Connection Limit* field to configure the
maximum number of connection requests. The default value (*-1*) allows
unlimited connections to the database.
* If the *templates?* is set to true, then database will be a template database.
* If *Template?* is set to *Yes*, the database will be a template database.
Click the *Security* tab to continue.
@@ -89,7 +89,7 @@ database. Click the *Add* icon (+) to add each security label selection:
* Specify a security label provider in the *Provider* field. The named provider
must be loaded and must consent to the proposed labeling operation.
* Specify a a security label in the *Security Label* field. The meaning of a
* Specify a security label in the *Security Label* field. The meaning of a
given label is at the discretion of the label provider. PostgreSQL places no
restrictions on whether or how a label provider must interpret security
labels; it merely provides a mechanism for storing them.
@@ -125,8 +125,8 @@ Use the *Advanced* tab to set advanced parameters for the database.
* Use *Schema restriction* field to provide a SQL restriction that will be used
against the pg_namespace table to limit the schemas that you see.
For example, you might enter: *public* so that only *public* are shown in
the pgAdmin browser.Separate entries with a comma or tab as you type.
For example, you might enter: *public* so that only *public* is shown in
the pgAdmin browser. Separate entries with a comma or tab as you type.
Click the *SQL* tab to continue.
+1
View File
@@ -17,3 +17,4 @@ PL/SQL code.
schema_diff
erd_tool
psql_tool
ai_tools
+1 -1
View File
@@ -89,7 +89,7 @@ Use the *Security Labels* panel to assign security labels. Click the *Add* icon
* Specify a security label provider in the *Provider* field. The named provider
must be loaded and must consent to the proposed labeling operation.
* Specify a a security label in the *Security Label* field. The meaning of a
* Specify a security label in the *Security Label* field. The meaning of a
given label is at the discretion of the label provider. PostgreSQL places no
restrictions on whether or how a label provider must interpret security
labels; it merely provides a mechanism for storing them.
+26 -21
View File
@@ -49,7 +49,7 @@ To modify the displayed data:
* To change a numeric value within the grid, double-click the value to select
the field. Modify the content in the square in which it is displayed.
* To change a non-numeric value within the grid, double-click the content to
access the edit bubble. After modifying the contentof the edit bubble, click
access the edit bubble. After modifying the content of the edit bubble, click
the *Ok* button to display your changes in the data grid, or *Cancel* to
exit the edit bubble without saving.
@@ -62,7 +62,7 @@ table. As soon as you store the data, the row is assigned a row number, and a
fresh empty line is added to the data grid.
To write a SQL NULL to the table, simply leave the field empty. When you store
the new row, the will server fill in the default value for that column. If you
the new row, the server will fill in the default value for that column. If you
store a change to an existing row, the value NULL will explicitly be written.
To write an empty string to the table, enter the special string '' (two single
@@ -101,6 +101,11 @@ properties of the geometries directly in map, just click the specific geometry:
viewer will render geometries with the same SRID in the map. If SRID=4326 the
OSM tile layer will be added into the map.
- *Custom tile provider:* A custom map tile provider ({z}/{x}/{y} URL template,
in EPSG:3857, EPSG:4326 or EPSG:3395) can be configured as a base layer via
*Preferences > Query Tool > Geometry Viewer*. When configured, it is
selected as the default base layer.
- *Data size:* For performance reasons, the viewer will render no more than
100000 geometries, totaling up to 20MB.
@@ -110,9 +115,9 @@ properties of the geometries directly in map, just click the specific geometry:
**JSON Data Editor**
A built in json editor is provided for *JSON/JSONB Data*.Double clicking on json/jsonb
data type cell in data grid will open JSON Editor.Editor provides different mode to
view and edit json data.
A built-in JSON editor is provided for *JSON/JSONB Data*. Double-clicking on a JSON/JSONB
data type cell in the data grid will open the JSON Editor. The editor provides different modes to
view and edit JSON data.
.. image:: images/json_editor_modes.png
@@ -120,21 +125,21 @@ view and edit json data.
:align: center
*Code Mode*:
Provides way to format & compact json data.Also provides ability to repair json data
by fixing quotes and escape characters, removing comments and JSONP notation and
turn JavaScript objects into JSON.
Provides a way to format and compact JSON data. Also provides the ability to repair JSON data
by fixing quotes and escape characters, removing comments and JSONP notation, and
turning JavaScript objects into JSON.
*Tree Mode*:
Enabled to change, add, move, remove, and duplicate fields and values.Provides ability
to searh & hilight data.
Enables changing, adding, moving, removing, and duplicating fields and values. Provides the ability
to search and highlight data.
*Form Mode*:
Allows only to edit values in json data there by providing ability to keep data structure
Allows editing only the values in JSON data, thereby providing the ability to keep the data structure
unchanged while editing.
*Preview Mode*:
Provides ability to check data before saving and also shows size of current json data.
Format and compact json data as well.
Provides the ability to check data before saving and also shows the size of the current JSON data.
Allows formatting and compacting JSON data as well.
*Editor Toolbar*
@@ -170,18 +175,18 @@ Tree/Form mode:
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
| *Transform* | Click to *Transform* to filter data using JSME query language. | Code, Preview, Tree |
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
| *Undo* | Click to *Undo* to undo last action performed on data . | Code, Preview, Tree, From |
| *Undo* | Click to *Undo* to undo last action performed on data. | Code, Preview, Tree, Form |
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
| *Redo* | Click to *Redo* to repat last action performed on data . | Code, Preview, Tree, From |
| *Redo* | Click to *Redo* to repeat last action performed on data. | Code, Preview, Tree, Form |
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
| *Mode* | Click to *Mode* dropdown to change dipaly mode of editor.Different modes avaialble | Code, Tree, From, Preview |
| | are Code, Preview, Tree, From. | |
| *Mode* | Click the *Mode* dropdown to change the display mode of the editor. Different modes | Code, Tree, Form, Preview |
| | available are Code, Preview, Tree, and Form. | |
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
| *Expand All* | Click to *Expand All* to expand json data. | Tree, From |
| *Expand All* | Click *Expand All* to expand JSON data. | Tree, Form |
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
| *Collapse All* | Click to *Redo* to collapse json data. | Tree, From |
| *Collapse All* | Click *Collapse All* to collapse JSON data. | Tree, Form |
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
| *Search Box* | Enter partial/complete string to search in data. | Tree, From |
| *Search Box* | Enter partial/complete string to search in data. | Tree, Form |
+----------------------+----------------------------------------------------------------------------------------+---------------------------+
@@ -235,6 +240,6 @@ A View/Edit Data tab can be converted to a Query Tool Tab just by editing the qu
:alt: Promote View/Edit Data tab to Query Tool tab warning
:align: center
You can disable the dialog by selecting the "Don't Ask again" checkbox. If you wish to resume the confirmation dialog, you can do it from "Prefrences -> Query Tool -> Editor -> Show View/Edit Data Promotion Warning?"
You can disable the dialog by selecting the "Don't Ask again" checkbox. If you wish to resume the confirmation dialog, you can do it from "Preferences -> Query Tool -> Editor -> Show View/Edit Data Promotion Warning?"
Once you chose to continue, you won't be able to use the features of View/Edit mode like the filter and sorting options, limit, etc. It is a one-way conversion. It will be a query tool now.
+16 -10
View File
@@ -72,7 +72,7 @@ Export Options
| | You can select the option *With DROP Table* if you wish to have DROP Table DDL statements before | |
| | each CREATE Table DDL. You can see the option by clicking the down arrow beside the SQL button. | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Download image* | Click the *Download image* icon to save the ERD diagram in a image formate | Option/Alt + |
| *Download image* | Click the *Download image* icon to save the ERD diagram in an image format. | Option/Alt + |
| | | Ctrl + I |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
@@ -86,6 +86,9 @@ Editing Options
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| Icon | Behavior | Shortcut |
+======================+===================================================================================================+================+
| *Search table* | Click to search for a table in the diagram. Selecting a table from the search results will bring | Option/Alt + |
| | it into view and highlight it. | Ctrl + F |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Add table* | Click this button to add a new table to the diagram. On clicking, this will open a table dialog | Option/Alt + |
| | where you can put the table details. | Ctrl + A |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
@@ -109,11 +112,14 @@ Table Relationship Options
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| Icon | Behavior | Shortcut |
+======================+===================================================================================================+================+
| *1M* | Click this button to open a one-to-many relationship dialog to add a relationship between the | Option/Alt + |
| *1-1* | Click this button to open a one-to-one relationship dialog to add a relationship between the | Option/Alt + |
| | two tables. The selected table becomes the referencing table. | Ctrl + B |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *1-M* | Click this button to open a one-to-many relationship dialog to add a relationship between the | Option/Alt + |
| | two tables. The selected table becomes the referencing table and will have the *many* endpoint of | Ctrl + O |
| | the link. | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *MM* | Click this button to open a many-to-many relationship dialog to add a relationship between the | Option/Alt + |
| *M-M* | Click this button to open a many-to-many relationship dialog to add a relationship between the | Option/Alt + |
| | two tables. This option will create a new table based on the selected columns for the two relating| Ctrl + M |
| | tables and link them. | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
@@ -129,7 +135,7 @@ Node Color Options
| Icon | Behavior |
+======================+==========================================================================================================+
| *Fill Color* | Use Fill Color to change the background color of a table node. This is helpful if you want to |
| | identify a of group tables. Once set, all the newly added tables will take the same color. |
| | identify a group of tables. Once set, all the newly added tables will take the same color. |
+----------------------+----------------------------------------------------------------------------------------------------------+
| *Text Color* | Use Text Color to change the text color of a table node based on the fill color to make text |
| | easily readable. |
@@ -148,7 +154,7 @@ Utility Options
| *Add/Edit note* | Click this button to make notes on tables nodes while designing the database. | Option/Alt + |
| | | Ctrl + N |
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
| *Auto align* | Click this button to auto align all tables and links to make it look more cleaner. | Option/Alt + |
| *Auto align* | Click this button to auto-align all tables and links to make them look cleaner. | Option/Alt + |
| | | Ctrl + L |
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
| *Show details* | Click this button to toggle the column details visibility. It allows you to show few or more | Option/Alt + |
@@ -188,7 +194,7 @@ Table Dialog
The table dialog allows you to:
* Change the table structure details.
* It can be used edit an existing table or add a new one.
* It can be used to edit an existing table or add a new one.
* Refer :ref:`table dialog <table_dialog>` for information on different fields.
Table Node
@@ -200,11 +206,11 @@ Table Node
The table node shows table details in a graphical representation:
* The top bar has a *details toggle button* that is used to toggle column details visibility. There is also a *note button* that is visible only if there is some note added. you can click on this button to quickly change the note.
* The top bar has a *details toggle button* that is used to toggle column details visibility. There is also a *note button* that is visible only if there is some note added. You can click on this button to quickly change the note.
* The first row shows the schema name of the table. Eg. *public* in above image.
* The second row shows the table name. Eg. *users* in above image.
* All other rows below the table name are the columns of the table along with data type. If the column is a primary key then it will have lock key icon eg. id is the primary key in above image. Otherwise, it will have column icon.
* you can click on the node and drag to move on the canvas.
* You can click on the node and drag to move on the canvas.
* Upon double click on the table node or by clicking the edit button from the toolbar, the table dialog opens where you can change the table details. Refer :ref:`table dialog <table_dialog>` for information on different fields.
The One to One Link Dialog
@@ -219,7 +225,7 @@ The one to one link dialog allows you to:
* Add a one to one relationship between two tables.
* *Local Table* is the table that references a table and has the *one* end point.
* *Local Column* the column that references.
* *Select Constraint* To implement one to one relationship, the *Local Column* must have primaty key or unique constraint. The default is a unique constraint. Please note that this field is visible only when the selected *Local Column* does not have either of the mentioned constraints.
* *Select Constraint* To implement a one-to-one relationship, the *Local Column* must have a primary key or unique constraint. The default is a unique constraint. Please note that this field is visible only when the selected *Local Column* does not have either of the mentioned constraints.
* *Referenced Table* is the table that is being referred and has the *one* end point.
* *Referenced Column* the column that is being referred.
@@ -266,7 +272,7 @@ The table link shows relationship between tables:
* The single line endpoint of the link shows the column that is being referred.
* The three line endpoint of the link shows the column that refers.
* If one of the columns that is being referred or that refers is removed from the table then the link will get dropped.
* you can click on the link and drag to move on the canvas.
* You can click on the link and drag to move on the canvas.
The Table Notes
+1 -1
View File
@@ -67,7 +67,7 @@ in the *Delete Row* popup.
Click the *SQL* tab to continue.
Your entries in the *Event Trigger* dialog generate a generate a SQL command.
Your entries in the *Event Trigger* dialog generate a SQL command.
Use the *SQL* tab for review; revisit or switch tabs to make any changes to the
SQL command.
+2 -2
View File
@@ -58,8 +58,8 @@ Click the *Query* tab to continue.
Use the fields in the *Query* tab to write the query that will be exported:
* Use the *Export Data Query* field to specifies A SELECT, VALUES, INSERT, UPDATE,
DELETE, or MERGE command whose results are to be copied .
* Use the *Export Data Query* field to specify a SELECT, VALUES, INSERT, UPDATE,
DELETE, or MERGE command whose results are to be copied.
* Use *Force Quote columns* field to forces quoting to be used for all non-NULL
values in each specified column. NULL output is never quoted. This is a creatable
+1 -1
View File
@@ -23,7 +23,7 @@ Use the fields in the *General* tab to identify an extension:
* Use the drop-down listbox in the *Name* field to select the extension. Each
extension must have a unique name.
* Move the switch next to *Cascade?* towards right position to automatically
* Move the switch next to *Cascade?* to the *Yes* position to automatically
install any extensions that this extension depends on that are not already
installed.
* Store notes about the extension in the *Comment* field.
+1 -1
View File
@@ -131,7 +131,7 @@ function. Click the *Add* icon (+) to add each security label selection:
* Specify a security label provider in the *Provider* field. The named provider
must be loaded and must consent to the proposed labeling operation.
* Specify a a security label in the *Security Label* field. The meaning of a
* Specify a security label in the *Security Label* field. The meaning of a
given label is at the discretion of the label provider. PostgreSQL places no
restrictions on whether or how a label provider must interpret security
labels; it merely provides a mechanism for storing them.
+7 -4
View File
@@ -41,6 +41,9 @@ Use the fields in the *Definition* tab to define the function:
will change to an input text field.
* Use the drop-down listbox next to *Language* to select the implementation
language. The default is *sql*.
* Use the drop-down listbox next to *Depends on extensions* to select the extension that this function
depends on (for example, plpgsql). If set, dropping the extension will automatically drop the
function as well.
* Use the fields in the *Arguments* to define an argument. Click the *Add*
icon (+) to set parameters and values for the argument:
@@ -153,7 +156,7 @@ function. Click the *Add* icon (+) to add each security label selection:
* Specify a security label provider in the *Provider* field. The named provider
must be loaded and must consent to the proposed labeling operation.
* Specify a a security label in the *Security Label* field. The meaning of a
* Specify a security label in the *Security Label* field. The meaning of a
given label is at the discretion of the label provider. PostgreSQL places no
restrictions on whether or how a label provider must interpret security
labels; it merely provides a mechanism for storing them.
@@ -164,7 +167,7 @@ in the *Delete Row* popup.
Click the *SQL* tab to continue.
Your entries in the *Function* dialog generate a generate a SQL command (see an
Your entries in the *Function* dialog generate a SQL command (see an
example below). Use the *SQL* tab for review; revisit or switch tabs to make any
changes to the SQL command.
@@ -178,8 +181,8 @@ the *Function* dialog:
:alt: Function dialog sql tab
:align: center
The example demonstrates creating an *plpgsql* function named *hire_salesmen*. The
function have three columns (p_ename, p_sal and p_comm).
The example demonstrates creating a *plpgsql* function named *hire_salesmen*. The
function has three columns (p_ename, p_sal and p_comm).
* Click the *Info* button (i) to access online help.
* Click the *Save* button to save work.
+1 -1
View File
@@ -46,7 +46,7 @@ Use the fields in the *Privileges Selection* window to grant
privileges. If you grant a privilege WITH GRANT OPTION, the Grantee will have
the right to grant privileges on the object to others. If WITH GRANT OPTION is
subsequently revoked, any role who received access to that object from that
Grantee (directly or through a chain of grants) will lose thier privileges on
Grantee (directly or through a chain of grants) will lose their privileges on
the object.
* Click the *Add* icon (+) to assign a set of privileges.
Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 679 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 72 KiB

+4 -2
View File
@@ -132,7 +132,9 @@ The JSON file format used when importing or exporting servers is quite
straightforward and simply contains a list of servers, with a number of
attributes. The following attributes are required to be present in every server
definition: Name, Group, Port, Username, SSLMode, MaintenanceDB and one of Host,
HostAddr or Service.
HostAddr or Service. A shared server may supply SharedUsername in place of
Username, in which case the shared username is also used as the username of the
imported server.
Password fields cannot be imported or exported.
@@ -196,4 +198,4 @@ The following example shows both a minimally defined and a fully defined server:
}
}
.. note:: If you're running pgAdmin in Server Mode, the values for parameters that require file paths should be specified relative to the user's home directory which is /<DATA_DIR>/storage/<user_id>/. If DATA_DIR is not specified, then it will /var/libe/pgadmin/storage/<user_id>/
.. note:: If you're running pgAdmin in Server Mode, the values for parameters that require file paths should be specified relative to the user's home directory which is /<DATA_DIR>/storage/<user_id>/. If DATA_DIR is not specified, then it will be /var/lib/pgadmin/storage/<user_id>/
+28 -15
View File
@@ -48,6 +48,31 @@ Use the fields in the *Definition* tab to define the index:
* Select *brin* to create a BRIN index. A BRIN index may improve
performance when managing minimum and maximum values and ranges.
* Use the drop-down listbox next to *Depends on extensions* to select the extension
that this index depends on (for example, edbspl). If set, dropping the extension
will automatically drop the index as well.
* Move the switch next to *Only Table?* to the *Yes* position to create the index
only on the parent table without recursing to its partitions. This button will be
enabled for partition tables only. The default is *No*.
* Move the switch next to *Unique?* to the *Yes* position to check for duplicate values
in the table when the index is created and when data is added. The default is *No*.
* Move the switch next to *NULLs not distinct?* to the *Yes* position to treat null values as not distinct.
The default is *No*. This option is available only on PostgreSQL 15 and above.
* Move the *Clustered?* switch to the *Yes* position to instruct the server to
cluster the table.
* Move the *Concurrent build?* switch to the *Yes* position to build the index
without taking any locks that prevent concurrent inserts, updates, or deletes
on the table.
* Use the *Constraint* field to provide a constraint expression; a constraint
expression limits the entries in the index to those rows that satisfy the
constraint.
Click the *With* tab to continue.
.. image:: images/index_with.png
:alt: Index dialog with tab
:align: center
* Use the *Fill Factor* field to specify a fill factor for the index. The fill
factor specifies how full the selected method will try to fill each index
page.
@@ -57,24 +82,12 @@ Use the fields in the *Definition* tab to define the index:
for each entry of a BRIN index.
* Select *Buffering* to specify whether the buffering build technique is used to build the index. The default is
*Auto*
* Move the switch next to *Deduplicate items?* towards the *right position* to control usage of the B-tree
* Move the switch next to *Deduplicate items?* to the *Yes* position to control usage of the B-tree
deduplication technique. The default is *Yes*. This option is available only on PostgreSQL 13 and above.
* Move the switch next to *Fast update?* towards the *right position* to control usage of the fast update technique.
* Move the switch next to *Fast update?* to the *Yes* position to control usage of the fast update technique.
The default is *Yes*.
* Move the switch next to *Autosummarize* towards the *right position* to define whether a summarization run is
* Move the switch next to *Autosummarize* to the *Yes* position to define whether a summarization run is
queued for the previous page range whenever an insertion is detected on the next one. The default is *No*
* Move the switch next to *Unique?* towards the *right position* to check for duplicate values
in the table when the index is created and when data is added. The default is *No*.
* Move the switch next to *NULLs not distinct?* towards the *right position* to treat null values as not distinct.
The default is*No*. This option is available only on PostgreSQL 15 and above.
* Move the *Clustered?* switch to the *Yes* position to instruct the server to
cluster the table.
* Move the *Concurrent build?* switch to the *Yes* position to build the index
without taking any locks that prevent concurrent inserts, updates, or deletes
on the table.
* Use the *Constraint* field to provide a constraint expression; a constraint
expression limits the entries in the index to those rows that satisfy the
constraint.
Click the *Columns* tab to continue.
+2
View File
@@ -22,6 +22,8 @@ When using main browser window, the following keyboard shortcuts are available:
+----------------------------+--------------------+------------------------------------+
| Shift + Alt + b | Shift + Option + b | Object explorer |
+----------------------------+--------------------+------------------------------------+
| Ctrl + Alt + b | Ctrl + Option + b | Toggle Object Explorer |
+----------------------------+--------------------+------------------------------------+
| Shift + Alt + n | Shift + Option + n | Create object |
+----------------------------+--------------------+------------------------------------+
| Shift + Alt + d | Shift + Option + d | Delete object |
+1 -1
View File
@@ -64,7 +64,7 @@ function. Click the *Add* icon (+) to add each security label selection:
* Specify a security label provider in the *Provider* field. The named provider
must be loaded and must consent to the proposed labeling operation.
* Specify a a security label in the *Security Label* field. The meaning of a
* Specify a security label in the *Security Label* field. The meaning of a
given label is at the discretion of the label provider. PostgreSQL places no
restrictions on whether or how a label provider must interpret security
labels; it merely provides a mechanism for storing them.
+27
View File
@@ -87,6 +87,8 @@ There are 3 ways to configure LDAP:
"LDAP_KEY_FILE","Specifies the path to the server private key file. This parameter
is applicable only if you are using *ldaps* as connection protocol or you have
set *LDAP_USE_STARTTLS* parameter to *True*."
"LDAP_CERT_VALIDATE", "Set this parameter to *False* if you want to bypass
the TLS certificate validation. By default it is set to True."
"LDAP_IGNORE_MALFORMED_SCHEMA", "Some flaky LDAP servers returns malformed schema.
If this parameter set to *True*, no exception will be raised and schema is thrown away
but authentication will be done. This parameter should remain False, as recommended."
@@ -108,3 +110,28 @@ There are 3 ways to configure LDAP:
"LDAP_BIND_PASSWORD", "Password for simple bind.
Specify the value if you have set the LDAP_BIND_USER parameter."
Brute-force protection
**********************
pgAdmin's built-in account-lockout setting (``MAX_LOGIN_ATTEMPTS``) applies
only to accounts using the ``INTERNAL`` authentication source. It does
**not** rate-limit or lock LDAP-authenticated logins.
When LDAP authentication is enabled, brute-force protection for LDAP
credentials is the responsibility of:
- **The directory.** Configure account-lockout policy on the LDAP server
itself (for example, OpenLDAP's ``ppolicy`` overlay, Active Directory's
account-lockout GPO, or the equivalent on FreeIPA / 389 Directory
Server). The directory is the credential authority and is the correct
place to enforce per-account lockout.
- **The reverse proxy or WAF in front of pgAdmin.** Configure
request-rate limiting on the login endpoint (for example, ``limit_req``
in nginx, ``stick-table`` rules in HAProxy, or the equivalent in your
WAF) to bound login-attempt volume per source IP. This is independent
of pgAdmin and protects against attackers who would otherwise reach the
LDAP server through pgAdmin.
See :ref:`Avoiding a bruteforce attack <login>` for the equivalent note
on the login page.
+1 -1
View File
@@ -13,7 +13,7 @@ documentation is attributed to the
.. topic:: pgAdmin 4
Copyright (C) 2013 - 2025, The pgAdmin Development Team
Copyright (C) 2013 - 2026, The pgAdmin Development Team
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose, without fee, and without a written agreement is
+15 -3
View File
@@ -60,6 +60,18 @@ asking you to contact the LDAP administrator to recover your LDAP password.
Avoiding a bruteforce attack
****************************
You have the possibility to lock an account by setting ``MAX_LOGIN_ATTEMPTS``
once it has reached the maximum number of login attempts.
You can disable this feature by setting the value to zero.
For accounts using the ``INTERNAL`` authentication source, pgAdmin locks
the account after ``MAX_LOGIN_ATTEMPTS`` consecutive failed logins. The
default is 3; set it to zero to disable.
For accounts authenticated against an external identity provider
(``LDAP``, ``OAUTH2``, ``KERBEROS``, ``WEBSERVER``), brute-force protection
is **not** handled by pgAdmin and ``MAX_LOGIN_ATTEMPTS`` does not apply.
Operators using these sources should:
- Configure account-lockout policy on the upstream provider (for example,
Active Directory's account-lockout GPO, OpenLDAP's ``ppolicy`` overlay,
or the OAuth2 provider's rate limits).
- Configure request rate-limiting (for example, ``limit_req`` in nginx)
on the reverse proxy in front of pgAdmin to bound login-attempt volume
per source IP.
+13 -13
View File
@@ -29,31 +29,31 @@ maintenance:
* Move the *ANALYZE* switch to the *Yes* position to issue ANALYZE commands
whenever the content of a table has changed sufficiently.
* Move the *DISABLE PAGE SKIPPING* switch to the *Yes* position to disables
* Move the *DISABLE PAGE SKIPPING* switch to the *Yes* position to disable
all page-skipping behavior.
* Move the *SKIP LOCKED* switch to the *Yes* position to specifies that
* Move the *SKIP LOCKED* switch to the *Yes* position to specify that
VACUUM should not wait for any conflicting locks to be released when
beginning work on a relation. This option is available from v12 onwards.
* Move the *TRUNCATE* switch to the *Yes* position to specifies that VACUUM
* Move the *TRUNCATE* switch to the *Yes* position to specify that VACUUM
should attempt to truncate off any empty pages at the end of the table and
allow the disk space for the truncated pages to be returned to the operating
system. This option is available from v12 onwards.
* Move the *PROCESS TOAST* switch to the *Yes* position to specifies that
* Move the *PROCESS TOAST* switch to the *Yes* position to specify that
VACUUM should attempt to process the corresponding TOAST table for each
relation, if one exists. This option is available from v14 onwards.
* Move the *PROCESS MAIN* switch to the *Yes* position to specifies that
* Move the *PROCESS MAIN* switch to the *Yes* position to specify that
VACUUM should attempt to process the main relation. This option is available
from v16 onwards.
* Move the *SKIP DATABASE STATS* switch to the *Yes* position to specifies
* Move the *SKIP DATABASE STATS* switch to the *Yes* position to specify
that VACUUM should skip updating the database-wide statistics about oldest
unfrozen XIDs. This option is available from v16 onwards.
* Move the *ONLY DATABASE STATS* switch to the *Yes* position to specifies
* Move the *ONLY DATABASE STATS* switch to the *Yes* position to specify
that VACUUM should do nothing except update the database-wide statistics
about oldest unfrozen XIDs . This option is available from v16 onwards.
@@ -64,7 +64,7 @@ maintenance:
of VACUUM in parallel using integer background workers. This option is
available from v13 onwards.
* Use the *BUFFER USAGE LIMIT* field to specifies the Buffer Access Strategy
* Use the *BUFFER USAGE LIMIT* field to specify the Buffer Access Strategy
ring buffer size for VACUUM. This size is used to calculate the number of
shared buffers which will be reused as part of this strategy. This option
is available from v16 onwards
@@ -78,11 +78,11 @@ maintenance:
This enables the query optimizer to select the fastest query plan for optimal
performance.
* Move the *SKIP LOCKED* switch to the *Yes* position to specifies that
* Move the *SKIP LOCKED* switch to the *Yes* position to specify that
ANALYZE should not wait for any conflicting locks to be released when
beginning work on a relation. This option is available from v12 onwards.
* Use the *BUFFER USAGE LIMIT* field to specifies the Buffer Access Strategy
* Use the *BUFFER USAGE LIMIT* field to specify the Buffer Access Strategy
ring buffer size for ANALYZE. This size is used to calculate the number of
shared buffers which will be reused as part of this strategy. This option
is available from v16 onwards
@@ -103,7 +103,7 @@ maintenance:
without taking any locks that prevent concurrent inserts, updates, or
deletes on the table. This option is available from v12 onwards.
* Use the *TABLESPACE* field to specifies that indexes will be rebuilt on
* Use the *TABLESPACE* field to specify that indexes will be rebuilt on
a new tablespace. This option is available from v14 onwards.
.. image:: images/maintenance_cluster.png
@@ -118,6 +118,6 @@ switch to the *No* position; by default, status messages are included.
When you've completed the dialog, click *OK* to start the background process;
to exit the dialog without performing maintenance operations, click *Cancel*.
pgAdmin will run the maintenance process in background. You can view all the background
process with there running status and logs on the :ref:`Processes <processes>`
pgAdmin will run the maintenance process in the background. You can view all the background
processes with their running status and logs on the :ref:`Processes <processes>`
tab.
+8 -5
View File
@@ -10,7 +10,7 @@ the REFRESH MATERIALIZED VIEW command to update the content of a materialized
view.
The *Materialized View* dialog organizes the development of a materialized_view
through the following dialog tabs: *General*, *Definition*, *Storage*,
through the following dialog tabs: *General*, *Definition*, *Code*,
*Parameter*, and *Security*. The *SQL* tab displays the SQL code generated by
dialog selections.
@@ -31,10 +31,10 @@ Use the fields in the *General* tab to identify the materialized view:
Click the *Definition* tab to continue.
.. image:: images/materialized_view_definition.png
:alt: Materialized view dialog storage tab
:alt: Materialized view dialog definition tab
:align: center
Use the fields in the *Storage* tab to maintain the materialized view:
Use the fields in the *Definition* tab to maintain the materialized view:
* Move the *With Data* switch to the *Yes* position to specify the materialized
view should be populated at creation time. If not, the materialized view
@@ -47,11 +47,14 @@ Use the fields in the *Storage* tab to maintain the materialized view:
* Use the *Fill Factor* field to specify a fill factor for the materialized
view. The fill factor for a table is a percentage between 10 and 100. 100
(complete packing) is the default.
* Use the drop-down listbox next to *Depends on extensions* to select the extension that this materialized view
depends on (for example, edbspl). If set, dropping the extension will automatically drop the
materialized view as well.
Click the *Code* tab to continue.
.. image:: images/materialized_view_code.png
:alt: Materialized view dialog definition tab
:alt: Materialized view dialog code tab
:align: center
Use the text editor field in the *Code* tab to provide the query that will
@@ -101,7 +104,7 @@ selection:
* Specify a security label provider in the *Provider* field. The named provider
must be loaded and must consent to the proposed labeling operation.
* Specify a a security label in the *Security Label* field. The meaning of a
* Specify a security label in the *Security Label* field. The meaning of a
given label is at the discretion of the label provider. PostgreSQL places no
restrictions on whether or how a label provider must interpret security
labels; it merely provides a mechanism for storing them.
+38 -8
View File
@@ -5,8 +5,10 @@
*****************
The pgAdmin menu bar provides drop-down menus for access to options, commands,
and utilities. The menu bar displays the following selections: *File*, *Object*,
Tools*, and *Help*. Selections may be grayed out which indicates they are
and utilities. Its layout adapts based on your deployment: in Web mode,
you will see *File*, *Object*, *Tools*, and *Help* menus. In Desktop mode,
the *pgAdmin4* application menu replaces the standard *File* menu.
Selections may be grayed out which indicates they are
disabled for the object currently selected in the *pgAdmin* tree control.
The File Menu
@@ -16,7 +18,7 @@ The File Menu
:alt: pgAdmin file menu bar
:align: center
Use the *File* menu to access the following options:
Use the *File* menu available in web mode to access the following options:
+-------------------------+---------------------------------------------------------------------------------------------------------+
| Option | Action |
@@ -25,8 +27,30 @@ Use the *File* menu to access the following options:
+-------------------------+---------------------------------------------------------------------------------------------------------+
| *Reset Layout* | If you have modified the workspace, click to restore the default layout. |
+-------------------------+---------------------------------------------------------------------------------------------------------+
| *Runtime* | Click to open a submenu to Configure, View Log and Zoom settings. Only visible when pgAdmin4 runs in |
| | desktop mode. To know more about runtime menu :ref:`click here <desktop_deployment>` |
The pgAdmin4 Menu
*****************
.. image:: /images/pgadmin4_menu.png
:alt: pgAdmin pgadmin4 menu bar
:align: center
Use the *pgAdmin4* menu available in desktop mode to access the following options:
+-------------------------+---------------------------------------------------------------------------------------------------------+
| Option | Action |
+=========================+=========================================================================================================+
| *About pgAdmin 4* | Provide pgadmin4 configuration information like version, mode etc. |
+-------------------------+---------------------------------------------------------------------------------------------------------+
| *Preferences/settings* | Click to open the :ref:`Preferences <preferences>` dialog to customize your pgAdmin settings. |
+-------------------------+---------------------------------------------------------------------------------------------------------+
| *Reset Layout* | If you have modified the workspace, click to restore the default layout. |
+-------------------------+---------------------------------------------------------------------------------------------------------+
| *View Logs* | This will show current pgadmin4 logs. |
+-------------------------+---------------------------------------------------------------------------------------------------------+
| *Configure runtime* | Click to open a window that allows to configure application port, connection timeout to launch the |
| | application. To know more about runtime menu :ref:`click here <desktop_deployment>` |
+-------------------------+---------------------------------------------------------------------------------------------------------+
The Object Menu
@@ -81,9 +105,9 @@ following options (in alphabetical order):
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Trigger(s)* | Click to *Disable* or *Enable* trigger(s) for the currently selected table. Options are displayed on the flyout menu. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *Truncate* | Click to remove all rows from a table (*Truncate*), to remove all rows from a table and its child tables |
| | (*Truncate Cascade*) or to remove all rows from a table and automatically restart sequences owned by columns |
| | (*Truncate Restart Identity*). Options are displayed on the flyout menu. |
| *Truncate* | Click to remove all rows from a table/foreign tables (*Truncate*), to remove all rows from a table/foreign tables and |
| | its child tables (*Truncate Cascade*) or to remove all rows from a table/foreign tables and automatically restart |
| | sequences owned by columns (*Truncate Restart Identity*). Options are displayed on the flyout menu. |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
| *View Data* | Click to access a context menu that provides several options for viewing data (see below). |
+-----------------------------+--------------------------------------------------------------------------------------------------------------------------+
@@ -132,6 +156,12 @@ Use the *Tools* menu to access the following options (in alphabetical order):
+------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+
| *Search Objects...* | Click to open the :ref:`Search Objects... <search_objects>` and start searching any kind of objects in a database. |
+------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+
| *AI Reports* | Click to access a submenu with AI-powered analysis options (requires :ref:`AI configuration <ai_tools>`): |
| | |
| | - *Security Report* - Generate an AI-powered security analysis for the selected server, database, or schema. |
| | - *Performance Report* - Generate an AI-powered performance analysis for the selected server or database. |
| | - *Design Report* - Generate an AI-powered design review for the selected database or schema. |
+------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+
| *Add named restore point* | Click to open the :ref:`Add named restore point... <add_restore_point_dialog>` dialog to take a point-in-time snapshot of the current |
| | server state. |
+------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+
+299 -19
View File
@@ -1,15 +1,59 @@
.. _oauth2:
*****************************************
`Enabling OAUTH2 Authentication`:index:
*****************************************
*******************************************************
`Enabling OAUTH2 and OIDC Authentication`:index:
*******************************************************
To enable OAUTH2 authentication for pgAdmin, you must configure the OAUTH2
settings in the *config_local.py* or *config_system.py* file (see the
:ref:`config.py <config_py>` documentation) on the system where pgAdmin is
installed in Server mode. You can copy these settings from *config.py* file
and modify the values for the following parameters:
To enable OAUTH2 or OpenID Connect (OIDC) authentication for pgAdmin, you must
configure the OAUTH2 settings in the *config_local.py* or *config_system.py*
file (see the :ref:`config.py <config_py>` documentation) on the system where
pgAdmin is installed in Server mode. You can copy these settings from *config.py*
file and modify the values for the following parameters.
.. warning::
pgAdmin reads OAuth2 provider settings **only** from the ``OAUTH2_CONFIG``
list; each provider is a single dictionary entry within that list. Settings
such as ``OAUTH2_CLIENT_ID`` or ``OAUTH2_SSL_CERT_VERIFICATION`` defined at
the top level of the configuration are ignored.
This matters in container deployments. Although most options can be set with
an individual ``PGADMIN_CONFIG_<KEY>`` environment variable, OAuth2 is the
exception: individual variables such as ``PGADMIN_CONFIG_OAUTH2_CLIENT_ID``
or ``PGADMIN_CONFIG_OAUTH2_SSL_CERT_VERIFICATION`` will **not** configure a
provider. Instead, configure the whole provider list through a single
``PGADMIN_CONFIG_OAUTH2_CONFIG`` variable, for example::
PGADMIN_CONFIG_OAUTH2_CONFIG="[{'OAUTH2_NAME': 'my-provider', 'OAUTH2_DISPLAY_NAME': 'My Provider', 'OAUTH2_CLIENT_ID': '...', 'OAUTH2_CLIENT_SECRET': '...', 'OAUTH2_SERVER_METADATA_URL': 'https://provider.example.com/.well-known/openid-configuration', 'OAUTH2_SCOPE': 'openid email profile', 'OAUTH2_SSL_CERT_VERIFICATION': False}]"
If pgAdmin detects per-provider OAuth2 settings at the top level while no
provider is configured in ``OAUTH2_CONFIG``, it logs a warning at startup
to highlight the misconfiguration.
OAuth2 vs OpenID Connect (OIDC)
================================
pgAdmin supports both OAuth2 and OIDC authentication protocols:
**OAuth2** is an authorization framework that allows third-party applications to
obtain limited access to user accounts. When using OAuth2, pgAdmin must explicitly
call the provider's userinfo endpoint to retrieve user profile information.
**OpenID Connect (OIDC)** is an identity layer built on top of OAuth2 that provides
standardized user authentication and profile information. When using OIDC, user
identity information is included directly in the ID token, which is more efficient
and secure.
.. note::
When **OAUTH2_SERVER_METADATA_URL** is configured, pgAdmin treats the provider
as an OIDC provider and will:
- Use ID token claims for user identity (sub, email, preferred_username)
- Skip the userinfo endpoint call when ID token contains sufficient information
- Validate the ID token automatically using the provider's public keys
This is the **recommended approach** for modern identity providers like
Microsoft Entra ID (Azure AD), Google, Keycloak, Auth0, and Okta.
.. _AzureAD: https://learn.microsoft.com/en-us/security/zero-trust/develop/configure-tokens-group-claims-app-roles
@@ -23,29 +67,28 @@ and modify the values for the following parameters:
"AUTHENTICATION_SOURCES", "The default value for this parameter is *internal*.
To enable OAUTH2 authentication, you must include *oauth2* in the list of values
for this parameter. you can modify the value as follows:
for this parameter. You can modify the value as follows:
* [‘oauth2’, ‘internal’]: pgAdmin will display an additional button for authenticating with oauth2"
"OAUTH2_NAME", "The name of the Oauth2 provider, ex: Google, Github"
"OAUTH2_DISPLAY_NAME", "Oauth2 display name in pgAdmin"
"OAUTH2_CLIENT_ID", "Oauth2 Client ID"
"OAUTH2_CLIENT_SECRET", "Oauth2 Client Secret"
"OAUTH2_CLIENT_SECRET", "Oauth2 Client Secret. **Optional for public clients using Authorization Code + PKCE**. For confidential clients (server-side apps), keep this set. For public clients (no secret), pgAdmin will enforce PKCE and perform an unauthenticated token exchange."
"OAUTH2_CLIENT_AUTH_METHOD", "Client authentication method for the token endpoint. Default behavior uses *OAUTH2_CLIENT_SECRET* (confidential client), or PKCE when no secret is provided (public client). Set to *workload_identity* to authenticate using an Azure Entra ID workload identity (federated credential) without a client secret."
"OAUTH2_WORKLOAD_IDENTITY_TOKEN_FILE", "When **OAUTH2_CLIENT_AUTH_METHOD** is *workload_identity*, path to the projected OIDC token file (Kubernetes service account JWT). This file must exist at pgAdmin startup."
"OAUTH2_TOKEN_URL", "Oauth2 Access Token endpoint"
"OAUTH2_AUTHORIZATION_URL", "Endpoint for user authorization"
"OAUTH2_SERVER_METADATA_URL", "Server metadata url for your OAuth2 provider"
"OAUTH2_SERVER_METADATA_URL", "**OIDC Discovery URL** (recommended for OIDC providers). When set, pgAdmin will use OIDC flow with automatic ID token validation and user claims from the ID token. Example: *https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration*. When using this parameter, OAUTH2_TOKEN_URL and OAUTH2_AUTHORIZATION_URL are optional as they will be discovered automatically."
"OAUTH2_API_BASE_URL", "Oauth2 base URL endpoint to make requests simple, ex: *https://api.github.com/*"
"OAUTH2_USERINFO_ENDPOINT", "User Endpoint, ex: *user* (for github, or *user/emails* if the user's email address is private) and *userinfo* (for google),"
"OAUTH2_SCOPE", "Oauth scope, ex: 'openid email profile'. Note that an 'email' claim is required in the resulting profile."
"OAUTH2_ICON", "The Font-awesome icon to be placed on the oauth2 button, ex: fa-github"
"OAUTH2_USERINFO_ENDPOINT", "User Endpoint, ex: *user* (for github, or *user/emails* if the user's email address is private) and *userinfo* (for google). **For OIDC providers**, this is optional if the ID token contains sufficient claims (email, preferred_username, or sub)."
"OAUTH2_SCOPE", "Oauth scope, ex: 'openid email profile'. **For OIDC providers**, include 'openid' scope to receive an ID token."
"OAUTH2_ICON", "The Font Awesome icon to be placed on the oauth2 button. A brand icon name (e.g. fa-github) uses the brands style by default; to use another style, include the style class explicitly (e.g. 'fas fa-key')."
"OAUTH2_BUTTON_COLOR", "Oauth2 button color"
"OAUTH2_USERNAME_CLAIM", "The claim which is used for the username. If the value is empty
the email is used as username, but if a value is provided, the claim has to exist. Ex: *oid* (for AzureAD), *email* (for Github)"
"OAUTH2_USERNAME_CLAIM", "The claim which is used for the username. If the value is empty, **for OIDC providers** pgAdmin will use: 1) email, 2) preferred_username, or 3) sub (in that order). **For OAuth2 providers** without OIDC, email is required. Ex: *oid* (for AzureAD), *email* (for Github), *preferred_username* (for Keycloak)"
"OAUTH2_AUTO_CREATE_USER", "Set the value to *True* if you want to automatically
create a pgAdmin user corresponding to a successfully authenticated Oauth2 user.
Please note that password is not stored in the pgAdmin database."
"OAUTH2_ADDITIONAL_CLAIMS", "If a dictionary is provided, pgAdmin will check for a matching key and value on the userinfo endpoint
and in the Id Token. In case there is no match with the provided config, the user will receive an authorization error.
Useful for checking AzureAD_ *wids* or *groups*, GitLab_ *owner*, *maintainer* and *reporter* claims."
"OAUTH2_ADDITIONAL_CLAIMS", "If a dictionary is provided, pgAdmin will check for a matching key and value on the **ID token first** (for OIDC providers), then fall back to the userinfo endpoint response. In case there is no match with the provided config, the user will receive an authorization error. Useful for checking AzureAD_ *wids* or *groups*, GitLab_ *owner*, *maintainer* and *reporter* claims."
"OAUTH2_SSL_CERT_VERIFICATION", "Set this variable to False to disable SSL certificate verification for OAuth2 provider.
This may need to set False, in case of self-signed certificates."
"OAUTH2_CHALLENGE_METHOD", "Enable PKCE workflow. PKCE method name, only *S256* is supported"
@@ -83,3 +126,240 @@ Ref: https://oauth.net/2/pkce
To enable PKCE workflow, set the configuration parameters OAUTH2_CHALLENGE_METHOD to *S256* and OAUTH2_RESPONSE_TYPE to *code*.
Both parameters are mandatory to enable PKCE workflow.
Public vs Confidential OAuth Clients
====================================
OAuth providers support two common client types:
- **Confidential clients** have a client secret and can authenticate to the token endpoint.
- **Public clients** do not have a client secret (or the secret cannot be safely stored).
pgAdmin supports interactive user login for both client types:
- If **OAUTH2_CLIENT_SECRET** is set, pgAdmin treats the provider as a confidential client.
- If **OAUTH2_CLIENT_SECRET** is missing, empty, or set to *None*, pgAdmin treats the provider as a public client and **requires PKCE**.
.. note::
For public clients, pgAdmin uses Authlib's native behavior to perform an **unauthenticated token exchange**
(token endpoint client authentication method: ``none``). This is required for Authorization Code + PKCE
flows where no client secret is available.
Azure Entra ID Workload Identity (AKS) (No Client Secret)
=========================================================
pgAdmin can authenticate to Microsoft Entra ID (Azure AD) **without a client secret** using an
AKS Workload Identity projected service account token (OIDC federated credential).
This is a **confidential client** scenario (server-side app), but client authentication to the token
endpoint is performed using a **JWT client assertion**.
Enable workload identity mode
-----------------------------
Set the following parameters in your provider configuration:
.. code-block:: python
OAUTH2_CONFIG = [{
'OAUTH2_NAME': 'entra-workload-identity',
'OAUTH2_DISPLAY_NAME': 'Microsoft Entra ID',
'OAUTH2_CLIENT_ID': '<Application (client) ID>',
'OAUTH2_CLIENT_SECRET': None, # not required
'OAUTH2_CLIENT_AUTH_METHOD': 'workload_identity',
'OAUTH2_WORKLOAD_IDENTITY_TOKEN_FILE':
'/var/run/secrets/azure/tokens/azure-identity-token',
'OAUTH2_SERVER_METADATA_URL':
'https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration',
'OAUTH2_SCOPE': 'openid email profile',
}]
With this configuration:
- pgAdmin will **not** require **OAUTH2_CLIENT_SECRET**.
- pgAdmin will **not** use PKCE for this provider.
- During the token exchange, pgAdmin will send:
- ``client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer``
- ``client_assertion=<projected service account JWT>``
Azure App Registration setup
----------------------------
In Microsoft Entra ID:
- Create an **App registration** for pgAdmin.
- Configure a **Redirect URI** to ``<http/https>://<pgAdmin Server URL>/oauth2/authorize``.
- In **Certificates & secrets**, you do **not** need to create a client secret for workload identity.
Federated credential (workload identity) configuration
------------------------------------------------------
Add a **Federated credential** to the App registration:
- **Issuer**: your AKS cluster OIDC issuer URL.
- **Subject**: ``system:serviceaccount:<namespace>:<serviceaccount-name>``
- **Audience**: typically ``api://AzureADTokenExchange``
AKS ServiceAccount example
--------------------------
Example ServiceAccount for AKS Workload Identity:
.. code-block:: yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: pgadmin
namespace: pgadmin
annotations:
azure.workload.identity/client-id: "<Application (client) ID>"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: pgadmin
namespace: pgadmin
spec:
template:
metadata:
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: pgadmin
.. note::
The projected token file path can vary by cluster configuration.
In many AKS setups it is provided via the ``AZURE_FEDERATED_TOKEN_FILE`` environment
variable and mounted under ``/var/run/secrets/azure/tokens/``.
OIDC Configuration Examples
============================
Using OIDC with Discovery Metadata (Recommended)
-------------------------------------------------
When using OIDC providers, configure the **OAUTH2_SERVER_METADATA_URL** parameter
to enable automatic discovery and ID token validation:
.. code-block:: python
OAUTH2_CONFIG = [{
'OAUTH2_NAME': 'my-oidc-provider',
'OAUTH2_DISPLAY_NAME': 'My OIDC Provider',
'OAUTH2_CLIENT_ID': 'your-client-id',
'OAUTH2_CLIENT_SECRET': 'your-client-secret',
'OAUTH2_SERVER_METADATA_URL': 'https://provider.example.com/.well-known/openid-configuration',
'OAUTH2_SCOPE': 'openid email profile',
# OAUTH2_USERINFO_ENDPOINT is optional when using OIDC
# Token and authorization URLs are discovered automatically
}]
With this configuration:
- pgAdmin will use the OIDC discovery endpoint to automatically find token and authorization URLs
- User identity will be extracted from ID token claims (sub, email, preferred_username)
- The userinfo endpoint will only be called as a fallback if ID token lacks required claims
- ID token will be automatically validated using the provider's public keys
Using OIDC as a Public Client (No Client Secret) with PKCE
-----------------------------------------------------------
If your OAuth/OIDC application is configured as a **public client** (no client secret), pgAdmin can still perform
interactive user login using Authorization Code + PKCE.
.. code-block:: python
OAUTH2_CONFIG = [{
'OAUTH2_NAME': 'my-oidc-public',
'OAUTH2_DISPLAY_NAME': 'My OIDC Provider (Public Client)',
'OAUTH2_CLIENT_ID': 'your-client-id',
# Public client: omit OAUTH2_CLIENT_SECRET or set it to None/empty.
'OAUTH2_CLIENT_SECRET': None,
'OAUTH2_SERVER_METADATA_URL': 'https://provider.example.com/.well-known/openid-configuration',
'OAUTH2_SCOPE': 'openid email profile',
# PKCE is mandatory for public clients
'OAUTH2_CHALLENGE_METHOD': 'S256',
'OAUTH2_RESPONSE_TYPE': 'code',
}]
With this configuration:
- pgAdmin enforces PKCE (challenge method + response type)
- The token exchange is performed without a client secret
Username Resolution for OIDC
-----------------------------
When **OAUTH2_SERVER_METADATA_URL** is configured (OIDC mode), pgAdmin will
resolve the username in the following order:
1. **OAUTH2_USERNAME_CLAIM** (if configured) - checks ID token first, then userinfo
2. **email** claim from ID token or userinfo endpoint
3. **preferred_username** claim from ID token (standard OIDC claim)
4. **sub** claim from ID token (always present in OIDC, used as last resort)
Example with custom username claim:
.. code-block:: python
OAUTH2_CONFIG = [{
# ... other config ...
'OAUTH2_USERNAME_CLAIM': 'preferred_username',
# pgAdmin will use 'preferred_username' from ID token for the username
}]
Example without custom claim (uses automatic fallback):
.. code-block:: python
OAUTH2_CONFIG = [{
# ... other config ...
# No OAUTH2_USERNAME_CLAIM specified
# pgAdmin will try: email -> preferred_username -> sub
}]
Additional Claims Authorization with OIDC
------------------------------------------
When using **OAUTH2_ADDITIONAL_CLAIMS** with OIDC providers, pgAdmin will:
1. Check the ID token claims first (more secure, no additional network call)
2. Fall back to userinfo endpoint response if needed
Example:
.. code-block:: python
OAUTH2_CONFIG = [{
# ... other config ...
'OAUTH2_ADDITIONAL_CLAIMS': {
'groups': ['admin-group', 'pgadmin-users'],
'roles': ['database-admin']
},
# pgAdmin will check these claims in ID token first,
# then userinfo endpoint if not found
}]
Legacy OAuth2 Configuration (Without OIDC)
-------------------------------------------
For providers that don't support OIDC discovery, configure all endpoints manually:
.. code-block:: python
OAUTH2_CONFIG = [{
'OAUTH2_NAME': 'github',
'OAUTH2_DISPLAY_NAME': 'GitHub',
'OAUTH2_CLIENT_ID': 'your-client-id',
'OAUTH2_CLIENT_SECRET': 'your-client-secret',
'OAUTH2_TOKEN_URL': 'https://github.com/login/oauth/access_token',
'OAUTH2_AUTHORIZATION_URL': 'https://github.com/login/oauth/authorize',
'OAUTH2_API_BASE_URL': 'https://api.github.com/',
'OAUTH2_USERINFO_ENDPOINT': 'user',
'OAUTH2_SCOPE': 'user:email',
# No OAUTH2_SERVER_METADATA_URL - pure OAuth2 mode
}]
In this mode, user identity is retrieved only from the userinfo endpoint.
+132 -7
View File
@@ -27,6 +27,83 @@ The left pane of the *Preferences* tab displays a tree control; each node of
the tree control provides access to options that are related to the node under
which they are displayed.
The AI Node
***********
Use preferences found in the *AI* node of the tree control to configure
AI-powered features and LLM (Large Language Model) providers.
.. image:: images/preferences_ai.png
:alt: Preferences AI section
:align: center
**Note:** AI features must be enabled in the server configuration (``LLM_ENABLED = True``
in ``config.py``) for these preferences to be available.
Use the fields on the *AI* panel to configure your LLM provider:
* Use the *Default Provider* drop-down to select your LLM provider. Options include:
*Anthropic*, *OpenAI*, *Ollama*, or *Docker Model Runner*.
**Anthropic Settings:**
* Use the *API URL* field to set a custom API endpoint URL. Leave empty to use
the default Anthropic API (``https://api.anthropic.com/v1``). Set a custom URL
to use an Anthropic-compatible API provider.
* Use the *API Key File* field to specify the path to a file containing your
Anthropic API key. This path refers to the filesystem where the pgAdmin
server is running (e.g., inside the container if using Docker). The ``~``
prefix is expanded to the home directory of the user running the pgAdmin
server process. The API key may be optional when using a custom API URL
with a provider that does not require authentication.
* Use the *Model* field to select from the available Claude models. Click the
refresh button to fetch the latest available models from your configured
endpoint.
**OpenAI Settings:**
* Use the *API URL* field to set a custom API endpoint URL. Leave empty to use
the default OpenAI API (``https://api.openai.com/v1``). Set a custom URL to
use any OpenAI-compatible API provider (e.g., LiteLLM, LM Studio, EXO).
Include the ``/v1`` path prefix if required by your provider
(e.g., ``http://localhost:1234/v1``).
* Use the *API Key File* field to specify the path to a file containing your
OpenAI API key. This path refers to the filesystem where the pgAdmin
server is running (e.g., inside the container if using Docker). The ``~``
prefix is expanded to the home directory of the user running the pgAdmin
server process. The API key may be optional when using a custom API URL
with a provider that does not require authentication.
* Use the *Model* field to select from the available GPT models. Click the
refresh button to fetch the latest available models from your configured
endpoint.
**Ollama Settings:**
* Use the *API URL* field to specify the Ollama server URL
(default: ``http://localhost:11434``).
* Use the *Model* field to select from the available models or enter a custom
model name (e.g., ``llama2``, ``mistral``). Click the refresh button to fetch
the latest available models from your Ollama server.
**Docker Model Runner Settings:**
* Use the *API URL* field to specify the Docker Model Runner API URL
(default: ``http://localhost:12434``). Available in Docker Desktop 4.40+.
* Use the *Model* field to select from the available models or enter a custom
model name. Click the refresh button to fetch the latest available models
from your Docker Model Runner.
.. note:: You can also use the *OpenAI* provider with a custom API URL for any
OpenAI-compatible endpoint, including Docker Model Runner, LM Studio, EXO,
and other local inference servers. This can be useful when you want to use
a provider that isn't explicitly listed but supports the OpenAI API format.
The Browser Node
****************
@@ -60,6 +137,9 @@ Use the fields on the *Display* panel to specify general display preferences:
all the shared servers from the object explorer. **Note:** This option is visible only when
pgAdmin is running in server mode.
* When the *Show column data type?* switch is turned off, then the data types
of the columns will not be displayed alongside their column names.
* When the *Show empty object collections?* switch is turned off, then all object
collections which are empty will be hidden from browser tree.
@@ -101,7 +181,7 @@ related settings:
:align: center
* Use *Enable object breadcrumbs?* to enable or disable object breadcrumbs
displayed on on object mouse hover.
displayed on object mouse hover.
* Use *Show comment with object breadcrumbs?* to enable or disable the
comment visibility which comes displayed with object breadcrumbs.
@@ -139,11 +219,11 @@ Use field on *Tab settings* panel to specify the tab related properties.
* Use *Debugger tab title placeholder* field to customize the Debugger tab title.
* When the *Dynamic tab size* If set to True, the tabs will take full size as per the title, it will also applicable for already opened tabs
* When the *Dynamic tab size* switch is set to *True*, the tabs will take full size as per the title. This will also apply to already opened tabs.
* Use the *PSQL tool tab title placeholder* field to customize the PSQL tool tab title.
* When the *Open in new browser tab* filed is selected for Query tool, Schema Diff or Debugger, it will
* When the *Open in new browser tab* field is selected for Query tool, Schema Diff or Debugger, it will
open in a new browser tab when invoked.
* Use the *Query tool tab title placeholder* field to customize the query tool tab title.
@@ -172,7 +252,7 @@ Expand the *Dashboards* node to specify your dashboard display preferences.
:alt: Preferences dashboard refresh options
:align: center
Use the fields on the *Refresh rates* panel to specify your refersh rates
Use the fields on the *Refresh rates* panel to specify your refresh rate
preferences for the graphs on the *Dashboard* tab:
* Use the *Average load statistics refresh rate* field to specify the number of
@@ -254,6 +334,12 @@ Use the fields on the *Options* panel to manage ERD preferences.
* Use *Cardinality Notation* to change the cardinality notation format
used to present relationship links.
* Use *Format ERD Project File?* if you want to format the .pgerd ERD
project file. It's a JSON file which will be formatted.
* Use *Image Download Resolution* to change the ERD image download
resolution. Higher values will use higher memory and slower rendering.
* When the *SQL With DROP Table* switch is set to *True*, the SQL
generated by the ERD Tool will add DROP table DDL before each CREATE
table DDL.
@@ -383,11 +469,17 @@ Use the fields on the *File Downloads* panel to manage file downloads related pr
* When the *Automatically open downloaded files?* switch is set to *True*
the downloaded file will automatically open in the system's default
application associated with that file type.
application associated with that file type. **Note:** This option is applicable and
visible only in desktop mode.
* When the *Enable binary data download?* switch is set to *True*,
binary data can be downloaded from the result grid. Default is set to *False*
to prevent excessive memory usage on the server.
* When the *Prompt for the download location?* switch is set to *True*
a prompt will appear after clicking the download button, allowing you
to choose the download location.
to choose the download location. **Note:** This option is applicable and
visible only in desktop mode.
**Note:** File Downloads related settings are applicable and visible only in desktop mode.
@@ -451,7 +543,7 @@ monitored databases:
locations used by PostgreSQL.
**Note:** Use the 'Validate path' button to check the existence of the utility
programs (pg_dump, pg_dumpall, pg_restore and psql) and there respective versions.
programs (pg_dump, pg_dumpall, pg_restore and psql) and their respective versions.
.. image:: images/preferences_paths_help.png
:alt: Preferences binary path help section
@@ -543,6 +635,39 @@ a graphical EXPLAIN.
* When the *Verbose output?* switch is set to *True*, graphical explain details
will include extended information about the query execution plan.
Use the fields on the *Geometry Viewer* panel to configure a custom map tile
provider used as a base layer when viewing geometry data.
* Use the *Custom tile provider URL* field to specify the URL template of a
custom XYZ tile provider, e.g.
``https://myserver.example.com/tiles/{z}/{x}/{y}.png``. The template must
contain the ``{x}``, ``{y}`` and ``{z}`` placeholders, and may contain
``{s}`` for subdomains (a, b, c). Leave the field empty to disable the
custom tile provider.
* Use the *Custom tile provider name* field to specify the display name of
the custom tile provider in the layer selector of the Geometry Viewer.
* Use the *Custom tile provider CRS* field to specify the coordinate
reference system of the custom tile provider. If it is not EPSG:3857
(Web Mercator), the built-in tile layers will be hidden as they cannot be
mixed with other coordinate systems.
* Use the *Custom tile provider attribution* field to specify the
attribution text shown on the map. It may contain HTML links.
* Use the *Custom tile provider max zoom* field to specify the maximum zoom
level of the custom tile provider.
When a custom tile provider is configured, it is selected as the default
base layer of the Geometry Viewer.
An administrator may set a system-wide default for these fields via the
``DEFAULT_GEOMETRY_VIEWER_PROVIDER`` setting in :ref:`config_py`. It applies
to any user who has not saved their own values for the fields above; a
user's own preferences, once saved, always take precedence over the
system-wide default.
.. image:: images/preferences_graph_visualiser.png
:alt: Preferences sqleditor graph visualiser section
:align: center
+4 -1
View File
@@ -36,6 +36,9 @@ Use the fields in the *Definition* tab to define the procedure:
* Use the drop-down listbox next to *Language* to select a language. The default
is *edbspl*.
* Use the drop-down listbox next to *Depends on extensions* to select the extension that this procedure
depends on (for example, edbspl). If set, dropping the extension will automatically drop the
procedure as well.
* Use the fields in the *Arguments* section to define an argument. Click *Add* to set
parameters and values for the argument:
* Use the drop-down listbox next to *Data type* to select a data type.
@@ -132,7 +135,7 @@ procedure. Click *Add* to add each security label selection:
* Specify a security label provider in the *Provider* field. The named provider
must be loaded and must consent to the proposed labeling operation.
* Specify a a security label in the *Security Label* field. The meaning of a
* Specify a security label in the *Security Label* field. The meaning of a
given label is at the discretion of the label provider. PostgreSQL places no
restrictions on whether or how a label provider must interpret security
labels; it merely provides a mechanism for storing them.
+88 -2
View File
@@ -32,8 +32,9 @@ The Query Tool features two panels:
* The upper panel displays the *SQL Editor*. You can use the panel to enter,
edit, or execute a query or a script. It also shows the *History* tab which can be used
to view the queries that have been executed in the session, and a *Scratch Pad*
which can be used to hold text snippets during editing. If the Scratch Pad is
to view the queries that have been executed in the session, a *Scratch Pad*
which can be used to hold text snippets during editing, and an *AI Assistant*
tab for generating SQL from natural language (when AI is configured). If the Scratch Pad is
closed, it can be re-opened (or additional ones opened) by right-clicking in
the SQL Editor and other panels and adding a new panel.
* The lower panel displays the *Data Output* panel. The tabbed panel displays
@@ -201,6 +202,51 @@ can be adjusted in ``config_local.py`` or ``config_system.py`` (see the
`MAX_QUERY_HIST_STORED` value. See the :ref:`Deployment <deployment>` section
for more information.
AI Assistant Panel
******************
The *AI Assistant* tab provides a chat-style interface for generating SQL queries
from natural language descriptions. This feature requires an AI provider to be
configured in *Preferences > AI*. For configuration details, see the
:ref:`preferences` documentation.
.. image:: images/query_ai_assistant.png
:alt: Query tool AI Assistant panel
:align: center
To use the AI Assistant:
1. Click on the *AI Assistant* tab in the upper panel, or use the *AI Assistant*
toolbar button.
2. Type a description of the SQL query you need in natural language.
3. Press Enter or click the send button to submit your request.
4. The AI will analyze your database schema and generate appropriate SQL.
The AI Assistant displays conversations with your messages and AI responses. When
the AI generates SQL, it appears in a syntax-highlighted code block with action
buttons:
* **Insert** - Insert the SQL at the current cursor position in the SQL Editor.
* **Replace** - Replace all content in the SQL Editor with the generated SQL.
* **Copy** - Copy the SQL to the clipboard.
The AI Assistant maintains conversation context, allowing you to refine queries
iteratively. For example, you can ask for a query and then follow up with
"also add a filter for active users" to modify the previous result.
**Tips for effective use:**
* Be specific about table and column names if you know them.
* Describe the desired output format (e.g., "show count by category").
* For complex queries, break down requirements step by step.
* Use the *Clear* button to start a fresh conversation.
**Note:** The AI Assistant uses database schema inspection tools to understand
your database structure. It supports SELECT, INSERT, UPDATE, DELETE, and DDL
statements. All generated queries should be reviewed before execution. See
:ref:`ai_data_handling` for details of the information that is transmitted to
your configured LLM provider.
The Data Output Panel
*********************
@@ -275,6 +321,8 @@ the drop down on the right side of *Explain Analyze* button in the toolbar.
Please note that pgAdmin generates the *Explain [Analyze]* plan in JSON format.
**Note**: If multiple queries are present and none is selected, *Explain [Analyze]* runs the query located at the cursor position.
On successful generation of *Explain* plan, it will create three tabs/panels
under the Explain panel.
@@ -333,6 +381,44 @@ If planner mis-estimated number of rows (actual vs planned) by
:alt: Query tool explain plan statistics
:align: center
* AI Insights
The *AI Insights* tab provides AI-powered analysis of query execution plans,
identifying performance bottlenecks and suggesting optimizations. This tab is
only available when an AI provider is configured in *Preferences > AI*.
.. image:: images/query_explain_ai_insights.png
:alt: Query tool explain plan AI insights
:align: center
When you switch to the AI Insights tab, the AI analyzes the execution plan and
provides:
**Performance Bottlenecks** - Issues identified in the query plan, such as:
* Sequential scans on large tables that could benefit from indexes
* Significant differences between estimated and actual row counts
* Expensive sort or hash operations
* Nested loops with high iteration counts
**Recommendations** - Concrete suggestions to improve query performance:
* Index creation statements with appropriate columns
* ANALYZE commands to update table statistics
* Configuration parameter adjustments
* Query restructuring suggestions
Each recommendation that includes SQL (such as CREATE INDEX statements) has
action buttons to *Copy* the SQL to the clipboard or *Insert* it into the
Query Editor.
Click the *Regenerate* button to request a fresh analysis of the current plan.
**Note:** AI analysis is generated on-demand when you first click the AI Insights
tab or when a new explain plan is generated while the tab is active. The analysis
provides guidance but all suggested changes should be carefully evaluated before
applying to production databases.
Messages Panel
**************
+3 -3
View File
@@ -85,9 +85,7 @@ Query Editing Options
| +---------------------------------------------------------------------------------------------------+-----------------------+
| | Select *Find* to provide a search target, and search the SQL Editor contents. | Cmd/Ctrl + F |
| +---------------------------------------------------------------------------------------------------+-----------------------+
| | Select *Replace* to locate and replace (with prompting) individual occurrences of the target. | Option + Cmd + F (MAC)|
| | | Ctrl + Shift + F |
| | | (Others) |
| | Select *Replace* to locate and replace (with prompting) individual occurrences of the target. | Cmd/Ctrl + R |
| +---------------------------------------------------------------------------------------------------+-----------------------+
| | Select *Go to Line/Column* to go to specified line number and column position | Cmd/Ctrl + L |
| +---------------------------------------------------------------------------------------------------+-----------------------+
@@ -241,6 +239,8 @@ Pagination Options
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Edit Range* | Click to open the from and to rows range inputs to allow setting them. | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Show Entire Range* | Click to get all the rows and set the from and to rows range | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Page No* | Enter the page no you want to jump to out of total shown next to this input | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *First Page* | Click to go to the first page. | |
+11
View File
@@ -12,6 +12,17 @@ notes for it.
:maxdepth: 1
release_notes_9_18
release_notes_9_17
release_notes_9_16
release_notes_9_15
release_notes_9_14
release_notes_9_13
release_notes_9_12
release_notes_9_11
release_notes_9_10
release_notes_9_9
release_notes_9_8
release_notes_9_7
release_notes_9_6
release_notes_9_5
+47
View File
@@ -0,0 +1,47 @@
************
Version 9.10
************
Release date: 2025-11-13
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.9.
Supported Database Servers
**************************
**PostgreSQL**: 13, 14, 15, 16, 17 and 18
**EDB Advanced Server**: 13, 14, 15, 16, 17 and 18
Bundled PostgreSQL Utilities
****************************
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 18.0
New features
************
| `Issue #4306 <https://github.com/pgadmin-org/pgadmin4/issues/4306>`_ - Added the ability to search for tables and automatically bring them into view in the ERD tool.
| `Issue #6391 <https://github.com/pgadmin-org/pgadmin4/issues/6391>`_ - Add support of DEPENDS/NO DEPENDS ON EXTENSION for PROCEDURE.
| `Issue #6698 <https://github.com/pgadmin-org/pgadmin4/issues/6698>`_ - Add support for setting image download resolution in the ERD tool.
| `Issue #7885 <https://github.com/pgadmin-org/pgadmin4/issues/7885>`_ - Add support for displaying detailed Citus query plans instead of 'Custom Scan' placeholder.
| `Issue #8912 <https://github.com/pgadmin-org/pgadmin4/issues/8912>`_ - Add support for formatting .pgerd ERD project file.
Housekeeping
************
| `Issue #8676 <https://github.com/pgadmin-org/pgadmin4/issues/8676>`_ - Migrate pgAdmin UI to use React 19.
Bug fixes
*********
| `Issue #8504 <https://github.com/pgadmin-org/pgadmin4/issues/8504>`_ - Fixed an issue where data output column resize is not sticking in Safari.
| `Issue #9117 <https://github.com/pgadmin-org/pgadmin4/issues/9117>`_ - Fixed an issue where Schema Diff does not ignore Tablespace for indexes.
| `Issue #9132 <https://github.com/pgadmin-org/pgadmin4/issues/9132>`_ - Fixed an issue where the 2FA window redirected to the login page after session expiration.
| `Issue #9233 <https://github.com/pgadmin-org/pgadmin4/issues/9233>`_ - Fixed an issue where the Select All option on the columns tab of import/export data was not working in languages other than English.
| `Issue #9240 <https://github.com/pgadmin-org/pgadmin4/issues/9240>`_ - Fixed an issue where the Debian build process failed with a "Sphinx module not found" error when using a Python virtual environment.
| `Issue #9281 <https://github.com/pgadmin-org/pgadmin4/issues/9281>`_ - Fixed an issue where the last used storage directory was reset to blank, leading to access denied errors during backup or restore operations.
| `Issue #9304 <https://github.com/pgadmin-org/pgadmin4/issues/9304>`_ - Fixed an issue that prevented assigning multiple users to an RLS policy.
| `Issue #9320 <https://github.com/pgadmin-org/pgadmin4/issues/9320>`_ - Fixed remote code execution vulnerability when restoring PLAIN-format SQL dumps in server mode (CVE-2025-12762).
| `Issue #9323 <https://github.com/pgadmin-org/pgadmin4/issues/9323>`_ - Fixed Command injection vulnerability allowing arbitrary command execution on Windows (CVE-2025-12763).
| `Issue #9324 <https://github.com/pgadmin-org/pgadmin4/issues/9324>`_ - Fixed LDAP authentication flow vulnerable to TLS certificate verification bypass (CVE-2025-12765).
| `Issue #9325 <https://github.com/pgadmin-org/pgadmin4/issues/9325>`_ - Fixed LDAP injection vulnerability in LDAP authentication flow (CVE-2025-12764).
+45
View File
@@ -0,0 +1,45 @@
************
Version 9.11
************
Release date: 2025-12-11
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.10.
Supported Database Servers
**************************
**PostgreSQL**: 13, 14, 15, 16, 17 and 18
**EDB Advanced Server**: 13, 14, 15, 16, 17 and 18
Bundled PostgreSQL Utilities
****************************
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 18.0
New features
************
| `Issue #6388 <https://github.com/pgadmin-org/pgadmin4/issues/6388>`_ - Add support of DEPENDS/NO DEPENDS ON EXTENSION for INDEX.
| `Issue #6390 <https://github.com/pgadmin-org/pgadmin4/issues/6390>`_ - Add support of DEPENDS/NO DEPENDS ON EXTENSION for MATERIALIZED VIEW.
| `Issue #8968 <https://github.com/pgadmin-org/pgadmin4/issues/8968>`_ - Add support for showing the column data type beside column name in the object explorer.
| `Issue #9263 <https://github.com/pgadmin-org/pgadmin4/issues/9263>`_ - Added Ubuntu 25.10 and Fedora 43
| `Issue #9345 <https://github.com/pgadmin-org/pgadmin4/issues/9345>`_ - Add conditional TLS support for the Ingress in the Helm chart.
Housekeeping
************
Bug fixes
*********
| `Issue #9036 <https://github.com/pgadmin-org/pgadmin4/issues/9036>`_ - Fixed an issue on type casting of default string function/procedure arguments in debugger tool.
| `Issue #9155 <https://github.com/pgadmin-org/pgadmin4/issues/9155>`_ - Fix pkg_resources deprecation warning by migrating Docker base image to python:3-alpine from alpine:latest.
| `Issue #9297 <https://github.com/pgadmin-org/pgadmin4/issues/9297>`_ - Fixed an issue where EXPLAIN should run on query under cursor if no text is selected.
| `Issue #9351 <https://github.com/pgadmin-org/pgadmin4/issues/9351>`_ - Fixed an issue where opening file in Query Tool does not retain file name in tab.
| `Issue #9354 <https://github.com/pgadmin-org/pgadmin4/issues/9354>`_ - Fixed an issue where connection is failing via Query Tool/PSQL Tool workspaces.
| `Issue #9368 <https://github.com/pgadmin-org/pgadmin4/issues/9368>`_ - Plain SQL restore runs with '\restrict' option to prevent harmful psql meta-commands (CVE-2025-13780).
| `Issue #9372 <https://github.com/pgadmin-org/pgadmin4/issues/9372>`_ - Fixed an issue where copying highlighted text in the query tool data output cell editor would copy the complete string.
| `Issue #9373 <https://github.com/pgadmin-org/pgadmin4/issues/9373>`_ - Fixed an issue where copying a single cell should not add quoting.
| `Issue #9393 <https://github.com/pgadmin-org/pgadmin4/issues/9393>`_ - Fix the Helm chart server definition and change the app version.
| `Issue #9399 <https://github.com/pgadmin-org/pgadmin4/issues/9399>`_ - Specify the correct hostname placeholder for the Password Exec command.
| `Issue #9408 <https://github.com/pgadmin-org/pgadmin4/issues/9408>`_ - Ensure the proper handling of extra volume mount configurations in the Helm deployment template by correcting the configuration value references.
+60
View File
@@ -0,0 +1,60 @@
************
Version 9.12
************
Release date: 2026-02-05
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.11.
.. warning:: Starting with this release, pgAdmin Windows installers are signed
with a new code signing certificate. When installing or running pgAdmin on
Windows, you should verify that the digital signature shows the certificate
name as **"Open Source Developer, David John Page"**. This certificate will
be used for this and future releases.
Supported Database Servers
**************************
**PostgreSQL**: 13, 14, 15, 16, 17 and 18
**EDB Advanced Server**: 13, 14, 15, 16, 17 and 18
Bundled PostgreSQL Utilities
****************************
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 18.0
New features
************
| `Issue #6451 <https://github.com/pgadmin-org/pgadmin4/issues/6451>`_ - Add new options like INHERIT and SET to the Role's membership tab.
| `Issue #8890 <https://github.com/pgadmin-org/pgadmin4/issues/8890>`_ - Add a new button in the query tool data output toolbar to get entire range of data.
| `Issue #9292 <https://github.com/pgadmin-org/pgadmin4/issues/9292>`_ - Enhance OAUTH2 and OIDC authentication support with improved claims handling and configuration options.
Housekeeping
************
Bug fixes
*********
| `Issue #8916 <https://github.com/pgadmin-org/pgadmin4/issues/8916>`_ - Warn user of unsaved data output edits before page navigation.
| `Issue #8987 <https://github.com/pgadmin-org/pgadmin4/issues/8987>`_ - Fix Query Tool state restoration for new connections and queries.
| `Issue #9074 <https://github.com/pgadmin-org/pgadmin4/issues/9074>`_ - Fix pg_restore logs to distinguish UI sync issues from actual failures.
| `Issue #9110 <https://github.com/pgadmin-org/pgadmin4/issues/9110>`_ - Optimize checkbox selection logic in backup dialog objects tree.
| `Issue #9196 <https://github.com/pgadmin-org/pgadmin4/issues/9196>`_ - Fixed an issue where double click to open a file in the file manager is not working.
| `Issue #9223 <https://github.com/pgadmin-org/pgadmin4/issues/9223>`_ - Upgrade ID column in the database table to BigInteger to support large OID values.
| `Issue #9235 <https://github.com/pgadmin-org/pgadmin4/issues/9235>`_ - Fixed an issue where "View/Edit Data" shortcut opened "First 100 rows" instead of "All Rows".
| `Issue #9258 <https://github.com/pgadmin-org/pgadmin4/issues/9258>`_ - Ensure saved shared server passwords are re-encrypted on password change.
| `Issue #9260 <https://github.com/pgadmin-org/pgadmin4/issues/9260>`_ - Fixed an issue where data filter dialog removes newline character when sending SQL to the query tool.
| `Issue #9285 <https://github.com/pgadmin-org/pgadmin4/issues/9285>`_ - Fixed an issue where the dashboard freezes on initial render when there is a high number of locks.
| `Issue #9293 <https://github.com/pgadmin-org/pgadmin4/issues/9293>`_ - Fixed the SSL certificate issue while checking for the upgrade.
| `Issue #9332 <https://github.com/pgadmin-org/pgadmin4/issues/9332>`_ - Fixed a sorting issue in the system stats memory usage table.
| `Issue #9350 <https://github.com/pgadmin-org/pgadmin4/issues/9350>`_ - Disable Parameters and Membership fields when object is not new for Login and group roles.
| `Issue #9380 <https://github.com/pgadmin-org/pgadmin4/issues/9380>`_ - Fixed an issue where the Query History panel would auto-scroll to the top and did not preserve the scroll bar position for the selected entry.
| `Issue #9402 <https://github.com/pgadmin-org/pgadmin4/issues/9402>`_ - Fixed an issue where pgAdmin4 app on macOS cannot auto-update while running on a read-only volume even if present in the Applications folder.
| `Issue #9500 <https://github.com/pgadmin-org/pgadmin4/issues/9500>`_ - Fixed an issue where connection parameters were using localized values instead of literal values, causing connection failures.
| `Issue #9518 <https://github.com/pgadmin-org/pgadmin4/issues/9518>`_ - Mask the secret key for restrict option in the process watcher when restoring plain SQL file (CVE-2026-1707).
| `Issue #9522 <https://github.com/pgadmin-org/pgadmin4/issues/9522>`_ - Ensure the container deployment supports boolean values in yaml format.
| `Issue #9552 <https://github.com/pgadmin-org/pgadmin4/issues/9552>`_ - Ensure that the tooltip for the password cell is not visible.
| `Issue #9553 <https://github.com/pgadmin-org/pgadmin4/issues/9553>`_ - Fix pgAdmin fails when performing Backup/Restore on a PostgreSQL connection defined exclusively via pg_service.conf.
| `Issue #9567 <https://github.com/pgadmin-org/pgadmin4/issues/9567>`_ - Update menu bar documentation.
+44
View File
@@ -0,0 +1,44 @@
************
Version 9.13
************
Release date: 2026-03-05
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.12.
Supported Database Servers
**************************
**PostgreSQL**: 13, 14, 15, 16, 17 and 18
**EDB Advanced Server**: 13, 14, 15, 16, 17 and 18
Bundled PostgreSQL Utilities
****************************
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 18.0
New features
************
| `Issue #229 <https://github.com/pgadmin-org/pgadmin4/issues/229>`_ - Allow users to customize "OF TYPE" columns during table creation.
| `Issue #5578 <https://github.com/pgadmin-org/pgadmin4/issues/5578>`_ - No FK are shown in diagram created from existing tables in the ERD Tool.
| `Issue #6386 <https://github.com/pgadmin-org/pgadmin4/issues/6386>`_ - Add support for 'ONLY' in Index creation dialog.
| `Issue #8198 <https://github.com/pgadmin-org/pgadmin4/issues/8198>`_ - Allow "drag-n-drop" for only user chosen tables, and show relations between them.
| `Issue #9229 <https://github.com/pgadmin-org/pgadmin4/issues/9229>`_ - Load predefined users from a JSON file through command line.
| `Issue #9641 <https://github.com/pgadmin-org/pgadmin4/issues/9641>`_ - Core LLM integration infrastructure, AI reports for security, schema, and performance, AI chat for the Query Tool, and AI Insights for EXPLAIN.
Housekeeping
************
Bug fixes
*********
| `Issue #7578 <https://github.com/pgadmin-org/pgadmin4/issues/7578>`_ - Fixed an issue where the 'Quote strings only' configuration was ignored when downloading the result set.
| `Issue #8988 <https://github.com/pgadmin-org/pgadmin4/issues/8988>`_ - Fixed an issue where tools settings changed by the users were not restored on application relaunch.
| `Issue #9258 <https://github.com/pgadmin-org/pgadmin4/issues/9258>`_ - Fixed an issue where modifying a shared server incorrectly updated the original server details.
| `Issue #9484 <https://github.com/pgadmin-org/pgadmin4/issues/9484>`_ - Fixed an issue where a long name in ERD table node was not breaking into multiple lines.
| `Issue #9486 <https://github.com/pgadmin-org/pgadmin4/issues/9486>`_ - Fixed an issue where column comments were not displayed in the SQL tab for materialised views.
| `Issue #9572 <https://github.com/pgadmin-org/pgadmin4/issues/9572>`_ - Fix an issue where deployment of helm chart crashing with operation not permitted.
| `Issue #9583 <https://github.com/pgadmin-org/pgadmin4/issues/9583>`_ - Fix translation compilation.
| `Issue #9649 <https://github.com/pgadmin-org/pgadmin4/issues/9649>`_ - Fix broken checkbox selection in backup dialog objects tree.
| `Issue #9651 <https://github.com/pgadmin-org/pgadmin4/issues/9651>`_ - Fixed an issue in file dialog where rename was not working.
+52
View File
@@ -0,0 +1,52 @@
************
Version 9.14
************
Release date: 2026-04-02
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.13.
Supported Database Servers
**************************
**PostgreSQL**: 13, 14, 15, 16, 17 and 18
**EDB Advanced Server**: 13, 14, 15, 16, 17 and 18
Bundled PostgreSQL Utilities
****************************
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 18.2
New features
************
| `Issue #4011 <https://github.com/pgadmin-org/pgadmin4/issues/4011>`_ - Added support to download binary data from result grid.
| `Issue #9703 <https://github.com/pgadmin-org/pgadmin4/issues/9703>`_ - Added support for custom LLM provider URLs for OpenAI and Anthropic, allowing use of OpenAI-compatible providers such as LM Studio, EXO, and LiteLLM.
| `Issue #9709 <https://github.com/pgadmin-org/pgadmin4/issues/9709>`_ - Fixed an issue where AI features (AI Assistant tab, AI Reports menus, and AI Preferences) were visible in the UI even when LLM_ENABLED is set to False.
| `Issue #9738 <https://github.com/pgadmin-org/pgadmin4/issues/9738>`_ - Allow copying of text from the AI Assistant chat panel.
Housekeeping
************
Bug fixes
*********
| `Issue #8992 <https://github.com/pgadmin-org/pgadmin4/issues/8992>`_ - Fixed an issue where selecting all in the Query Tool's Messages tab would select the entire page content.
| `Issue #9279 <https://github.com/pgadmin-org/pgadmin4/issues/9279>`_ - Fixed an issue where OAuth2 authentication fails with 'object has no attribute' if OAUTH2_AUTO_CREATE_USER is False.
| `Issue #9392 <https://github.com/pgadmin-org/pgadmin4/issues/9392>`_ - Ensure that the Geometry Viewer refreshes when re-running queries or switching geometry columns, preventing stale data from being displayed.
| `Issue #9457 <https://github.com/pgadmin-org/pgadmin4/issues/9457>`_ - Fixed Process Watcher garbled text on Windows with non-UTF-8 locales.
| `Issue #9570 <https://github.com/pgadmin-org/pgadmin4/issues/9570>`_ - Fixed an issue where ALT+F5 for executing a query in the Query Tool shows a crosshair cursor icon for rectangular selection.
| `Issue #9648 <https://github.com/pgadmin-org/pgadmin4/issues/9648>`_ - Fixed an issue where the default fillfactor value for B-tree indexes was incorrect.
| `Issue #9694 <https://github.com/pgadmin-org/pgadmin4/issues/9694>`_ - Fixed an issue where AI Reports are grayed out after setting an API key by auto-selecting the default provider.
| `Issue #9696 <https://github.com/pgadmin-org/pgadmin4/issues/9696>`_ - Fixed an issue where AI Assistant does not notify that No API Key or Provider is Set.
| `Issue #9702 <https://github.com/pgadmin-org/pgadmin4/issues/9702>`_ - Fixed misleading AI activity messages that could be mistaken for actual database operations.
| `Issue #9719 <https://github.com/pgadmin-org/pgadmin4/issues/9719>`_ - Fixed an issue where AI Reports fail with OpenAI models that do not support the temperature parameter.
| `Issue #9721 <https://github.com/pgadmin-org/pgadmin4/issues/9721>`_ - Fixed an issue where permissions page is not completely accessible on full scroll.
| `Issue #9729 <https://github.com/pgadmin-org/pgadmin4/issues/9729>`_ - Fixed an issue where some LLM models would not use database tools in the AI assistant, instead returning text descriptions of tool calls.
| `Issue #9732 <https://github.com/pgadmin-org/pgadmin4/issues/9732>`_ - Improve the AI Assistant user prompt to be more descriptive of the actual functionality.
| `Issue #9734 <https://github.com/pgadmin-org/pgadmin4/issues/9734>`_ - Fixed an issue where LLM responses are not streamed or rendered properly in the AI Assistant.
| `Issue #9736 <https://github.com/pgadmin-org/pgadmin4/issues/9736>`_ - Fix an issue where the AI Assistant was not retaining conversation context between messages, with chat history compaction to manage token budgets.
| `Issue #9740 <https://github.com/pgadmin-org/pgadmin4/issues/9740>`_ - Fixed an issue where the AI Assistant input textbox sometimes swallows the first character of input.
| `Issue #9758 <https://github.com/pgadmin-org/pgadmin4/issues/9758>`_ - Clarify where the LLM API key files should be.
| `Issue #9789 <https://github.com/pgadmin-org/pgadmin4/issues/9789>`_ - Fixed an issue where the Query tool kept prompting for a password when using a shared server.
| `Issue #9795 <https://github.com/pgadmin-org/pgadmin4/issues/9795>`_ - Support /v1/responses for OpenAI models.
+113
View File
@@ -0,0 +1,113 @@
************
Version 9.15
************
Release date: 2026-05-11
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.14.
Supported Database Servers
**************************
**PostgreSQL**: 13, 14, 15, 16, 17 and 18
**EDB Advanced Server**: 13, 14, 15, 16, 17 and 18
Bundled PostgreSQL Utilities
****************************
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 18.2
New features
************
| `Issue #9657 <https://github.com/pgadmin-org/pgadmin4/issues/9657>`_ - Allow the container image to run as a non-default user via the PUID and PGID environment variables.
Housekeeping
************
| `Issue #9764 <https://github.com/pgadmin-org/pgadmin4/issues/9764>`_ - Update the Swedish translation.
| `Issue #9827 <https://github.com/pgadmin-org/pgadmin4/issues/9827>`_ - Bump Python and JavaScript dependencies.
| `Issue #9832 <https://github.com/pgadmin-org/pgadmin4/issues/9832>`_ - Fix the Czech translation for 'Refresh'.
| `Issue #9834 <https://github.com/pgadmin-org/pgadmin4/issues/9834>`_ - Bump runtime dependencies and upgrade ESLint to v10.
| `Issue #9839 <https://github.com/pgadmin-org/pgadmin4/issues/9839>`_ - Update the Russian translation.
| `Issue #9870 <https://github.com/pgadmin-org/pgadmin4/issues/9870>`_ - Bump runtime and development dependencies.
| `Issue #9873 <https://github.com/pgadmin-org/pgadmin4/issues/9873>`_ - Use an ``<OWNER>`` placeholder in resql tests instead of a hardcoded 'postgres' role to support non-default superuser names.
| `Issue #9893 <https://github.com/pgadmin-org/pgadmin4/issues/9893>`_ - Update the Spanish translation.
| `Issue #9906 <https://github.com/pgadmin-org/pgadmin4/issues/9906>`_ - Update the Italian translation.
Bug fixes
*********
| `Issue #9656 <https://github.com/pgadmin-org/pgadmin4/issues/9656>`_ - Use absolute paths for ``a2enmod`` and ``a2enconf`` in the Debian setup script so it works when ``/usr/sbin`` is not on PATH.
| `Issue #9830 <https://github.com/pgadmin-org/pgadmin4/issues/9830>`_ - Fix cross-user data access and shared-server privilege escalation in server mode (CVE-2026-7813). Also applies the ``@with_object_filters`` access-control decorator to ``ServerNode.list``.
| `Issue #9835 <https://github.com/pgadmin-org/pgadmin4/issues/9835>`_ - Tighten Shared Server feature parity, owner-only field handling, and write guards as a follow-up to the data-isolation hardening.
| `Issue #9865 <https://github.com/pgadmin-org/pgadmin4/issues/9865>`_ - Fix stored cross-site scripting (XSS) via crafted PostgreSQL object names rendered in the Browser Tree and Explain Visualizer (CVE-2026-7814). Reported by Fahar Abbas.
| `Issue #9898 <https://github.com/pgadmin-org/pgadmin4/issues/9898>`_ - Fix SQL injection in Maintenance tool option values (CVE-2026-7815). Reported by j3seer.
| `Issue #9899 <https://github.com/pgadmin-org/pgadmin4/issues/9899>`_ - Fix OS command injection in Import/Export query export (CVE-2026-7816). Reported by Chung Kim (chungkn), OneMount Group.
| `Issue #9900 <https://github.com/pgadmin-org/pgadmin4/issues/9900>`_ - Fix local-file inclusion and server-side request forgery in LLM API configuration endpoints (CVE-2026-7817). Reported by j3seer.
| `Issue #9901 <https://github.com/pgadmin-org/pgadmin4/issues/9901>`_ - Fix unsafe deserialization in the session manager that could lead to remote code execution (CVE-2026-7818). Also encrypts session files at rest using Fernet, restricts session-file permissions to 0o600, switches the session-digest default from SHA-1 to SHA-256, drops several non-roundtrippable live objects from the session (``AuthSourceManager`` and the Azure, RDS, Google Cloud, and BigAnimal cloud-provider instances), tightens DATA_DIR file and directory permissions at creation, creates ``pgadmin4.log`` with mode 0o600, hardens ``EnhancedRotatingFileHandler._open`` against rotation failures, and bounds the ``user_info_server`` prompt retry loop so a non-interactive caller cannot spin forever. Reported by Fernando Bortotti.
| `Issue #9902 <https://github.com/pgadmin-org/pgadmin4/issues/9902>`_ - Fix symlink-based path traversal in the file manager (CVE-2026-7819). Reported by Fernando Bortotti.
| `Issue #9904 <https://github.com/pgadmin-org/pgadmin4/issues/9904>`_ - Fix account-lockout bypass on Flask-Security's default ``/login`` view by overriding ``User.is_active`` and ``User.is_locked()`` so the ``locked`` field is honored on every authentication path (CVE-2026-7820). Reported by Fernando Bortotti.
Additional changes (no associated issue)
****************************************
The commits below did not have a dedicated GitHub issue. They are listed here for transparency.
Bug fixes
---------
| ``1518b0828`` - Restore the SERVER_MODE python-test path and fix two endpoint regressions surfaced by it.
| ``d57acce35`` - Harden validation, preference, and connection-params paths against pre-existing edge cases.
Test-suite stability
--------------------
| ``a11d289bd`` - Harden ``click_modal`` backdrop wait and ``open_query_tool`` stale-element retry in feature tests.
| ``a50a553b0`` - Feature tests use ``sys.executable``; sync ``yarn.lock`` to ``package.json``.
| ``0fad04de8`` - PSQL socket tests use the authenticated tester; the role-dependencies test skips cleanly on auth failure.
| ``1f7194924`` - Harden six regression tests against environmental drift.
| ``dc61039e9`` - Quote the username in the views/mview test helper for dotted local roles.
| ``9b29bc203`` - Quote the username in the types/compound-triggers test helpers for dotted local roles.
| ``504775de8`` - Quote the username in the user-mappings test helper for dotted local roles.
| ``208541cc4`` - ``ImportExportServersTestCase`` uses ``sys.executable`` instead of a bare ``python`` and surfaces subprocess errors instead of swallowing them as a misleading JSON-parse failure.
Refactoring
-----------
| ``6f4f28def`` - Factor the WTForms-error-to-JSON conversion into a helper and drop a dead import.
Documentation
-------------
| ``9923eefca`` - Clarify in ``login.rst`` and ``ldap.rst`` that ``MAX_LOGIN_ATTEMPTS`` applies only to the ``INTERNAL`` authentication source. Operators using LDAP, OAuth2, Kerberos, or Webserver auth should rely on the upstream identity provider's lockout policy and reverse-proxy request rate-limiting.
Dependencies
------------
Non-breaking ``dependabot`` updates aggregated for v9.15.
Python:
| ``boto3`` 1.42 -> 1.43 (``python_version > '3.9'``; Python 3.9 stays on 1.42)
| ``cryptography`` 46.0 -> 47.0
| ``psycopg`` 3.3.3 -> 3.3.4 (``python_version >= '3.10'``)
| ``pycodestyle`` >=2.5.0 -> >=2.14.0
| ``requests`` >=2.21.0 -> >=2.33.1
| ``safety`` >=1.9.0 -> >=3.7.0
| ``testtools`` 2.8.7 -> 2.9.1
| ``typer`` 0.24 -> 0.25 (``python > '3.9'``)
JavaScript (``web/``):
| ``@tanstack/react-query`` 5.90 -> 5.100.5
| ``axios`` 1.15.2 -> 1.16.0
| ``moment-timezone`` 0.6.0 -> 0.6.2
| ``postcss`` 8.5.6 -> 8.5.12
JavaScript (``runtime/``):
| ``axios`` 1.15.2 -> 1.16.0
| ``electron`` 41.3.0 -> 41.5.0
| ``eslint`` 10.2.1 -> 10.3.0
| ``globals`` 17.5.0 -> 17.6.0
+133
View File
@@ -0,0 +1,133 @@
************
Version 9.16
************
Release date: 2026-06-18
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.15.
.. warning:: Starting with this release, when a server connection is configured
with both an external password-exec command and a passfile, pgAdmin now
uses the passfile and ignores the password-exec command. Previously the
password-exec command took precedence. A warning is written to the log when
the password-exec command is ignored in favour of the passfile.
Supported Database Servers
**************************
**PostgreSQL**: 14, 15, 16, 17 and 18
**EDB Advanced Server**: 14, 15, 16, 17 and 18
Bundled PostgreSQL Utilities
****************************
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 18.4
New features
************
| `Issue #2431 <https://github.com/pgadmin-org/pgadmin4/issues/2431>`_ - Added an option to colourize panel and tab headers based on the connected server's colour, making it easier to tell which server a tab is connected to at a glance.
| `Issue #7641 <https://github.com/pgadmin-org/pgadmin4/issues/7641>`_ - Allow the OAuth2 login button icon to use any Font Awesome style (e.g. ``fas fa-key``), not only brand icons.
| `Issue #9301 <https://github.com/pgadmin-org/pgadmin4/issues/9301>`_ - Added a "Back to login" link to the Forgot Password and Reset Password pages.
| `Issue #9626 <https://github.com/pgadmin-org/pgadmin4/issues/9626>`_ - Add support for the TOAST tuple target storage parameter in the Materialized View dialog.
| `Issue #9646 <https://github.com/pgadmin-org/pgadmin4/issues/9646>`_ - Make the init container security context in the Helm chart configurable via containerSecurityContext, consistent with the main container.
| `Issue #9699 <https://github.com/pgadmin-org/pgadmin4/issues/9699>`_ - Add support for closing a tab with a middle-click on its title.
Housekeeping
************
| `Issue #9817 <https://github.com/pgadmin-org/pgadmin4/issues/9817>`_ - Correct the macOS Replace keyboard shortcut documented in the Query Tool toolbar reference.
| `Issue #9866 <https://github.com/pgadmin-org/pgadmin4/issues/9866>`_ - Fix a typo in the PGPASS_FILE environment-variable documentation and clarify its behaviour.
| `Issue #9917 <https://github.com/pgadmin-org/pgadmin4/issues/9917>`_ - Update the Italian translation.
| `Issue #9959 <https://github.com/pgadmin-org/pgadmin4/issues/9959>`_ - Bump Electron in the desktop runtime from 41.5.0 to 42.1.0 and pin the packaged Electron version.
| `Issue #9981 <https://github.com/pgadmin-org/pgadmin4/issues/9981>`_ - Clarify the SSH tunnel "Prompt for identity file password?" switch label and help text to indicate it applies only to identity-file authentication.
| `Issue #10014 <https://github.com/pgadmin-org/pgadmin4/issues/10014>`_ - Document the Kubernetes init-container tag-mismatch and RollingUpdate hazards on shared data volumes.
| `Issue #10018 <https://github.com/pgadmin-org/pgadmin4/issues/10018>`_ - Remove the EDB BigAnimal cloud deployment support.
| `Issue #10023 <https://github.com/pgadmin-org/pgadmin4/issues/10023>`_ - Bump JavaScript and Python third-party dependencies, including cryptography, axios, shell-quote, and assorted dependabot lockfile updates.
| `Issue #10049 <https://github.com/pgadmin-org/pgadmin4/issues/10049>`_ - Bump vulnerable transitive dependencies (tar, flatted) to clear security advisories.
| `Issue #10050 <https://github.com/pgadmin-org/pgadmin4/issues/10050>`_ - Rebase the version-specific SQL templates so the default targets PostgreSQL 14, the oldest supported server version, dropping the obsolete sub-14 template buckets.
| `Issue #10063 <https://github.com/pgadmin-org/pgadmin4/issues/10063>`_ - Strip the foreign-architecture slice from the macOS bundle so single-arch builds no longer ship the universal2 Python framework's unused arm64/x86_64 code.
| `Issue #10094 <https://github.com/pgadmin-org/pgadmin4/issues/10094>`_ - Update the Italian translation for v9.16.
Bug fixes
*********
| `Issue #6308 <https://github.com/pgadmin-org/pgadmin4/issues/6308>`_ - Fix the infinite loading spinner after an idle database connection is silently dropped, by detecting stale connections and offering a reconnect dialog.
| `Issue #7346 <https://github.com/pgadmin-org/pgadmin4/issues/7346>`_ - Fixed an issue where preferences set via the CLI (setup.py set-prefs) were not validated, so invalid values were stored silently; CLI preference values are now validated against the preference type and rejected (and reported) if invalid.
| `Issue #7596 <https://github.com/pgadmin-org/pgadmin4/issues/7596>`_ - Fix the Query Tool turning into a blank white screen when the runtime has a malformed default locale, by guarding the Query History date/time formatting against the resulting RangeError.
| `Issue #8318 <https://github.com/pgadmin-org/pgadmin4/issues/8318>`_ - Fixed an error ("i.default.find(...) is undefined") that prevented deleting a table or relationship link in the ERD tool when a foreign key referenced a column that had been renamed.
| `Issue #9060 <https://github.com/pgadmin-org/pgadmin4/issues/9060>`_ - Fixed an issue in the Create Table dialog where renaming a column did not update the column references in foreign key and unique constraint definitions for the new table.
| `Issue #9091 <https://github.com/pgadmin-org/pgadmin4/issues/9091>`_ - Fix the Query Tool re-prompting for an unsaved password in a loop and rejecting the re-entered password, by caching the entered password on the server manager when the primary connection is already established.
| `Issue #9128 <https://github.com/pgadmin-org/pgadmin4/issues/9128>`_ - Fixed an issue where the object breadcrumbs popup blocked clicks on the object explorer items beneath it.
| `Issue #9595 <https://github.com/pgadmin-org/pgadmin4/issues/9595>`_ - Fix missing ALTER ... SET DEFAULT statements for inherited columns in the generated table SQL/EDIT script.
| `Issue #9677 <https://github.com/pgadmin-org/pgadmin4/issues/9677>`_ - Fix the Unlogged table toggle in table properties not generating any ALTER TABLE ... SET LOGGED/UNLOGGED statement.
| `Issue #9701 <https://github.com/pgadmin-org/pgadmin4/issues/9701>`_ - Ensure pgAdmin uses psycopg3 rather than psycopg2 when connecting to the configuration database via PGADMIN_CONFIG_CONFIG_DATABASE_URI.
| `Issue #9744 <https://github.com/pgadmin-org/pgadmin4/issues/9744>`_ - Fix a View/Edit Data crash when the session contains a transaction object that is not filter-capable (e.g. left by the Query Tool or persisted by an older version), which could prevent the desktop application from loading after an upgrade.
| `Issue #9762 <https://github.com/pgadmin-org/pgadmin4/issues/9762>`_ - Fix the "Cannot read properties of undefined (reading 'map')" crash in the desktop runtime when a menu refresh is triggered before the application menus have been received.
| `Issue #9766 <https://github.com/pgadmin-org/pgadmin4/issues/9766>`_ - Fixed an issue where a server's custom foreground colour was not applied to the object counts (children count) and column type labels shown in the object explorer.
| `Issue #9782 <https://github.com/pgadmin-org/pgadmin4/issues/9782>`_ - Optimise pgAdmin startup time by replacing the fixed server-ping interval with adaptive pinging that backs off when no servers are configured.
| `Issue #9806 <https://github.com/pgadmin-org/pgadmin4/issues/9806>`_ - Fixed an issue where generated font and image filenames contained a double dot (e.g. ``Roboto-Bold..ttf``) due to the webpack asset filename template.
| `Issue #9828 <https://github.com/pgadmin-org/pgadmin4/issues/9828>`_ - Fix tool calls failing against OpenAI-compatible providers that emit empty/null name, arguments, or id fields in streaming continuation deltas.
| `Issue #9829 <https://github.com/pgadmin-org/pgadmin4/issues/9829>`_ - Fixed installation on Python 3.9 (e.g. RHEL/Rocky/AlmaLinux 8 and 9) failing with "No module named 'pkg_resources'" by pinning setuptools below the version that dropped pkg_resources for Python 3.9.
| `Issue #9854 <https://github.com/pgadmin-org/pgadmin4/issues/9854>`_ - Fix the JSON editor stripping trailing fractional zeros (e.g. 10.00) and rewriting large integers in jsonb values, which corrupted unmodified numbers when saving.
| `Issue #9864 <https://github.com/pgadmin-org/pgadmin4/issues/9864>`_ - Fixed a regression where the rectangular (block/column) text selection in the code editor stopped working; restored the default Alt+drag selection without re-introducing the crosshair cursor on Alt+F5 (#9570).
| `Issue #9868 <https://github.com/pgadmin-org/pgadmin4/issues/9868>`_ - Warn before opening a very large JSON/JSONB value in the data grid cell editor, which could freeze pgAdmin, and let the user choose whether to proceed.
| `Issue #9875 <https://github.com/pgadmin-org/pgadmin4/issues/9875>`_ - Fixed an issue where EXPLAIN and EXPLAIN ANALYZE failed to execute when blank lines separated clauses in the SQL query.
| `Issue #9810 <https://github.com/pgadmin-org/pgadmin4/issues/9810>`_ - Use the ServerManager's passfile for the credential gate in connect() so the check matches the passfile actually used for the connection, and warn on conflicting passfile/passexec settings.
| `Issue #9892 <https://github.com/pgadmin-org/pgadmin4/issues/9892>`_ - Fix blank difference counts on the top-level group rows in Schema Diff.
| `Issue #9896 <https://github.com/pgadmin-org/pgadmin4/issues/9896>`_ - Fix invalid DDL reconstruction for SERIAL columns in Schema Diff and the generated SQL/CREATE Script so the output round-trips on a clean target.
| `Issue #9933 <https://github.com/pgadmin-org/pgadmin4/issues/9933>`_ - Remove the administrator-role bypass from the server-access helpers so the access-control checks added in 9.15 (CVE-2026-7813) are enforced uniformly. The Administrator role manages pgAdmin itself, not other users' database connections.
| `Issue #9935 <https://github.com/pgadmin-org/pgadmin4/issues/9935>`_ - Fix "Illegal instruction" crash on startup of the Linux DEB and RPM packages on older x86_64 CPUs by pinning the psycopg C extension build to the x86-64 baseline.
| `Issue #9936 <https://github.com/pgadmin-org/pgadmin4/issues/9936>`_ - Fix the AI panel silently falling back to the default provider when a custom LLM API URL or key file was set, and allow self-hosted LLM endpoints on any loopback port.
| `Issue #9939 <https://github.com/pgadmin-org/pgadmin4/issues/9939>`_ - Fix saving a newly-added row in the Query Tool failing when the result set includes expression or alias columns that are not real columns of the underlying table.
| `Issue #9952 <https://github.com/pgadmin-org/pgadmin4/issues/9952>`_ - Ship libpq-oauth-18.so and libcurl in the Docker image so PostgreSQL 18 OAuth connections work out of the box.
| `Issue #9976 <https://github.com/pgadmin-org/pgadmin4/issues/9976>`_ - Fix a startup migration crash (NoSuchTableError) when an old configuration database contains a stale foreign-key reference.
| `Issue #9984 <https://github.com/pgadmin-org/pgadmin4/issues/9984>`_ - Fix the Docker entrypoint mishandling a quoted PGADMIN_CONFIG_CONFIG_DATABASE_URI, which caused a SQLAlchemy parse error and silently skipped PGADMIN_DEFAULT_EMAIL/PASSWORD setup.
| `Issue #9985 <https://github.com/pgadmin-org/pgadmin4/issues/9985>`_ - Make CAP_NET_BIND_SERVICE optional in the Docker image so the container works on restricted runtimes that disable that capability.
| `Issue #9987 <https://github.com/pgadmin-org/pgadmin4/issues/9987>`_ - Fix "AttributeError: 'PgAdmin' object has no attribute 'login_manager'" crash when running setup.py user-management commands (add-user, update-user) from the CLI.
| `Issue #9988 <https://github.com/pgadmin-org/pgadmin4/issues/9988>`_ - Provide an actionable error when 'openid' is in OAUTH2_SCOPE but OAUTH2_SERVER_METADATA_URL is not set, instead of a cryptic Authlib failure.
| `Issue #10013 <https://github.com/pgadmin-org/pgadmin4/issues/10013>`_ - Fix pg_attribute filtering by attname returning the wrong attnum when the same column name exists in multiple tables.
| `Issue #10022 <https://github.com/pgadmin-org/pgadmin4/issues/10022>`_ - Fix AI Assistant read-only transaction bypass that allowed prompt-injected multi-statement payloads to commit out of the READ ONLY wrapper and execute arbitrary SQL, chaining to RCE via COPY ... TO PROGRAM on a superuser connection (CVE-2026-12045). Reported by Isaac Chen.
| `Issue #10026 <https://github.com/pgadmin-org/pgadmin4/issues/10026>`_ - Fix SQL injection in the named restore point endpoint where the user-supplied restore point name was interpolated into SQL via str.format() instead of being passed as a bound parameter (CVE-2026-12050). Reported by Geo.
| `Issue #10027 <https://github.com/pgadmin-org/pgadmin4/issues/10027>`_ - Fix the spurious "Crypt key is missing" error and logged traceback in the Query Tool new-connection endpoints after a backend restart, by surfacing it as the standard CRYPTKEY_MISSING response so the client recovers transparently.
| `Issue #10028 <https://github.com/pgadmin-org/pgadmin4/issues/10028>`_ - Fix open redirect in the multi-factor authentication flow via an unvalidated 'next' parameter that allowed a crafted link to redirect an authenticated victim to an attacker-controlled host (CVE-2026-12049). Reported by Mai Phạm Hiền.
| `Issue #10029 <https://github.com/pgadmin-org/pgadmin4/issues/10029>`_ - Fixed a regression where the rectangular (block/column) text selection in the code editor stopped working; restored the default Alt+drag selection without re-introducing the crosshair cursor on Alt+F5 (#9570).
| `Issue #10030 <https://github.com/pgadmin-org/pgadmin4/issues/10030>`_ - Accept prepare and binary keyword arguments in DictCursor.execute() so callers using the modern psycopg API no longer raise TypeError.
| `Issue #10059 <https://github.com/pgadmin-org/pgadmin4/issues/10059>`_ - Fix the generated SQL for editing a SQL-language function/procedure whose body contains the word "return" (e.g. a RETURNING clause), which was wrongly treated as a SQL-standard body and produced a statement without the AS $BODY$ wrapper.
| `Issue #10068 <https://github.com/pgadmin-org/pgadmin4/issues/10068>`_ - Fix critical stored cross-site scripting where PostgreSQL server error text and Explain plan-node content passed through html-react-parser across notifier toasts, form errors, modal alerts, and the Explain visualiser. Because pgAdmin's default Content-Security-Policy allows inline script and an iframe ``srcdoc`` inherits the embedding origin, the injected JavaScript ran same-origin to the victim's authenticated pgAdmin session and could read every saved server connection credential and issue arbitrary SQL against every server the victim was connected to (CVE-2026-12048). Reported by Fernando Bortotti.
| `Issue #10069 <https://github.com/pgadmin-org/pgadmin4/issues/10069>`_ - Fix HTML injection in the cloud deployment module (RDS, Azure, Google) where SDK exception text was forwarded to the browser unsanitised and rendered through html-react-parser in the Cloud Wizard (CVE-2026-12047). Reported by Fernando Bortotti.
| `Issue #10072 <https://github.com/pgadmin-org/pgadmin4/issues/10072>`_ - Fix two SQL Editor endpoints (close and update_connection) missing the ``@pga_login_required`` decorator, making them reachable without authentication in server mode and exposing a pickle deserialization sink (CVE-2026-12046). Reported by Fernando Bortotti.
| `Issue #10078 <https://github.com/pgadmin-org/pgadmin4/issues/10078>`_ - Fix SQL injection across sixteen dialog templates that rendered ``COMMENT ON ... IS '<description>'`` and the related ``pgstattuple``/``pgstatindex`` stats sinks, where a low-privilege user could plant a table or index name containing an apostrophe and a superuser viewing statistics on the object would trigger SQL execution under the superuser role. The fix switches the affected templates to the ``qtLiteral`` escape filter and rewrites the ``pgstattuple``/``pgstatindex`` calls to address the relation via OID with a ``::oid::regclass`` cast, eliminating the embedded string-literal call form entirely. Also hardens ``qtLiteral`` to raise instead of silently returning raw values when ``conn`` is falsy (CVE-2026-12044). Reported by Jasser Chebbi.
Additional changes (no associated issue)
****************************************
Dependencies
------------
Non-breaking ``dependabot`` updates aggregated for v9.16.
Python:
| ``certifi`` 2026.4.22 -> 2026.5.20
| ``cryptography`` 47.0 -> 49.0
| ``Flask-Security-Too`` 5.4 -> 5.6 (``python_version <= '3.9'``)
| ``google-auth-oauthlib`` 1.3.1 -> 1.4.0 (``python_version > '3.9'``; Python 3.9 stays on 1.3.1)
| ``requests`` >=2.33.1 -> >=2.34.2 (``python_version > '3.9'``)
| ``safety`` >=3.7.0 -> >=3.8.1
| ``selenium`` 4.43.0 -> 4.44.0
| ``testscenarios`` 0.6.1 -> 0.6.2 (``python_version > '3.9'``)
| ``typer`` 0.25 -> 0.26 (``python_version > '3.9'``)
| ``urllib3`` 2.6 -> 2.7 (``python_version > '3.9'``)
JavaScript (``web/``):
| ``@tanstack/react-query`` 5.100.5 -> 5.100.9
| ``ip-address`` 10.1.0 -> 10.1.1
| ``postcss`` 8.5.12 -> 8.5.14
JavaScript (``runtime/``):
| ``axios`` 1.16.0 -> 1.18.0
| ``electron`` 41.5.0 -> 42.3.3
| ``eslint`` 10.3.0 -> 10.4.1
+119
View File
@@ -0,0 +1,119 @@
************
Version 9.17
************
Release date: 2026-07-30
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v9.16.
Supported Database Servers
**************************
**PostgreSQL**: 14, 15, 16, 17 and 18
**EDB Advanced Server**: 14, 15, 16, 17 and 18
Bundled PostgreSQL Utilities
****************************
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 18.4
New features
************
| `Issue #9990 <https://github.com/pgadmin-org/pgadmin4/issues/9990>`_ - Include the authenticated user's identity in the HTTP access log.
| `Issue #9942 <https://github.com/pgadmin-org/pgadmin4/issues/9942>`_ - Add an opt-in Gateway API HTTPRoute template to the Helm chart as an alternative to the existing Ingress.
| `Issue #10104 <https://github.com/pgadmin-org/pgadmin4/issues/10104>`_ - Add a preference to cap the row count fetched by the plain "View Data" action, so it is usable on large tables without always doing a full ``SELECT *``.
| `Issue #10142 <https://github.com/pgadmin-org/pgadmin4/issues/10142>`_ - Add support for a custom XYZ tile provider (URL, name, CRS, attribution, max zoom) in the Geometry Viewer, alongside the existing built-in base layers.
Housekeeping
************
| `Issue #10077 <https://github.com/pgadmin-org/pgadmin4/issues/10077>`_ - Centralize shared-server-group visibility and access-control logic, and adjust the ServerGroup-to-Server/SharedServer model relationships.
| `Issue #10093 <https://github.com/pgadmin-org/pgadmin4/issues/10093>`_ - Add a basic starter contributing guide.
| `Issue #10135 <https://github.com/pgadmin-org/pgadmin4/issues/10135>`_ - Fail the macOS appbundle build if any bundled library links outside the bundle, and scan all Mach-O binaries for bundle linkage rather than only files named ``.so``/``.dylib``.
| `Issue #10154 <https://github.com/pgadmin-org/pgadmin4/issues/10154>`_ - Pin the sonarqube-scan-action GitHub workflow to a full commit SHA instead of a mutable branch ref, to harden against supply-chain tampering.
| `Issue #10138 <https://github.com/pgadmin-org/pgadmin4/issues/10138>`_ - Update the Simplified Chinese (zh_Hans_CN) translation to match the wording used in the interface.
| `Issue #10156 <https://github.com/pgadmin-org/pgadmin4/issues/10156>`_ - Pin the Yarn version used by the build scripts to the ``packageManager`` field, rather than whatever Yarn happens to be on the builder's PATH.
| `Issue #10176 <https://github.com/pgadmin-org/pgadmin4/issues/10176>`_ - Bump JavaScript and Python third-party dependencies, including axios, webpack, react, electron, and certifi.
Bug fixes
*********
| `Issue #10053 <https://github.com/pgadmin-org/pgadmin4/issues/10053>`_ - Warn when OAuth2 provider settings are misplaced at the top level of the config instead of under ``OAUTH2_CONFIG``.
| `Issue #10100 <https://github.com/pgadmin-org/pgadmin4/issues/10100>`_ - Fix Schema Diff's "Generate Script" and the browser tree's CREATE Script view emitting wrong SQL for SERIAL/identity columns, by detecting column-owned sequences via pg_depend instead of guessing the sequence name.
| `Issue #10102 <https://github.com/pgadmin-org/pgadmin4/issues/10102>`_ - Fix a Schema Diff result-status filter chip showing "No difference found" after being toggled off and back on, even when real differences exist.
| `Issue #10106 <https://github.com/pgadmin-org/pgadmin4/issues/10106>`_ - Fix the Object Explorer briefly showing the literal HTML markup instead of a greyed-out "[Disconnecting...]" label when disconnecting a server or database.
| `Issue #10107 <https://github.com/pgadmin-org/pgadmin4/issues/10107>`_ - Detect a selected-but-unusable OS keyring and fall back gracefully instead of failing.
| `Issue #10109 <https://github.com/pgadmin-org/pgadmin4/issues/10109>`_ - Fix ALT+F5 ("Execute query at cursor") doing nothing when the cursor is on or near a statement that is not highlighted, in a Query Tool tab with multiple statements separated by blank lines.
| `Issue #10110 <https://github.com/pgadmin-org/pgadmin4/issues/10110>`_ - Stop the googleapiclient cloud-deployment dependency from pulling in the system oauth2client package.
| `Issue #10117 <https://github.com/pgadmin-org/pgadmin4/issues/10117>`_ - Fix a "'Response' object is not iterable" crash when expanding a Trigger node under a Table to view its trigger function, and report the correct error message on a node.sql failure in that flow.
| `Issue #10158 <https://github.com/pgadmin-org/pgadmin4/issues/10158>`_ - Honor the selected EOL sequence when copying query text to the clipboard.
| `Issue #10187 <https://github.com/pgadmin-org/pgadmin4/issues/10187>`_ - Fix the object browser's extension UI breaking under PostgreSQL 19's extension catalog changes.
| `Issue #10190 <https://github.com/pgadmin-org/pgadmin4/issues/10190>`_ - Fix a tool-permission bypass where a user denied the Query Tool, Grant Wizard, or Schema Diff permission could still drive that tool's backend routes and Socket.IO handlers directly, since the permission check was applied only to a single "front door" route per tool. Also fixes a non-owner triggering an adhoc connection against another user's shared server persisting a new server record still owned by that other user (CVE-2026-17350). Reported by LXY.
| `Issue #10191 <https://github.com/pgadmin-org/pgadmin4/issues/10191>`_ - Fix OS command injection in the MASTER_PASSWORD_HOOK feature, where an externally-sourced username (e.g. via OAuth2/OIDC, Kerberos, or webserver authentication) containing shell metacharacters could execute arbitrary commands as the pgAdmin service account when the configured hook string uses ``%u`` (CVE-2026-17347). Reported by Thiago Pereira.
| `Issue #10192 <https://github.com/pgadmin-org/pgadmin4/issues/10192>`_ - Fix a lexer-differential bypass of the AI Assistant's read-only transaction guard, where sqlparse's string-literal lexing disagreed with PostgreSQL's own parser under ``standard_conforming_strings = on``, letting a crafted multi-statement payload smuggle a COMMIT past the intended read-only wrapper, an incomplete fix for CVE-2026-12045 (CVE-2026-17351). Reported by Kai Aizen.
| `Issue #10193 <https://github.com/pgadmin-org/pgadmin4/issues/10193>`_ - Fix SQL injection in the Index Statistics all-indexes listing and the Publications/Subscriptions Dependencies views, where an apostrophe in a table, index, publication, or subscription name broke out of an unescaped template interpolation, an incomplete fix for CVE-2026-12044 (CVE-2026-17346). Reported by Hung Tran Quoc (@rampage0010).
| `Issue #10194 <https://github.com/pgadmin-org/pgadmin4/issues/10194>`_ - Fix several Constraints, Preferences, Debugger, and Schema Diff routes missing the ``@pga_login_required`` decorator, making them reachable without authentication in server mode, an incomplete fix for CVE-2026-12046 (CVE-2026-17348). Reported by Hung Tran Quoc (@rampage0010).
| `Issue #10200 <https://github.com/pgadmin-org/pgadmin4/issues/10200>`_ - Fix an adhoc server connection cloning another user's stored database credentials (password, save password flag, tunnel password) alongside ownership, letting a non-owner who cloned another user's shared server connect using that user's saved database password (CVE-2026-17349).
| `Issue #10213 <https://github.com/pgadmin-org/pgadmin4/issues/10213>`_ - Fix OS command injection in the Import/Export Data tool, where a query-based export could pass a crafted query string past the ``\copy (...)`` parenthesis-balance guard by exploiting a backslash-escape mismatch with psql's default ``standard_conforming_strings = on`` behaviour, exposing a live ``TO PROGRAM`` clause for arbitrary command execution (CVE-2026-17566). Reported by Arpit Jain.
Additional changes (no associated issue)
****************************************
Dependencies
------------
Non-breaking ``dependabot`` and audit-driven bumps aggregated for v9.17.
Python:
| ``certifi`` 2026.5.20 -> 2026.6.17
JavaScript (``web/``):
| ``@babel/core`` 7.29.0 -> 7.29.6
| ``@date-io/date-fns`` 3.x -> 3.2.1
| ``@szhsin/react-menu`` 4.5.1 -> 4.5.2
| ``@tanstack/react-query`` 5.100.9 -> 5.101.4
| ``@types/react`` 19.2.14 -> 19.2.17
| ``ajv`` 8.18.0 -> 8.20.0
| ``anti-trojan-source`` 1.8.1 -> 1.12.0
| ``autoprefixer`` 10.5.0 -> 10.5.4
| ``axios`` 1.16.0 -> 1.18.1
| ``core-js`` added as an explicit direct dependency (3.49.0), previously relied on transitively via pickr
| ``date-fns`` 4.1.0 -> 4.4.0
| ``dompurify`` 3.4.1 -> 3.4.12
| ``eslint-plugin-jest`` 29.15.2 -> 29.15.5
| ``globals`` 17.5.0 -> 17.7.0
| ``hotkeys-js`` 4.0.3 -> 4.0.4
| ``html-to-image`` 1.11.11 -> 1.11.13
| ``ip-address`` 10.1.1 -> 10.2.0
| ``jest`` / ``jest-environment-jsdom`` 30.3.0 -> 30.4.2 / 30.4.1
| ``marked`` 18.0.2 -> 18.0.7
| ``moment-timezone`` 0.6.2 -> 0.6.3
| ``papaparse`` 5.5.3 -> 5.5.4
| ``postcss`` 8.5.10/8.5.14 -> 8.5.20
| ``react`` / ``react-dom`` 19.2.5 -> 19.2.7
| ``react-checkbox-tree`` 2.0.1 -> 2.0.2
| ``react-draggable`` 4.5.0 -> 4.7.0
| ``react-timer-hook`` 4.0.5 -> 4.0.6
| ``sharp`` 0.34.4 -> 0.35.3
| ``sql-formatter`` 15.7.3 -> 15.8.2
| ``svgo`` 4.0.1 -> 4.0.2
| ``terser-webpack-plugin`` 5.5.0 -> 5.6.1
| ``typescript-eslint`` 8.59.0 -> 8.65.0
| ``webpack`` 5.106.2 -> 5.108.4
| ``webpack-bundle-analyzer`` 5.3.0 -> 5.3.1
| ``zustand`` 5.0.12 -> 5.0.14
| Transitive advisory bumps: ``brace-expansion``, ``form-data``, ``undici``, ``js-yaml``
| Reverted ``azure-mgmt-resource`` to avoid a breaking import-path change in ``pgadmin/misc/cloud/azure``
JavaScript (``runtime/``):
| ``electron`` 42.3.3 -> 43.1.1
| ``eslint`` 10.4.1 -> 10.7.0
| ``globals`` 17.6.0 -> 17.7.0
| ``axios`` 1.18.0 -> 1.18.1
| ``ip-address`` 10.1.1 -> 10.2.0
| ``postcss`` 8.5.10 -> 8.5.20
| Pinned ``packageManager`` to ``yarn@4.15.0``
Loaded 100 of 3573 files, more files were not shown because too many files have changed in this diff. Show more