← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2026-31942 · CWE-862 · Disclosed 2026-06-02

LibreChat is an enhanced ChatGPT clone that supports multiple AI providers

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
01 · The Real Story

This is a valet who can swap someone else’s car key tag, not steal the whole garage

CVE-2026-31942 is an authenticated IDOR/mass-assignment bug in LibreChat’s PUT /api/keys endpoint. In affected builds, the route passes userId: req.user.id and then spreads ...req.body, so an attacker-supplied userId overrides the authenticated user and lets any logged-in user overwrite another user’s stored provider key data. The GitHub advisory lists affected versions as <= 0.7.6, with a fix in 0.8.3-rc1 published on 2026-06-02.

Vendor HIGH is technically defensible in a lab because this is cross-user integrity failure over the network with low privileges. In real enterprise deployments it lands lower: the attacker must already have an account, must know or obtain a victim userId, and only gets leverage where LibreChat is actually using per-user stored API keys; this is not pre-auth, not host compromise, and not a clean data-exfil primitive on its own.

"Post-auth cross-user tampering, but not an internet-scale emergency"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Land a normal LibreChat user session

The attacker first authenticates to the target LibreChat instance using any legitimate low-privilege account. Tooling is mundane here: browser, curl, or Burp Suite is enough because the vulnerable route sits behind standard JWT auth. This immediately makes the bug post-initial-access rather than perimeter-breaking.
Conditions required:
  • Valid LibreChat user account
  • Reachability to the application over the network
Where this breaks in practice:
  • Requires prior access through SSO/local auth/invite flow
  • MFA, IdP hygiene, and tenant onboarding controls can stop this before the vuln matters
Detection/coverage: Perimeter scanners will miss this; authenticated DAST/API testing is required.
STEP 02

Obtain a victim userId

To weaponize the overwrite, the attacker needs a target user identifier to place in the JSON body. Practical tooling is again Burp, browser dev tools, or scraped app artifacts; the advisory does not provide a built-in enumeration path, so this usually depends on separate leaks, logs, exports, admin UI exposure, or another weakness.
Conditions required:
  • Knowledge of a valid victim userId
Where this breaks in practice:
  • No native user enumeration is described in the advisory
  • If user IDs are not exposed elsewhere, this becomes the biggest operational hurdle
Detection/coverage: Little native scanner coverage; this is business-logic context, not a signature-friendly exploit stage.
STEP 03

Overwrite the victim’s stored provider key with curl or Burp Repeater

The attacker sends PUT /api/keys with a body such as { "userId": "<victim>", "name": "openAI", "value": "attacker-key" }. Because the route spreads req.body after setting the server-side user ID, JavaScript object precedence lets the attacker-controlled userId win and the backend updates the victim record.
Conditions required:
  • Authenticated session
  • Valid victim userId
  • Target is running vulnerable code path
Where this breaks in practice:
  • Only impacts stored key records, not broader account ownership
  • If the deployment uses server-side shared provider credentials instead of user-stored keys, exploit value drops hard
Detection/coverage: API gateway, WAF, or proxy logs can catch anomalous PUT /api/keys volume, but generic web vul scanners usually do not model this overwrite.
STEP 04

Wait for downstream misuse or disruption

Impact shows up when the victim later uses the provider associated with the overwritten key. The attacker may cause denial of service with an invalid key, or in some configurations observe future prompts and usage metadata through the external provider account they control; that confidentiality angle is real but deployment-dependent rather than automatic.
Conditions required:
  • Victim continues using the affected provider
  • LibreChat deployment stores and uses per-user provider keys
Where this breaks in practice:
  • No immediate shell, tenant admin, or database takeover
  • Prompt visibility depends on how the external AI provider logs requests and what the victim actually uses
Detection/coverage: User complaints, provider auth failures, unexpected provider account usage, and audit deltas on stored key records are your best signals.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo public exploitation evidence found in the sources reviewed as of 2026-06-03. This is an inference from the absence of KEV listing and lack of indexed campaign reporting.
KEV statusNot listed in CISA KEV as of 2026-06-03.
Public PoCYes, effectively trivial. The vendor advisory includes a complete attack scenario and the request shape is simple enough to reproduce with curl or Burp; no exploit framework is needed.
EPSSNo public EPSS value was found in indexed sources on 2026-06-03. Treat that as unavailable, not as proof of low risk.
Vendor CVSSCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L = authenticated network attack, easy to execute once inside, strong integrity impact, limited availability impact.
Affected versionsGitHub advisory says <= 0.7.6.
Fixed versionPatched in 0.8.3-rc1 per the GitHub advisory.
Exposure realityLibreChat is popular open source software with ~38k GitHub stars and easy Docker deployment, but this bug is only reachable by authenticated users and only matters where user-stored API keys are in play.
Scanning / internet exposureNo product-specific GreyNoise/Censys/Shodan evidence was found in the reviewed sources for this CVE. Even if a LibreChat login page is internet-exposed, the vuln remains post-auth business logic.
Disclosure / creditDisclosed 2026-06-02 via GitHub Security Advisory GHSA-5jcj-rh68-cgj7. The advisory page reviewed does not list a public researcher credit.
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to MEDIUM (5.8/10)

The decisive friction is attacker position: this bug requires an already-authenticated LibreChat user and a valid victim userId, which makes it a post-initial-access abuse path rather than a perimeter event. The blast radius is further narrowed because the exploit modifies stored provider-key state; it does not directly hand over host control, tenant admin, or guaranteed message disclosure.

HIGH Technical root cause and affected/fixed versions from the GitHub advisory
MEDIUM Real-world impact reduction based on the need for victim `userId` and per-user key usage
LOW Public exploitation telemetry because no direct GreyNoise/Censys/EPSS record was found in indexed sources

Why this verdict

  • Down from vendor HIGH: requires authenticated remote access, which implies the attacker already cleared your identity layer and is not exploiting the internet-facing edge cold.
  • Further down: the attacker also needs a victim userId; the advisory shows overwrite logic, not a built-in enumeration primitive, so the exploit chain has a real dependency.
  • Impact is narrower than raw CVSS suggests: this tampers with stored provider keys and can disrupt or potentially observe future provider traffic, but it is not server RCE, database dump, or tenant-admin takeover.
  • Population is narrower: only deployments that let users store their own provider/API keys get the full impact. Environments using centrally managed server-side keys reduce the practical value of the bug.
  • Still not low: this is cross-user authorization failure in a multi-user app, and once the prerequisites are met the overwrite itself is easy and reliable.

Why not higher?

This is not pre-auth and not broadly wormable. It does not provide direct code execution, direct read access to all chats, or immediate control of the LibreChat host; exploitation value depends on a second condition that many shops will not meet, namely active use of per-user stored provider keys.

Why not lower?

The authorization failure is real, cross-user, and easy to execute once prerequisites are satisfied. In shared internal AI portals, letting one ordinary user silently replace another user’s API key is a serious integrity break with plausible downstream confidentiality consequences.

05 · Compensating Control

What to do — in priority order.

  1. Disable or avoid user-stored provider keys where possible — If your deployment can use centrally managed service credentials instead of user_provided keys, do that; it removes most of the exploit value because there is no per-user key record worth hijacking. For a MEDIUM verdict there is no mitigation SLA — go straight to the 365-day remediation window, but this is worth doing earlier if your tenant model is shared.
  2. Audit and alert on PUT /api/keys — Log request metadata, actor, target record, provider name, and old/new key events so cross-user key churn becomes visible. There is no mitigation SLA for this severity, but deploy the telemetry on your normal change cadence so you are not blind while you wait for remediation.
  3. Reduce low-privilege account sprawl — This vuln only exists after login, so tightening invites, dormant accounts, SSO groups, and MFA materially lowers exposure. Again, no mitigation SLA — go straight to the 365-day remediation window unless this is an externally accessible multi-user portal.
  4. Review where user IDs are exposed — The exploit is much easier if userId values leak through logs, exports, browser responses, or admin tooling. Minimize that exposure and sanitize support artifacts to preserve the main friction point that keeps this bug from being a higher-priority fire.
What doesn't work
  • A WAF alone will not reliably stop this; the malicious request looks like a normal authenticated API call and the flaw is object-level authorization logic.
  • Network segmentation does little if users already reach the LibreChat app; the abuse happens over the legitimate application path.
  • Rotating provider keys globally is not a fix; the attacker can simply overwrite the victim record again until the application logic is corrected.
06 · Verification

Crowdsourced verification payload.

Run this on the LibreChat host, inside the LibreChat container, or in CI against a checked-out source tree. Invoke it with python3 check_cve_2026_31942.py /app (Docker default example) or python3 check_cve_2026_31942.py /path/to/LibreChat; it only needs read access to the application files.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
# CVE-2026-31942 verification helper for LibreChat
# Exit codes:
#   0 = PATCHED
#   1 = VULNERABLE
#   2 = UNKNOWN / unable to determine

import json
import os
import re
import sys
from pathlib import Path

VULN_ROUTE = Path('api/server/routes/keys.js')
PACKAGE_JSON = Path('package.json')


def parse_semver(v):
    # Supports forms like 0.7.6, v0.7.6, 0.8.3-rc1
    v = v.strip()
    if v.startswith('v'):
        v = v[1:]
    m = re.match(r'^(\d+)\.(\d+)\.(\d+)(?:-([A-Za-z]+)(\d+)?)?$', v)
    if not m:
        return None
    major, minor, patch, pre_label, pre_num = m.groups()
    major, minor, patch = int(major), int(minor), int(patch)
    if pre_label is None:
        pre_rank = 1  # stable > prerelease
        pre_label = ''
        pre_num = 0
    else:
        pre_rank = 0
        pre_num = int(pre_num) if pre_num else 0
    return (major, minor, patch, pre_rank, pre_label, pre_num)


def cmp_ver(a, b):
    return (a > b) - (a < b)


def read_version(root):
    pkg = root / PACKAGE_JSON
    if not pkg.exists():
        return None
    try:
        data = json.loads(pkg.read_text(encoding='utf-8'))
        return data.get('version')
    except Exception:
        return None


def inspect_route(root):
    route = root / VULN_ROUTE
    if not route.exists():
        return None
    try:
        text = route.read_text(encoding='utf-8', errors='ignore')
    except Exception:
        return None

    # Vulnerable pattern from advisory
    if 'await updateUserKey({ userId: req.user.id, ...req.body });' in text:
        return 'vulnerable'

    # Likely fixed pattern from advisory
    fixed_markers = [
        'const { name, value, expiresAt } = req.body;',
        'await updateUserKey({ userId: req.user.id, name, value, expiresAt });'
    ]
    if all(marker in text for marker in fixed_markers):
        return 'patched'

    # Heuristic: if req.body is spread into updateUserKey after userId, flag as vulnerable
    heuristic = re.search(r'updateUserKey\s*\(\s*\{[^}]*userId\s*:\s*req\.user\.id[^}]*\.\.\.req\.body[^}]*\}\s*\)', text, re.S)
    if heuristic:
        return 'vulnerable'

    return 'unknown'


def main():
    if len(sys.argv) != 2:
        print('UNKNOWN - usage: python3 check_cve_2026_31942.py /path/to/LibreChat')
        sys.exit(2)

    root = Path(sys.argv[1]).resolve()
    if not root.exists() or not root.is_dir():
        print(f'UNKNOWN - path not found or not a directory: {root}')
        sys.exit(2)

    route_status = inspect_route(root)
    version = read_version(root)

    if route_status == 'vulnerable':
        print(f'VULNERABLE - code pattern matches CVE-2026-31942 in {root / VULN_ROUTE}')
        sys.exit(1)
    if route_status == 'patched':
        print(f'PATCHED - fixed code pattern found in {root / VULN_ROUTE}')
        sys.exit(0)

    if version:
        parsed = parse_semver(version)
        threshold = parse_semver('0.7.6')
        if parsed and threshold:
            if cmp_ver(parsed, threshold) <= 0:
                print(f'VULNERABLE - package.json version {version} is <= 0.7.6 and code pattern was inconclusive')
                sys.exit(1)
            else:
                print(f'PATCHED - package.json version {version} is > 0.7.6 and code pattern was inconclusive')
                sys.exit(0)

    print('UNKNOWN - could not confirm vulnerable route pattern or parse LibreChat version')
    sys.exit(2)


if __name__ == '__main__':
    main()
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: identify LibreChat instances that are multi-user and allow user-provided/stored API keys, because those are the only ones where this bug has real teeth. For this MEDIUM reassessment there is no noisgate mitigation SLA — go straight to the 365-day remediation window; queue the vendor fix within the noisgate remediation SLA of ≤365 days, and if you run an externally accessible shared tenant, front-load verification and logging even though there is no KEV or public exploitation evidence as of 2026-06-03.

Sources

  1. GitHub Security Advisory GHSA-5jcj-rh68-cgj7
  2. LibreChat security advisory index
  3. LibreChat docs: AI Endpoints (`user_provided` keys)
  4. LibreChat docs: Custom Endpoints
  5. LibreChat docs: Agents API and user-generated API keys
  6. LibreChat quick start / deployment options
  7. CISA Known Exploited Vulnerabilities Catalog
  8. FIRST EPSS FAQ
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.