FlawOpen

flawopen.com/Incidents/gitlab-commits-api-path-traversal-september-2026

● GitLab · CVE-2026-85706 · September 2026 · CVSS 10.0 · Critical
FlawOpen Security Research

CVE-2026-85706: GitLab Upload File Disclosure

GitLab’s unauthenticated upload parser could select server files; malformed escapes exposed fragments in errors, separate from file selection.[1], [3]

CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N[3]
💡 Plain English Explainer (ELI5)

Imagine a reception clerk accepting a visitor’s handwritten cabinet location instead of the document placed in the intake tray. Before checking the visitor’s badge, the clerk opens that cabinet; if a line is unreadable, the clerk quotes it aloud in an error message. Opening the document is one failure; repeating its contents is another.[1]

Core Concepts & Subsystem Terms

Upload metadata
Request-supplied file.path or metadata.path must not replace a buffer registered by trusted upload middleware.[1]
Percent decoding
Form parsing interprets % followed by two hexadecimal digits. An invalid escape can raise an error that includes the offending fragment.[1]
CWE-22 / CVSS
The CNA classifies CVE-2026-85706 as path traversal, CVSS 10.0 Critical; the dangerous boundary is attacker-controlled server-side file selection.[3]

Framework mappings

Incident Timeline

2026-09-10

GitLab released 19.1.8, 19.2.6 and 19.3.2 with the security fix.[2]

2026-09-23

The release advisory was updated with backports 18.11.12 and 19.0.9.[2]

Step-by-Step Attack Flow

Step 1

Selection before authentication

Upload metadata could substitute a server path for the expected temporary upload buffer before authentication.[1]

Step 2

Content becomes parameters

With application/x-www-form-urlencoded, the selected file was interpreted as form data rather than returned as a normal commit response.[1]

Step 3

Error reflection

An invalid percent escape could put the failing fragment into an error. The & and = delimiters affect its extent; a read alone does not prove disclosure.[1]

Step 4

Investigation

Correlate Rails API and Workhorse entries with correlation_id; compare api_error with written_bytes to assess whether response bytes were sent.[1]

Source Code: Flaw vs. Secure Implementation

Illustrative Python boundary model, not GitLab source or its patch. The vulnerable example uses only in-memory buffers and deliberately treats any percent sign as a parser failure; it does not reproduce real percent validation or fragment selection. The hardened version authenticates before handling content, rejects request metadata, accepts only a trusted registered byte buffer, bounds its size and returns generic errors. Deploy the vendor update: authentication inside a route is too late if earlier middleware has already parsed attacker-selected files.[1]

UNPATCHED FLAW
from urllib.parse import unquote_to_bytes

def parse_upload(authenticated, metadata, buffers):
    # Harmless in-memory fixtures; no filesystem access.
    body = buffers.get(metadata.get("path"), b"")
    if b"%" in body:
        return 400, body.decode("ascii", errors="replace")
    if not authenticated:
        return 401, "Unauthorized"
    return 200, unquote_to_bytes(body)
HARDENED SECURE PATCH
from urllib.parse import unquote_to_bytes

MAX_BYTES = 4096
HEX = frozenset(b"0123456789abcdefABCDEF")

def parse_upload(authenticated, metadata, registered_body):
    # registered_body comes from trusted upload middleware,
    # never from a path selected by request parameters.
    if not authenticated:
        return 401, "Unauthorized"
    if metadata or type(registered_body) is not bytes:
        return 400, "Invalid upload"
    if len(registered_body) > MAX_BYTES:
        return 413, "Upload too large"
    for i, byte in enumerate(registered_body):
        if byte == 37 and (i + 2 >= len(registered_body) or
                registered_body[i+1] not in HEX or
                registered_body[i+2] not in HEX):
            return 400, "Invalid upload"
    return 200, unquote_to_bytes(registered_body)

Engineering & System Hardening Checklist

Sources