Skip to content
Heist/ feed
Back to feed
Medium

pgAdmin 4: Tool permission bypass via backend routes and Socket.IO handlers

Type: advisorySources: OSV

The per-tool permission system (custom roles / role-based tool permissions, introduced in pgAdmin 4 9.3) did not enforce its permission check consistently. In SERVER mode, pgAdmin 4 gates each tool behind a per-tool Flask-Security permission, but the permission decorator (permissions_required) was applied only to a single "front door" route per tool. Every other backend route and Socket.IO handler in that tool's workflow relied solely on pga_login_required/socket_login_required, which check authentication but not the tool permission. The reporter verified three cases against a test build: (1) a user without tools_query_tool permission received 403 on the protected sqleditor initialization route, but the same session went on to connect the server, initialize the viewdata backend chain, and retrieve real table row content; (2) a user without tools_grant_wizard received 403 on the protected acl route, but the same session still enumerated grantable objects, generated GRANT SQL, and successfully applied it -- confirmed database-side via has_table_privilege(); (3) a user without tools_schema_diff received 403 on the protected panel route, but the same session initialized schema diff, enumerated and connected databases, and obtained real DDL differences via the compare_database Socket.IO handler. The reporter also confirmed a related but distinct issue: a non-owner triggering /misc/workspace/adhoc_connect_server against an administrator-owned shared server caused pgAdmin to persist

Products
Not specified
Affected versions
Not specified
Fixed versions
Not specified
CVEs
CVE-2026-17350
Confirmed fixed CVEs
Not specified
Updated in this feed
7 Nov 2026, 10:04 UTC · Revision 1

Source evidence

pgAdmin 4: Tool permission bypass via backend routes and Socket.IO handlers

core-osv · Original source

Published 31 Jul 2026, 16:00 UTC

The per-tool permission system (custom roles / role-based tool permissions, introduced in pgAdmin 4 9.3) did not enforce its permission check consistently. In SERVER mode, pgAdmin 4 gates each tool behind a per-tool Flask-Security permission, but the permission decorator (permissions_required) was applied only to a single "front door" route per tool. Every other backend route and Socket.IO handler in that tool's workflow relied solely on pga_login_required/socket_login_required, which check authentication but not the tool permission. The reporter verified three cases against a test build: (1) a user without tools_query_tool permission received 403 on the protected sqleditor initialization route, but the same session went on to connect the server, initialize the viewdata backend chain, and retrieve real table row content; (2) a user without tools_grant_wizard received 403 on the protected acl route, but the same session still enumerated grantable objects, generated GRANT SQL, and successfully applied it -- confirmed database-side via has_table_privilege(); (3) a user without tools_schema_diff received 403 on the protected panel route, but the same session initialized schema diff, enumerated and connected databases, and obtained real DDL differences via the compare_database Socket.IO handler. The reporter also confirmed a related but distinct issue: a non-owner triggering /misc/workspace/adhoc_connect_server against an administrator-owned shared server caused pgAdmin to persist

osv:CVE-2026-17350
Complete source record
{
  "id": "c9971a7b3f58a8d09adb781cf390356440af611ae5cac9c60628429210fde83c",
  "url": "https://osv.dev/vulnerability/CVE-2026-17350",
  "method": "structured",
  "payload": {
    "kind": "advisory",
    "title": "pgAdmin 4: Tool permission bypass via backend routes and Socket.IO handlers",
    "summary": "The per-tool permission system (custom roles / role-based tool permissions, introduced in pgAdmin 4 9.3) did not enforce its permission check consistently. In SERVER mode, pgAdmin 4 gates each tool behind a per-tool Flask-Security permission, but the permission decorator (permissions_required) was applied only to a single \"front door\" route per tool. Every other backend route and Socket.IO handler in that tool's workflow relied solely on pga_login_required/socket_login_required, which check authentication but not the tool permission. The reporter verified three cases against a test build: (1) a user without tools_query_tool permission received 403 on the protected sqleditor initialization route, but the same session went on to connect the server, initialize the viewdata backend chain, and retrieve real table row content; (2) a user without tools_grant_wizard received 403 on the protected acl route, but the same session still enumerated grantable objects, generated GRANT SQL, and successfully applied it -- confirmed database-side via has_table_privilege(); (3) a user without tools_schema_diff received 403 on the protected panel route, but the same session initialized schema diff, enumerated and connected databases, and obtained real DDL differences via the compare_database Socket.IO handler. The reporter also confirmed a related but distinct issue: a non-owner triggering /misc/workspace/adhoc_connect_server against an administrator-owned shared server caused pgAdmin to persist",
    "evidence": "osv:CVE-2026-17350",
    "products": [],
    "severity": "medium",
    "structured": {
      "id": "CVE-2026-17350",
      "aliases": [
        "CVE-2026-17350"
      ],
      "metrics": [
        {
          "type": "CVSS_V4",
          "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N"
        }
      ],
      "affected": [
        {
          "ranges": [
            {
              "repo": "https://github.com/pgadmin-org/pgadmin4",
              "type": "GIT",
              "events": [
                {
                  "introduced": "aa282711f84f70118cf350af66cec20085567c99"
                },
                {
                  "fixed": "067f7af2274cf034623426d2b74a7695018a5f1f"
                },
                {
                  "fixed": "461c3afba92baad37c70a6fbd52d205d13a9de53"
                },
                {
                  "fixed": "64a9cdbd6a240a962144f84418beaf9e66419779"
                },
                {
                  "fixed": "ba1984718ad703011740ad48cb9b82402b89cc2c"
                },
                {
                  "fixed": "d36bd8dc96812c716664feac533d240544e70adc"
                }
              ],
              "database_specific": {
                "extracted_events": [
                  {
                    "cpe": "cpe:2.3:a:pgadmin:pgadmin_4:*:*:*:*:*:postgresql:*:*",
                    "range": [
                      {
                        "introduced": "9.3"
                      },
                      {
                        "fixed": "9.17"
                      }
                    ],
                    "source": "CPE_RANGE"
                  },
                  {
                    "range": [
                      {
                        "fixed": "461c3afba92baad37c70a6fbd52d205d13a9de53"
                      }
                    ],
                    "source": "REFERENCES_COMMIT"
                  },
                  {
                    "range": [
                      {
                        "fixed": "64a9cdbd6a240a962144f84418beaf9e66419779"
                      }
                    ],
                    "source": "REFERENCES_COMMIT"
                  },
                  {
                    "range": [
                      {
                        "fixed": "ba1984718ad703011740ad48cb9b82402b89cc2c"
                      }
                    ],
                    "source": "REFERENCES_COMMIT"
                  },
                  {
                    "range": [
                      {
                        "fixed": "d36bd8dc96812c716664feac533d240544e70adc"
                      }
                    ],
                    "source": "REFERENCES_COMMIT"
                  }
                ]
              }
            }
          ],
          "versions": [
            "REL-9_16",
            "REL-9_15",
            "REL-9_14",
            "REL-9_13",
            "REL-9_12",
            "REL-9_11",
            "REL-9_10",
            "REL-9_9",
            "REL-9_8",
            "REL-9_7",
            "REL-9_6",
            "REL-9_5",
            "REL-9_4",
            "REL-9_3"
          ],
          "database_specific": {
            "source": "https://storage.googleapis.com/cve-osv-conversion/osv-output/CVE-2026-17350.json"
          }
        }
      ],
      "modified": "2026-10-28T02:30:50.247483527Z",
      "provider": "osv",
      "references": [
        "https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/17xxx/CVE-2026-17350.json",
        "https://nvd.nist.gov/vuln/detail/CVE-2026-17350",
        "https://github.com/pgadmin-org/pgadmin4/issues/10190",
        "https://github.com/pgadmin-org/pgadmin4/commit/461c3afba92baad37c70a6fbd52d205d13a9de53",
        "https://github.com/pgadmin-org/pgadmin4/commit/64a9cdbd6a240a962144f84418beaf9e66419779",
        "https://github.com/pgadmin-org/pgadmin4/commit/ba1984718ad703011740ad48cb9b82402b89cc2c",
        "https://github.com/pgadmin-org/pgadmin4/commit/d36bd8dc96812c716664feac533d240544e70adc",
        "https://github.com/pgadmin-org/pgadmin4"
      ],
      "provider_metadata": {
        "cwe_ids": [
          "CWE-862"
        ],
        "cna_assigner": "PostgreSQL",
        "osv_generated_from": "https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/17xxx/CVE-2026-17350.json"
      }
    },
    "related_cves": [
      {
        "value": "CVE-2026-17350",
        "evidence": "/id"
      }
    ],
    "security_relevant": true,
    "severity_evidence": "/severity/0/score"
  },
  "source_id": "core-osv",
  "document_id": 518955,
  "published_at": "2026-07-31T16:00:03.744+00:00"
}