Direct answer: CVE-2026-116241 lets a normal pgAdmin 4 account run operating system commands on the pgAdmin server through the Import/Export Data tool. The CVSS 3.1 score is 9.9. Today, check the version, check who has the Import/Export Data permission, then update to pgAdmin 4 v9.19.

Release 9.19 closes 15 CVEs at once, CVE-2026-116238 to CVE-2026-116252. One of them, CVE-2026-116238, lets an attacker bypass the failed login limit. According to the CVE-2026-116241 record, the command runs as the OS account that runs pgAdmin.

Main condition: The attacker needs 1 pgAdmin account with the Import/Export Data permission. The default User role has that permission. Limit: This article has no query content, no payload, and no attack sequence.

We read the CVE-2026-116241 record, the CVE-2026-116238 record, the pgAdmin 4 v9.19 release notes, the release announcement on postgresql.org, issue #10663, the dpage/pgadmin4 image page on Docker Hub, and CISA KEV on 2 November 2026, from 06.35 to 07.40 WIB. We tested 3 scripts in podman containers with the official dpage/pgadmin4 9.18 and 9.19 images, including an upgrade with the same volume. We wrote the apt, dnf, pip, and desktop paths from the pgAdmin download pages, with no test.

The problem: a normal pgAdmin account can run commands on the server

One leaked pgAdmin account or one dishonest employee is enough to get into the pgAdmin server.

pgAdmin 4 is a web-based admin tool for PostgreSQL. Many teams install it on a server with Docker, then give accounts to developers, analysts, or vendors. A normal account should only work in the databases that it owns. This flaw lets a normal account run commands on the pgAdmin host itself.

According to the CVE-2026-116241 record, CVE-2026-116241 is a bypass of the CVE-2026-17566 fix. The same tool also got the CVE-2026-7816 fix in version 9.15. So the pgAdmin 9.18 that you installed in September is still vulnerable.

Timeline: 9.15 on 11 May, 9.18 on 17 September, a placeholder issue on 23 October, release 9.19 on 29 October, 15 CVEs on 30 October, not on KEV on 2 November.
Grey dots: older fixes in the same tool. Teal dot: the safe release. Red dots: CVE publication. Amber dots: the status when this article came out. Source: CVE.org, pgAdmin release notes, pgAdmin GitHub, Docker Hub, and CISA KEV.

The dpage/pgadmin4 image page on Docker Hub shows that the 9.19 and latest tags came out on 29 October 2026, at 15.29 UTC. The CVE records came out on 30 October 2026, at 19.55 UTC, from the PostgreSQL CNA. On 2 November 2026, the pgAdmin security page does not show these 15 CVEs yet.

How it works: 1 export tool, commands on the host

The query export check in pgAdmin and the psql parser read quote marks in different ways.

This section uses the official summary. Its purpose is to help you choose the correct steps, not to explain an attack.

Summary from the CVE record

According to the CVE-2026-116241 record, the Import/Export Data tool wraps the user's export query in a psql \copy command, then checks that query. The check refuses a backslash inside a single-quoted string, but not inside a double-quoted identifier. When a specific database setting changes, psql can run a command on the pgAdmin host before the database server refuses the query.

The CVE record also gives 2 important facts. A normal database role can change that setting for itself. The Import/Export Data permission goes to non-admins by default. The fix in 9.19 refuses every backslash inside a double-quoted identifier.

Scores of the 15 CVEs in release 9.19

The score is different for each CVSS version, so this table gives both. All scores come from the PostgreSQL CNA.

CVEAreaCVSS 3.1CVSS 4.0
CVE-2026-116241The Import/Export Data tool runs an OS command on the pgAdmin host9.99.4
CVE-2026-116238The failed login limit and password checks can be bypassed8.19.2
CVE-2026-116239An LDAP or Kerberos login enters another account with the same name8.58.9
CVE-2026-116249Connection file paths escape the user storage folder8.47.2
CVE-2026-116242, 116244, 116245, 116247SSH, Kerberos, services, and password files use the pgAdmin service account identity8.27.1
CVE-2026-116243The utility binary path can point to shared storage7.57.7
CVE-2026-116248A normal user can share a server with every user6.86.0
CVE-2026-116240, 116250, 116252, 116246File Manager, symlink races, materialized view refresh, and passwords in the environment6.57.0 to 7.3
CVE-2026-116251Other sessions stay alive after a password change4.22.3
Three cards: CVE-2026-116241 Import/Export Data score 9.9, CVE-2026-116238 failed login limit score 9.2, and 13 other CVEs. Safe version: pgAdmin 4 v9.19.
(1) Red cards: the 2 flaws with the highest scores. (2) Amber card: 13 other flaws in the same release. (3) Amber panel: the attack condition. (4) Teal box: the safe version. Source: CVE records from the PostgreSQL CNA and the pgAdmin 4 v9.19 release notes.

CVE-2026-116238 is important for servers that are open to the internet. According to the CVE-2026-116238 record, pgAdmin locks an account after 3 failed logins, but some login paths do not count the tries. As a result, an attacker with no account can guess passwords with no limit, including the admin password.

Exploitation status on 2 November 2026

We found no attack reports, no public exploit code in official sources, and no CISA KEV entry. issue #10663 only had a placeholder until release 9.19.

Rama Digital analysis: the pgAdmin login page shows the version number to anyone, for example ver=91800 for 9.18. Automatic scanners can find vulnerable servers fast. Do not wait for the KEV status.

Who has the highest risk

The risk depends on who can log in to pgAdmin, not only on the version.

This table summarises the CVE-2026-116241 record, the CVE-2026-116238 record, and the pgAdmin 4 v9.19 release notes.

pgAdmin conditionRiskReason
Server mode open to the internetHighestAnyone can try to log in, and the failed login limit can be bypassed
Many non-admin accounts: team, interns, vendorsHighThe default User role has the Import/Export Data permission
LDAP or Kerberos loginHighCVE-2026-116239 can log a user in to another account with the same name
pgAdmin on the same host as a database or applicationCheckCommands run as the pgAdmin OS account, so secrets on the host are open too
1 admin, VPN access onlyCheckAn attacker needs an account first, but the update is still required
Desktop mode on a laptopLowOnly 1 local user
Six risk cards: server mode open to the internet, many non-admin accounts, and LDAP login are red; a shared database host and VPN access are amber; desktop mode is grey.
Red cards: many people can log in. Amber cards: check the configuration. Grey card: low risk. Bottom box: a version check from outside. Source: CVE records, pgAdmin 4 v9.19 release notes, and Rama Digital analysis.

A pgAdmin server often keeps production database passwords. One pgAdmin host under attacker control can open every database that is registered there. The same risk pattern is in the LXD and Incus certificate flaw CVE-2026-112816: an admin tool that holds many servers is an asset with the highest patch priority.

Vulnerable and safe versions

Every pgAdmin 4 version below 9.19 is vulnerable to these 15 CVEs. Use this table to find the update step for your install type.

Install typeStatus on 2 November 2026Move to
Docker image dpage/pgadmin4Vulnerable up to tag 9.18Tag 9.19, out on 29 October
The latest tagVulnerable if pulled before 29 OctoberPull again, then check the version
apt or dnf package from the pgAdmin repositoryVulnerable below 9.19The 9.19 package
Python wheel pgadmin4Vulnerable below 9.19pgadmin4==9.19
Desktop for Windows and macOSVulnerable, lower risk because of 1 userThe 9.19 installer
Cannot update todayVulnerableStep 4, then update as soon as possible
A table of 6 install types: Docker up to tag 9.18, an old latest tag, apt, dnf, pip, and desktop are vulnerable. Bottom box: 4 server mode settings before the first 9.19 start.
Red: vulnerable, a safe version exists. Amber: lower risk or a temporary step. Bottom box: the settings that change in 9.19. Source: pgAdmin 4 v9.19 release notes, Docker Hub, and the CVE records.

The version data comes from the CVE-2026-116241 record, the pgAdmin 4 v9.19 release notes, and the dpage/pgadmin4 image page on Docker Hub. The CVE records give the affected range "before 9.19" for all 15 CVEs. We found no backport to older branches, so do not wait for a 9.18.x version.

Prerequisites

  • sudo access or Docker access to every server that runs pgAdmin.
  • A pgAdmin admin account to open the Users menu.
  • A list of the people who really need a pgAdmin account. Examples: database admins, developers, and analysts.
  • A backup of the /var/lib/pgadmin volume or folder before the update.
  • A maintenance window of 10 to 15 minutes. The container is made again, so active sessions stop.

Step 1: Check the pgAdmin version

Save this script as check-version-pgadmin.sh. The script reads the version from Docker or Podman containers, apt or dnf packages, the version.py file, or the login page.

#!/usr/bin/env bash
# Check the pgAdmin 4 version for CVE-2026-116241 and 14 other CVEs in release 9.19.
# Use:   sudo bash check-version-pgadmin.sh                (containers, packages, and files on this machine)
#        bash check-version-pgadmin.sh --url https://PGADMIN-ADDRESS
#        bash check-version-pgadmin.sh 9.14 9.18 9.19         (versions that you copied from other servers)
SAFE_VERSION=9.19

result() {
  if [ -z "$2" ]; then echo "$1: version not readable"; return; fi
  if [ "$(printf '%s\n%s\n' "$SAFE_VERSION" "$2" | sort -V | head -n1)" = "$SAFE_VERSION" ]; then
    echo "$1: pgAdmin $2 -> SAFE (fixed since $SAFE_VERSION)"
  else
    echo "$1: pgAdmin $2 -> VULNERABLE, update to $SAFE_VERSION or later"
  fi
}

# URL mode: the login page shows the version as a number, for example ver=91800 for 9.18.
if [ "$1" = "--url" ]; then
  fetch() { if command -v curl >/dev/null 2>&1; then curl -fsSL -m 10 "$1"; else wget -qO- -T 10 "$1"; fi; }
  num=$(fetch "${2%/}/login" | grep -o 'ver=[0-9]*' | head -n1 | cut -d= -f2)
  if [ -z "$num" ]; then echo "$2: version not readable from the login page"; exit 1; fi
  result "$2" "$((num / 10000)).$(((num % 10000) / 100))"
  exit 0
fi

# Manual mode: each argument is 1 version.
if [ $# -gt 0 ]; then
  for v in "$@"; do result "typed version" "$v"; done
  exit 0
fi

found=0
seen=" "
# Docker or Podman containers with a pgadmin4 image. The podman-docker package makes docker call podman, so skip a name seen before.
for rt in docker podman; do
  command -v "$rt" >/dev/null 2>&1 || continue
  while read -r name image; do
    case "$image" in *pgadmin4*) ;; *) continue ;; esac
    case "$seen" in *" $name "*) continue ;; esac
    seen="$seen$name "
    v=$("$rt" exec "$name" python3 -c 'import sys; sys.path.insert(0, "/pgadmin4"); import version; print(version.APP_VERSION)' 2>/dev/null)
    result "container $name ($image)" "$v"; found=1
  done < <("$rt" ps --format '{{.Names}} {{.Image}}' 2>/dev/null)
done

# apt or dnf packages from the official pgAdmin repository.
for p in pgadmin4-server pgadmin4-web pgadmin4-desktop; do
  v=""
  command -v dpkg-query >/dev/null 2>&1 && v=$(dpkg-query -W -f='${Version}' "$p" 2>/dev/null)
  [ -z "$v" ] && command -v rpm >/dev/null 2>&1 && v=$(rpm -q --qf '%{VERSION}' "$p" 2>/dev/null | grep -v 'not installed')
  [ -n "$v" ] && { result "package $p" "${v%%[-~]*}"; found=1; }
done

# The version.py file from a package or a container.
for d in /usr/pgadmin4/web /pgadmin4; do
  [ -f "$d/version.py" ] || continue
  v=$(cd "$d" && python3 -c 'import version; print(version.APP_VERSION)' 2>/dev/null)
  result "file $d/version.py" "$v"; found=1
done

[ "$found" = 0 ] && echo "pgAdmin not found on this machine. For pip: pip show pgadmin4. For desktop: the Help menu, About pgAdmin 4."
exit 0
# On the server: read the version from containers, packages, or the version.py file
sudo bash check-version-pgadmin.sh
# From a laptop: read the version from the pgAdmin login page
bash check-version-pgadmin.sh --url https://PGADMIN-ADDRESS
# Check versions that you copied from other servers
bash check-version-pgadmin.sh 9.14 9.18 9.19
Terminal: the version check reads the pgadmin 9.18 container as VULNERABLE and pgadmin-lab 9.19 as SAFE. Manual mode and login page mode give the same result.
The test uses podman. The docker command in the image calls podman, and the script reads both. This test found a duplicate container name bug, and we fixed it. Source: Rama Digital test in podman containers with the official dpage/pgadmin4 9.18 and 9.19 images, 2 November 2026.

For pip, run pip show pgadmin4 in the pgAdmin virtualenv. For desktop, open the Help menu, then About pgAdmin 4.

Check the result: "VULNERABLE" means go to steps 2 and 3 today. "SAFE" still means do step 5, because this flaw is in every version before 29 October.

Step 2: Check who can use Import/Export Data

This flaw needs 1 account with the Import/Export Data permission, so the account list sets how fast you must act.

Save this script as check-access-pgadmin.py. The script only reads the pgAdmin configuration database in read-only mode.

# Check who can use the Import/Export Data tool in pgAdmin 4 (CVE-2026-116241).
# Docker:  docker exec -i pgadmin python3 - < check-access-pgadmin.py
# Package: sudo python3 check-access-pgadmin.py /var/lib/pgadmin/pgadmin4.db
# The script only reads the pgAdmin configuration database. It changes nothing.
import sqlite3
import sys

db = sys.argv[1] if len(sys.argv) > 1 else '/var/lib/pgadmin/pgadmin4.db'
c = sqlite3.connect(f'file:{db}?mode=ro', uri=True)
PERM = 'tools_import_export_data'

roles = {rid: (name, perms) for rid, name, perms in c.execute('SELECT id, name, permissions FROM role')}
accounts = list(c.execute(
    'SELECT u.email, u.auth_source, u.active, r.name, r.permissions FROM user u '
    'JOIN roles_users ru ON ru.user_id = u.id JOIN role r ON r.id = ru.role_id ORDER BY u.id'))
admins = [a for a in accounts if a[3] == 'Administrator']
print(f'Configuration database: {db}')
print(f'pgAdmin accounts: {len(accounts)} (admin {len(admins)}, non-admin {len(accounts) - len(admins)})')

for name, perms in roles.values():
    if name == 'Administrator':
        continue
    on = PERM in (perms or '').split(',')
    status = 'ON -> accounts with this role can use the vulnerable tool' if on else 'removed -> OK'
    print(f'Role {name}: Import/Export Data permission {status}')

can = [a for a in accounts if a[3] != 'Administrator' and PERM in (a[4] or '').split(',')]
if can:
    print('Non-admin accounts with that permission:')
    for email, source, active, role, _ in can:
        print(f'  - {email} (role {role}, login {source}, {"active" if active else "inactive"})')

cols = [r[1] for r in c.execute('PRAGMA table_info(server)')]
if 'passexec_cmd' in cols:
    n = c.execute("SELECT COUNT(*) FROM server WHERE COALESCE(passexec_cmd, '') <> ''").fetchone()[0]
    note = ' -> set SERVER_PASSEXEC_COMMANDS before the first 9.19 start' if n else ''
    print(f'Servers with a Password Exec Command: {n}{note}')
# Docker or Podman. Replace pgadmin with the name of your container.
docker exec -i pgadmin python3 - < check-access-pgadmin.py
# apt or dnf package, web mode
sudo python3 check-access-pgadmin.py /var/lib/pgadmin/pgadmin4.db
Terminal: the access check on pgAdmin 9.18 shows 4 accounts, the User role with the Import/Export Data permission ON, and 3 non-admin accounts that can use the vulnerable tool.
The User role gets this permission by default. We added 3 dummy accounts with the User role through the setup.py add-user command. Source: Rama Digital test in podman containers with the official dpage/pgadmin4 9.18 and 9.19 images, 2 November 2026.

This script reads the default SQLite database. When your pgAdmin uses PostgreSQL as its configuration database, check accounts and roles in the Users menu.

Check the result: "ON" and a list of non-admin accounts means that every account in the list can use the vulnerable tool. Compare that list with the people who really need pgAdmin. Turn off accounts that you do not know.

Step 3: Update to pgAdmin 4 v9.19

Only the update closes these 15 flaws. Choose the commands for your install type from step 1.

# Docker: pull the new image, then make the container again with your old options
docker pull dpage/pgadmin4:9.19
docker stop pgadmin && docker rm pgadmin
docker run -d --name pgadmin [old options: -p, -e, -v] dpage/pgadmin4:9.19

# Docker Compose: change the image tag in compose.yaml to dpage/pgadmin4:9.19
docker compose pull pgadmin
docker compose up -d pgadmin

# apt package (Ubuntu, Debian) from the pgAdmin repository
sudo apt update
sudo apt install --only-upgrade pgadmin4 pgadmin4-web pgadmin4-server pgadmin4-desktop
sudo systemctl restart apache2

# dnf package (RHEL, Rocky, Fedora) from the pgAdmin repository
sudo dnf upgrade 'pgadmin4*'
sudo systemctl restart httpd

# Python wheel in a virtualenv
pip install --upgrade pgadmin4==9.19

# Check again after the update
sudo bash check-version-pgadmin.sh
Terminal: the 9.18 container is removed, the 9.19 image is pulled, and the container is made again with the same volume. The version check prints SAFE and the 4 accounts stay.
The /var/lib/pgadmin volume keeps the accounts and settings. The 9.19 container updates the configuration database at the first start. Source: Rama Digital test in podman containers with the official dpage/pgadmin4 9.18 and 9.19 images, 2 November 2026.
  1. Back up the /var/lib/pgadmin volume first. This volume keeps accounts, registered servers, and settings.
  2. Use the same docker run options as the old container: ports, env, and volumes. Change only the image tag.
  3. Do not use the latest tag without a version check. A tag that you pulled before 29 October is still 9.18.
  4. For the apt package, follow the pgAdmin apt download page. The web package uses Apache, so restart Apache after the update.
  5. Run check-version-pgadmin.sh again on every server.

Before the first 9.19 start in server mode

The pgAdmin 4 v9.19 release notes change 4 behaviours in server mode. Check these items before the update, because some connections can stop.

  • Password Exec Command: free-text commands are replaced by the SERVER_PASSEXEC_COMMANDS list. Fill in that list before the first start. When it is empty, the old commands are not used and the log shows a warning.
  • libpq services: a server that uses a service works only when the service name is in SERVER_MODE_ALLOWED_SERVICES.
  • SSH tunnels: tunnels no longer use the SSH key of the pgAdmin service account. Keep an identity file in user storage.
  • Password files and client certificates: libpq no longer uses the files of the service account. Set those files on each server.

Step 2 counts the servers with a Password Exec Command. When the number is more than 0, add this list. We tested this Docker env line in a 9.19 container, and the container started normally.

# Docker: add this env to the 9.19 container. The value uses Python syntax.
-e PGADMIN_CONFIG_SERVER_PASSEXEC_COMMANDS="{'vault': ['/usr/local/bin/get-pg-password', '--ttl', '300']}"

# Package: write it in /usr/pgadmin4/web/config_local.py
SERVER_PASSEXEC_COMMANDS = {
    'vault': ['/usr/local/bin/get-pg-password', '--ttl', '300'],
}
A 6-row ladder: check the version, check the Import/Export Data permission, update to 9.19, 2 steps labelled not a fix (remove the permission, close access), then check signs.
(1) to (3): the fix order. (4a) and (4b): temporary steps. The red labels remind you that only the update closes the flaw. Source: pgAdmin 4 v9.19 release notes, CVE records, and Rama Digital analysis.

Check the result: the version script prints "SAFE" on every server, and the access script shows the same number of accounts as before the update.

Step 4: Restrict access until the update is done

This step reduces who can use the flaw, but it does not close the flaw.

The CVE record and the release notes give no official workaround. Rama Digital analysis: we made 2 steps from the flaw conditions. Condition: the update cannot run today. Limit: admins keep full permissions, and a leaked admin account is still dangerous.

4a. Remove Import/Export Data from non-admin roles

According to the User Management documentation, an admin manages accounts, roles, and permissions in the Users, Roles, and Permissions tabs. Log in as an admin, open the account menu at the top right, then choose Users. In the Permissions tab, search for Import/Export Data, then remove that permission from the User role and from your own roles.

Terminal: after the permission is removed, the access check prints role User: Import/Export Data permission removed, OK. No non-admin account can use the tool.
In the test, we removed the permission through the configuration database because the test container has no browser. On your server, use the Users menu in pgAdmin. Source: Rama Digital test in podman containers with the official dpage/pgadmin4 9.18 and 9.19 images, 2 November 2026.

Run check-access-pgadmin.py again. Give the permission back after the update to 9.19 if your team needs the export tool.

4b. Close pgAdmin to the internet

pgAdmin does not need to be open to the internet. Open the port only on 127.0.0.1, then get access through an SSH tunnel or a VPN. Another method is to allow only the office IP in the reverse proxy.

# Docker: open the pgAdmin port only on 127.0.0.1 of the server
docker run -d --name pgadmin -p 127.0.0.1:5050:80 [other options] dpage/pgadmin4:9.19
# From the admin laptop: make an SSH tunnel, then open http://127.0.0.1:5050
ssh -L 5050:127.0.0.1:5050 admin@SERVER-ADDRESS

# Nginx in front of pgAdmin: allow only the office or VPN IP
location / {
    allow 198.51.100.10;
    deny all;
    proxy_pass http://127.0.0.1:5050;
}

The sample IP uses the RFC 5737 documentation block. We did not run this port example in the test, because our test rules do not allow open ports. Also turn on pgAdmin two-factor authentication for admin accounts.

Check the result: the access script prints "removed", and the pgAdmin login page does not open from another network, for example a phone hotspot.

Step 5: Look for signs of use

The attacker uses a valid pgAdmin account, so compare activity with team records.

Save this script as check-signs-pgadmin.py. The script shows Import/Export Data jobs per account, marks jobs that have the word PROGRAM, and shows accounts with failed logins. The script does not print the job commands.

# Check for signs of Import/Export Data use and password guessing in pgAdmin 4.
# Docker:  docker exec -i pgadmin python3 - < check-signs-pgadmin.py
# Package: sudo python3 check-signs-pgadmin.py /var/lib/pgadmin/pgadmin4.db
# The script only reads the process history and account data. It does not print the commands.
import sqlite3
import sys

db = sys.argv[1] if len(sys.argv) > 1 else '/var/lib/pgadmin/pgadmin4.db'
c = sqlite3.connect(f'file:{db}?mode=ro', uri=True)
admins = {u for (u,) in c.execute(
    "SELECT ru.user_id FROM roles_users ru JOIN role r ON r.id = ru.role_id WHERE r.name = 'Administrator'")}

jobs = [j for j in c.execute(
    'SELECT p.user_id, u.email, p.start_time, p.exit_code, p.command, p.arguments '
    'FROM process p LEFT JOIN user u ON u.id = p.user_id ORDER BY p.start_time')
    if (j[4] or '').endswith('psql') and 'copy' in (j[5] or '').lower()]
print(f'Import/Export Data jobs in the process history: {len(jobs)} (UTC time)')
for uid, email, start, code, _, args in jobs:
    signs = []
    if 'program' in (args or '').lower():
        signs.append('SIGN: the command has the word PROGRAM')
    if uid not in admins:
        signs.append('non-admin account')
    print(f'  {(start or "?")[:16]}  {email}  exit code {code}  {", ".join(signs) or "normal"}')

failed = list(c.execute('SELECT email, login_attempts, locked FROM user WHERE login_attempts > 0 OR locked = 1'))
print(f'Accounts with failed logins or a lock: {len(failed)}')
for email, n, lock in failed:
    print(f'  - {email}: {n} failed{", LOCKED" if lock else ""}')
# Import/Export Data job history and accounts with failed logins
docker exec -i pgadmin python3 - < check-signs-pgadmin.py
# Requests to the Import/Export tool and to other login views in the container access log
docker logs pgadmin 2>&1 | grep -E '"POST /import_export/job/' | tail -n 20
docker logs pgadmin 2>&1 | grep -E '"POST /(login|verify|change|reset)' | tail -n 20
# Processes in the container. Normal: python3, master, pickup, qmgr.
docker exec pgadmin ps -o user,pid,ppid,comm
Terminal: the signs check shows 3 Import/Export jobs, 1 SIGN label for the word PROGRAM from a non-admin account, 1 LOCKED admin, 0 log requests, and normal processes.
All jobs and failed logins are dummy data that we wrote to test the script. The script does not print the job commands. Source: Rama Digital test in podman containers with the official dpage/pgadmin4 9.18 and 9.19 images, 2 November 2026.
Two columns of signs: in pgAdmin (odd Import/Export jobs, the word PROGRAM, locked or new accounts) and on the host (shell processes, POST to other login views, outbound traffic).
Amber column: signs inside pgAdmin. Red column: signs on the host and network. Bottom box: the steps when you find even 1 sign. Source: CVE records, Rama Digital podman test, and Rama Digital analysis.

The official Docker image writes the access log to stdout, so docker logs has every request. The pgAdmin login form sends data to /authenticate/login. Rama Digital analysis: a POST to /login, /verify, /change, or /reset is not the usual form flow, so check its source IP.

Check the result: all jobs come from accounts that you know, there is no "SIGN" label, and the container has only normal processes. The job history can be deleted from the UI, so no sign does not mean safe.

When you find even 1 sign, disconnect pgAdmin from the network, then keep the logs and pgadmin4.db as evidence. Change the passwords of all pgAdmin accounts and the database passwords saved in pgAdmin. Change SSH keys and secrets on the host, then rebuild the container from the 9.19 image.

When the database holds customer data, Indonesian Law 27/2022 on Personal Data Protection requires a written notice within 3 times 24 hours after a personal data leak. Also read the GitLab CVE-2026-97229 warning.

Example: 1 morning of response for 3 pgAdmin servers

This simulation uses dummy data. Server names, times, and results are not customer data.

Time (WIB)Dummy serverCheckResultAction
08.00pga-team (Docker 9.18, open to the internet)Steps 1 and 2VULNERABLE, 6 non-admin accountsSteps 4a and 4b, then update
08.30pga-lab (Docker latest, pulled in August)Step 1VULNERABLE, old latest tagPull 9.19 again
09.00dba-laptop (desktop 9.14)Step 1VULNERABLE, 1 userInstall the 9.19 build
09.30pga-teamStep 51 job from a vendor account at 02.14Incident response, change database passwords

The lesson from the simulation: a safe version does not prove that a server is clean. Pga-team was updated, but step 5 still found a job from before the update.

Who applies the patch

The patch responsibility is different for each install type, so this table shows who acts today.

Install typePatch ownerWhat you do
Docker container on your own serverYour team pulls the 9.19 imageSteps 1 to 5
apt or dnf package from the pgAdmin repositoryYour team runs the package updateSteps 1 to 5
pgAdmin from a hosting panel or a ready-made VPS imageThe hosting provider or your teamAsk for written proof of the version, then steps 2 and 5
Desktop on an admin laptopThe laptop ownerInstall the 9.19 build

Rama Digital recommendation: treat pgAdmin like a master key to your databases. Limit accounts with the least privilege rule, as in the guide to access rights, approvals, and audit logs. Also update the runtime on the same server, for example for the PHP2Shell warning. For a wider review standard, read the GitLab CVE-2026-97229 warning.

Checklist for today

  1. Make a list of all pgAdmin installs: servers, containers, and laptops. Owner: infrastructure team. Evidence: a list of names and versions.
  2. Check the version with step 1. Owner: server admin. Evidence: the script output.
  3. Check accounts and permissions with step 2. Owner: pgAdmin admin. Evidence: the script output.
  4. Apply step 4 on every pgAdmin that cannot update today. Owner: pgAdmin admin. Evidence: the access script prints "removed".
  5. Update to 9.19. Set SERVER_PASSEXEC_COMMANDS first if needed. Owner: server admin. Evidence: the script output after the update.
  6. Check for signs with step 5. Owner: server admin and security team. Evidence: the script output and the access log.
  7. Remove accounts that nobody uses, and turn on two-factor authentication for admins.

Questions and answers

I use pgAdmin desktop on a laptop. Am I safe?

Your risk is low, because desktop mode has only 1 local user. Still install the 9.19 build, because this release closes 15 flaws.

Can an attacker with no account use CVE-2026-116241?

Not directly. This flaw needs 1 pgAdmin account with the Import/Export Data permission. But CVE-2026-116238 allows password guesses with no limit, so a pgAdmin that is open to the internet still has a high risk.

Do attackers already use this flaw?

On 2 November 2026, we found no attack reports, and this flaw is not on CISA KEV. The login page shows the version number, so vulnerable servers are easy to find.

I updated to 9.18 in September. Why is it still vulnerable?

Version 9.18 fixed CVE-2026-17566. CVE-2026-116241 is a bypass of that fix, so every version below 9.19 is vulnerable.

Can removing the Import/Export Data permission replace the update?

No. That step closes only 1 of the 15 flaws, and admin accounts keep the permission. The update to 9.19 is still required.

Do I lose accounts and registered servers when I make the container again?

No, when you use the same volume for /var/lib/pgadmin. Our test moved a container from 9.18 to 9.19, and the 4 accounts stayed. Back up that volume first.

I use pgAdmin from a hosting provider. What do I do?

Ask the provider for written proof of the version. Then check the version from the login page with step 1, and check your accounts in the Users menu.

Next step

The limits that still apply: step 4 does not close the flaw, and an update does not remove access that an attacker already used. When your pgAdmin shows a sign in step 5, or you need help with an audit and recovery, use Vibe Code Rescue. For 1 urgent issue today, book a 30-minute session.

Sources