Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2026-17350

CVE-2026-17350: pgAdmin 4 Auth Bypass Vulnerability

CVE-2026-17350 is an authentication bypass flaw in pgAdmin 4 that allows users to access restricted tools despite permission denials. This article covers the technical details, affected versions, and mitigation steps.

Published:

CVE-2026-17350 Overview

CVE-2026-17350 is a missing authorization vulnerability [CWE-862] in pgAdmin 4 running in SERVER mode. The per-tool permission system introduced in pgAdmin 4 9.3 applied the permissions_required decorator only to a single front-door route per tool. Other backend routes and Socket.IO handlers relied on pga_login_required or socket_login_required, which check authentication but not tool permission. An authenticated user denied a specific tool by an administrator can still drive that tool end-to-end through unguarded routes and sockets. Affected tools include Query Tool, Grant Wizard, Schema Diff, ERD, PSQL, Debugger, Backup, Restore, Maintenance, and Import/Export.

Critical Impact

An authenticated pgAdmin user can bypass administrator-imposed tool restrictions and reach features including an interactive psql session over the /pty Socket.IO namespace and backup/restore/maintenance/import-export jobs.

Affected Products

  • pgAdmin 4 in SERVER mode, versions 9.3 through 9.16
  • pgAdmin 4 deployments using role-based tool permissions (custom roles)
  • pgAdmin 4 multi-user hosted environments relying on tool-level segregation of duties

Discovery Timeline

  • 2026-07-31 - CVE-2026-17350 published to NVD
  • 2026-08-05 - Last updated in NVD database

Technical Details for CVE-2026-17350

Vulnerability Analysis

The pgAdmin 4 per-tool permission system was designed to let administrators grant or deny tools like Query Tool, Grant Wizard, and Schema Diff to specific roles. The enforcement was incomplete. Only one entry route per tool carried the permissions_required Flask-Security decorator. All other HTTP routes and Socket.IO event handlers in the same tool's workflow relied on authentication-only guards.

The reporter validated three end-to-end bypasses against a test build. A user without tools_query_tool permission received HTTP 403 on the protected sqleditor initialization route, then reused the same session to connect the server, initialize the viewdata chain, and retrieve real table rows. A user without tools_grant_wizard bypassed the acl gate but still enumerated grantable objects, generated GRANT SQL, and applied it, confirmed via has_table_privilege(). A user without tools_schema_diff bypassed the panel route and obtained real DDL diffs via the compare_database Socket.IO handler.

Root Cause

The root cause is inconsistent decorator application [CWE-862]. Socket.IO handlers had no permission-aware equivalent of permissions_required; only socket_login_required existed. Backend routes internal to each tool's workflow were also missing the decorator. A related defect in /misc/workspace/adhoc_connect_server allowed a non-owner triggering the endpoint against an administrator-owned shared server to persist a new server row still owned by the administrator, leaking user_id and shared fields from the source.

Attack Vector

An authenticated user with a valid pgAdmin login and a working stored database connection, but explicitly denied a specific tool by an administrator, can invoke the tool's non-front-door routes and Socket.IO namespaces directly. The bypass does not grant new database privileges; it circumvents pgAdmin's organizational segregation-of-duties control at the application layer.

python
# Security patch: socket_permissions_required decorator added in
# web/pgadmin/authenticate/__init__.py
def socket_permissions_required(*fsperms):
    """Socket.IO equivalent of flask_security's permissions_required.

    Refuses the event unless the current user is authenticated and holds
    all of the named pgAdmin permissions (via their roles). This mirrors
    the HTTP @permissions_required decorator so that Socket.IO handlers
    enforce the same tool-level RBAC as the routes.
    """
    def wrapper(f):
        @functools.wraps(f)
        def wrapped(*args, **kwargs):
            if not current_user.is_authenticated:
                disconnect()
                raise ConnectionRefusedError("Unauthorised !")

            for fsperm in fsperms:
                if not current_user.has_permission(fsperm):
                    disconnect()

Source: pgadmin4 commit 461c3afb

Detection Methods for CVE-2026-17350

Indicators of Compromise

  • HTTP 403 responses on tool front-door routes such as /sqleditor/initialize, /acl, or /schema_diff/panel followed immediately by HTTP 200 responses on other routes of the same tool from the same session
  • Socket.IO connections to /pty, /schema_diff, or viewdata namespaces from users whose roles lack the corresponding tools_* permission
  • Server rows in the pgAdmin config database created via adhoc_connect_server whose user_id does not match the requesting session

Detection Strategies

  • Review pgAdmin access logs for sessions that receive 403 on a tool's protected route but continue to interact with other routes belonging to that tool
  • Correlate Socket.IO handler invocations with the authenticated user's assigned permissions in the pgAdmin role table
  • Audit the pgAdmin server table for entries whose ownership fields diverge from the creating user

Monitoring Recommendations

  • Enable verbose Flask request logging and capture the permissions_required denial events alongside subsequent same-session requests
  • Monitor invocations of compare_database, /pty, and backup/restore job endpoints and cross-reference with role assignments
  • Alert on unexpected GRANT statements executed by users without tools_grant_wizard permission via PostgreSQL audit logging (pgaudit)

How to Mitigate CVE-2026-17350

Immediate Actions Required

  • Upgrade pgAdmin 4 to version 9.17 or later, which applies permissions_required and socket_permissions_required as the outermost decorators on every backend route and Socket.IO handler for affected tools
  • Inventory custom roles that rely on tool-level deny policies and reconfirm intended access after upgrade
  • Review the pgAdmin server configuration table and remove any anomalous rows created through adhoc_connect_server misuse

Patch Information

The fix is delivered across multiple commits in the pgadmin-org/pgadmin4 repository, including commit 461c3afb, commit 64a9cdbd, commit ba198471, and commit d36bd8dc. Tracking details are in GitHub Issue #10190. Regression tests assert HTTP 403 on every gated route and socket handler for a permissionless user.

Workarounds

  • Restrict pgAdmin SERVER mode access to trusted users only until upgrading, treating tool-level denies as advisory rather than enforced
  • Rely on PostgreSQL role privileges rather than pgAdmin tool permissions for security-critical segregation of duties
  • Place pgAdmin behind a reverse proxy that logs and can rate-limit Socket.IO namespaces such as /pty and /schema_diff
bash
# Verify running pgAdmin 4 version and upgrade via pip
python -c "import pgadmin4; print(pgadmin4.__version__)"
pip install --upgrade "pgadmin4>=9.17"

# For container deployments
docker pull dpage/pgadmin4:9.17
docker stop pgadmin && docker rm pgadmin
docker run -d --name pgadmin -p 80:80 dpage/pgadmin4:9.17

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.