flawopen.com/Incidents/gitlab-commits-api-path-traversal-september-2026
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]
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-85706as path traversal, CVSS 10.0 Critical; the dangerous boundary is attacker-controlled server-side file selection.[3]
Framework mappings
- CWE reference for the weakness described in this guide: CWE-22
- CISA KEV — added 2026-09-11 — CVE-2026-85706
Incident Timeline
GitLab released 19.1.8, 19.2.6 and 19.3.2 with the security fix.[2]
The release advisory was updated with backports 18.11.12 and 19.0.9.[2]
Step-by-Step Attack Flow
Selection before authentication
Upload metadata could substitute a server path for the expected temporary upload buffer before authentication.[1]
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]
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]
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]
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)
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
- ✓Upgrade to a supported fixed release. Patched branch floors are 18.11.12, 19.0.9, 19.1.8, 19.2.6 and 19.3.2; affected releases start at 18.7. Verify the installed version against the vendor advisory.[2], [3]
- ✓Test anonymous requests with harmless in-memory markers: assert no buffer lookup before authentication and no marker in responses or ordinary logs. Reject client path metadata before upload parsing.[1]
- ✓Preserve the Rails API and Workhorse logs; join entries on
correlation_id, then compareapi_errorwithwritten_bytes. Inspect decoded file.path and metadata.path values rather than excluding whole log lines containing a benign upload path. A read is not proof of disclosure: GitLab says a reflected fragment in api_error, corroborated by Workhorse written_bytes, indicates bytes were returned; HTTP status alone is not a verdict.[1] - ✓Scope rotation to credentials in the confirmed or reasonably suspected disclosure. If
db_key_baseorotp_key_basemay be involved, consult GitLab Support before rotating: GitLab says there is currently no supported rotation path fordb_key_baseand warns that unsupported key rotation can cause data loss. Missing logs are not evidence of no disclosure.[1]