system: OPERATIONAL
← back to all hacks
SUPPLY CHAIN MEDIUM

pgAdmin 4 ships an LLM panel and a classic LFI+SSRF arrives with it (CVE-2026-7817)

pgAdmin 4 9.15 patches an authenticated LFI and SSRF in its new LLM API configuration endpoints. The bug class is decades old; the surface is brand new.

2026-05-28 // 6 min affects: pgadmin-4-9.13, pgadmin-4-9.14

What is this?

On May 11, 2026, the pgAdmin project disclosed CVE-2026-7817, a pair of bugs in the LLM API configuration endpoints that ship with pgAdmin 4 9.13 and 9.14. The reporter, credited as j3seer, found that two user-settable preferences — api_key_file and api_url — flow into the LLM provider client with no validation. An authenticated pgAdmin user can read arbitrary files the server process can open (local file inclusion) and coerce the server into making HTTP requests to internal targets such as cloud metadata services (server-side request forgery). The fix landed in pgAdmin 4 9.15 via commit 24485fe96.

The bug class is forty years old. What is new is the surface: pgAdmin’s LLM panel — an assistant for query authoring — is the seam through which both vectors reach the network and the filesystem. As more legacy products add “ask the assistant” panels in 2026, this exact category of finding is going to keep recurring.

How it works

The LLM panel lets a user point pgAdmin at an OpenAI-compatible endpoint by configuring two values: a URL for the API base, and a path to a file containing the key. Both values are stored as user preferences and consumed by the chat path and the model-list endpoints.

The vulnerable shape, simplified from the GitHub issue and the 9.15 patch:

# Vulnerable (< 9.15) — pseudocode of the LLM client construction
api_url      = preferences.get("llm.api_url")        # arbitrary string
api_key_path = preferences.get("llm.api_key_file")   # arbitrary path

with open(api_key_path, "r") as f:        # LFI: any file readable by pgAdmin
    api_key = f.read()

client = OpenAIClient(base_url=api_url,   # SSRF: any URL, including
                      api_key=api_key)    #       http://169.254.169.254/...

Two reachable behaviours fall out of that:

  1. LFI via api_key_file. The path is opened by the pgAdmin service account. The contents are then handed to the provider client as an API key. The natural way to observe the read is the error path — a malformed key triggers a response that confirms the read happened, and in some configurations echoes a fragment. Pointing the field at a service-account config, a private key, or a credentials file is enough to confirm exfiltration.
  2. SSRF via api_url. The string is used verbatim as the base URL for outbound HTTP. Setting it to http://169.254.169.254/latest/meta-data/iam/security-credentials/ on a cloud-hosted pgAdmin causes the server to fetch instance-metadata credentials on the attacker’s behalf. The chat and model-list endpoints both reach this code path.

Exploitation requires authentication, which is the only thing keeping the CVSS at 6.5 (CVSS 3.1) / 7.1 (CVSS 4.0). On a pgAdmin instance with self-registration, weak SSO mapping, or a leaked low-privilege account, that prerequisite is easy.

The 9.15 patch is small and worth reading. It does three things at the LLM-preferences boundary: it confines api_key_file to the user’s private storage (server mode) or home directory (desktop mode), it requires the file to be printable ASCII and caps the read at 1024 bytes, and it gates api_url against a new allow-list (config.ALLOWED_LLM_API_URLS) checked at every entry point.

Why it matters

Three things, in order of how often you will see them in 2026.

First, the bolt-on LLM panel is a new attack surface for old bugs. pgAdmin shipped a small AI feature and inherited a CWE-552 (LFI) and a CWE-918 (SSRF) at the seam. The same pattern is showing up across DB tooling, IDEs, ticketing systems, and observability dashboards as each adds “configure your LLM provider here” preferences. Every one of those preference fields that reaches a file open or an HTTP client without validation is a candidate.

Second, authenticated does not mean safe. The pgAdmin role required to set LLM preferences is held by anyone who can log in and edit their own settings. For a multi-tenant pgAdmin behind SSO — a common shape in fintech and platform engineering — the practical attacker is anyone with a corporate identity and a reason to point a tool at their own pgAdmin tab.

Third, SSRF to cloud metadata remains the single highest-payoff post-auth pivot on managed PostgreSQL fleets. If pgAdmin is running in EC2, GCE, or AKS with an instance role attached, the SSRF reads the role’s STS credentials. Those credentials then read the database secret, the RDS snapshot bucket, and whatever else the role has been granted. CVE-2026-7817 is interesting less for what it is than for which environment it sits in.

This is the same pattern flagged earlier in 2026 in LMDeploy CVE-2026-33626 (SSRF in an inference server) and LiteLLM CVE-2026-42208 (SQLi in an LLM proxy): traditional web vulnerabilities in software that did not exist three years ago, sitting close to credentials and to model traffic.

Defenses

  1. Upgrade to pgAdmin 4 9.15 or later. The patch is in release notes for 9.15 and bundled with seven other security fixes (CVE-2026-7813 through CVE-2026-7820) in the same release. Do not pick this one off.
  2. Audit existing LLM preferences before upgrading. Pull the preferences table from your pgAdmin database and look for api_key_file values pointing outside the user storage area and api_url values that are not https://api.openai.com, https://api.anthropic.com, or whatever provider you actually use. Anything else is either misconfiguration or evidence of exploitation.
  3. Set config.ALLOWED_LLM_API_URLS explicitly. Once on 9.15, define the allow-list in config_local.py with the two or three provider endpoints your team actually uses. The defaults are conservative but the value of the control comes from being narrow.
  4. Block egress from pgAdmin to RFC1918 and link-local. On cloud-hosted pgAdmin, deny outbound traffic from the pgAdmin host to 169.254.0.0/16, 127.0.0.0/8, and the relevant RFC1918 ranges, except for the database itself. This neutralises the SSRF impact even if a future variant slips past the allow-list. On AWS, prefer IMDSv2 with a hop limit of 1 so a server-side request cannot reach the metadata service in the first place.
  5. Run pgAdmin as a dedicated low-privilege user. The LFI vector only reads what the pgAdmin process can read. A service account whose home directory is empty and whose group memberships do not include ssl-cert, shadow, or the cloud-init credentials path strips most of the value of the read primitive.
  6. Generalise the lesson to your own LLM panels. If you ship a product with an “add your LLM provider” page, treat the API URL like any other user-controlled URL fed to an HTTP client (allow-list scheme, host, port; resolve once and pin) and treat the key path like any other user-controlled file path (confine to a writable user directory, cap the read length, refuse symlinks). The pgAdmin 9.15 patch is a workable reference shape.

Status

ItemReferenceDateNotes
Issue filedpgAdmin GitHub #99002026-05-01Reported by j3seer
CVE assignedCVE-2026-78172026-05-11NVD published
pgAdmin 4 9.15 releasedpgAdmin project2026-05-11Eight security fixes total
Patch commit24485fe962026-05-11Allow-list + path containment + ASCII shape
Vendor write-upSentinelOne2026-05-18Includes detection guidance
SeverityNVDCVSS 3.1: 6.5 / CVSS 4.0: 7.1
Affected versionspgAdmin project9.13 ≤ version < 9.15

The right takeaway is not “pgAdmin had a bug.” It is “the LLM-configuration boundary is becoming a standard place to find LFI and SSRF in mature products, and the validation patterns to defend it are now known.” Apply the patch; then apply the pattern.

Sources