Feb 13, 2026·6 min read·54 visits
lakeFS failed to properly sanitize file paths in its Local Block Adapter. Due to a missing trailing slash in a prefix check and loose namespace validation, attackers can use `../` sequences to access files outside their repo. Fixed in v1.77.0.
A critical path traversal vulnerability in the lakeFS Local Block Adapter allows authenticated users to break out of their storage namespace boundaries. By exploiting a weak prefix validation check and a namespace logic error, attackers can read and write files in sibling repositories or unrelated directories on the host filesystem.
lakeFS bills itself as "Git for data." It’s a slick piece of engineering that brings branching, committing, and rolling back to your massive object storage buckets (S3, Azure Blob, GCS). But not everyone runs lakeFS on the cloud. For local development, testing, or just smaller deployments, lakeFS includes a Local Block Adapter. This component acts as a translation layer, pretending your local filesystem is an object store.
The Local Block Adapter is supposed to be a sandbox. It takes a root directory—say, /var/lib/lakefs/—and promises that all data operations will stay strictly within that box. It manages repositories as subdirectories, keeping repo-A separate from repo-B. Ideally, this creates a hard boundary, preventing a user in one repository from peeking at the data in another.
But boundaries in code are only as strong as the logic verifying them. CVE-2026-26187 is the story of what happens when you verify the general neighborhood but forget to check the specific house address. It turns out, lakeFS was checking if you were on the right street, but it didn't care if you were breaking into the neighbor's basement.
This vulnerability is actually a "buy one, get one free" deal. It stems from two distinct logic failures in pkg/block/local/adapter.go that, when combined, allow for a delightful escape. The first is a classic Prefix Bypass. When the adapter checked if a requested file path was valid, it used strings.HasPrefix.
Here is the catch: strings.HasPrefix("/data/lakefs_secrets", "/data/lakefs") returns true. Why? Because structurally, the string /data/lakefs is literally the start of /data/lakefs_secrets. By failing to append a directory separator (like /) to the check, the code allowed access to any sibling directory that started with the same characters. It’s like a bouncer letting you into "Club 33" because your ID says "Club 3"—close enough, right?
The second flaw is a Namespace Escape. lakeFS manages data in "Namespaces" (roughly mapping to repositories). The code correctly calculated where a namespace should be, but it didn't enforce that the final file path stayed inside that specific namespace. It only checked if the file was inside the global adapter root. This meant that if I'm in repo-A, I could issue a request for ../../repo-B/sensitive_file. The global check looks at the path, sees it resolves to /var/lib/lakefs/repo-B/sensitive_file, nods its head because that is indeed under /var/lib/lakefs, and serves up the stolen data.
Let's look at the smoking gun. This is the vulnerable verification function found in pkg/block/local/adapter.go. It's short, sweet, and insecure.
// Vulnerable Code: The Prefix Bypass
func (l *Adapter) verifyRelPath(p string) error {
// DANGER: No trailing separator on l.path
if !strings.HasPrefix(filepath.Clean(p), l.path) {
return fmt.Errorf("%s: %w", p, ErrBadPath)
}
return nil
}Because l.path (the adapter root) isn't terminated with a slash, an attacker can access /lakefs_backup if the root is /lakefs. But it gets worse. Here is how parameters were extracted and constructed:
// Vulnerable Code: The Namespace Escape
p := path.Join(l.path, ptr.StorageNamespace[len(DefaultNamespacePrefix):], ptr.Identifier)
if err := l.verifyRelPath(p); err != nil {
return "", err
}The code blindly joins the root, the namespace, and the user-controlled Identifier. It then passes the result to the flawed verifyRelPath. Even if verifyRelPath was perfect, this logic is still flawed because it only asserts that the file is in the Adapter Root, not the Namespace.
Here is the fix introduced in commit cbc106275357302a834280f133265dc39f1384ce. Notice the two-step verification: first verify the namespace is valid, then verify the final path is strictly inside that namespace.
// Patched Code
// 1. Validate the Namespace directory itself
namespacePath := filepath.Clean(filepath.Join(l.path, ptr.StorageNamespace[len(DefaultNamespacePrefix):]))
if err := l.verifyRelPath(namespacePath); err != nil {
return "", err
}
// 2. Validate the final path is INSIDE the namespace
p := filepath.Clean(filepath.Join(namespacePath, ptr.Identifier))
// Check 1: p != namespacePath (can't access root of namespace)
// Check 2: HasPrefix with Separator (prevents sibling access)
if p != namespacePath && !strings.HasPrefix(p, namespacePath+string(filepath.Separator)) {
return "", fmt.Errorf("%s: %w", p, ErrBadPath)
}To exploit this, you need authenticated access to a lakeFS instance using the Local Block Adapter. The scenario is simple: you have access to Repo-A, but you want the juicy secrets in Repo-B. Both live under the same adapter root on the server's disk.
Step 1: Setup.
Imagine the server stores data at /var/lib/lakefs/block/.
Your repository (Repo-A) is at /var/lib/lakefs/block/repo-a/.
The target (Repo-B) is at /var/lib/lakefs/block/repo-b/.
Step 2: The Traversal.
You craft an API request to read an object. Instead of a normal filename like data.csv, you pass a malicious identifier:
identifier = "../../repo-b/config/secrets.yaml"
Step 3: Execution.
lakeFS constructs the path:
path.Join("/var/lib/lakefs/block", "repo-a", "../../repo-b/config/secrets.yaml")
This resolves to:
/var/lib/lakefs/block/repo-b/config/secrets.yaml
The vulnerable verifyRelPath checks: Does this path start with /var/lib/lakefs/block? Yes.
The server reads the file from Repo-B and returns it to you, the user of Repo-A. You have just violated the tenant isolation.
In a single-tenant environment, this might seem like a low-risk bug—you are stealing your own data. But lakeFS is often deployed in environments where data governance is key. The promise of lakeFS is that you can have different repositories for different teams (e.g., Finance vs. Engineering), potentially on the same infrastructure.
Data Leakage: An engineer with access to a sandbox repository could read production financial data if it is stored on the same local adapter volume.
Data Integrity: This isn't just a read vulnerability. The Local Block Adapter supports writes. An attacker could overwrite files in sibling repositories, corrupting the "source of truth" or injecting malicious data into a production pipeline. Imagine overwriting a machine learning model in a different repo with a poisoned version.
Host Compromise (Limited): While the prefix check prevents you from going strictly above the adapter root (you can't read /etc/passwd unless the adapter root is /), the "Prefix Bypass" bug means if the admin named folders poorly (e.g., /data/lakefs and /data/lakefs_ssh_keys), you might be able to read sensitive host configuration files adjacent to the storage directory.
The fix is straightforward: Upgrade to lakeFS v1.77.0. The treeverse team patched this by enforcing strict directory separators in their prefix checks and ensuring that object paths are anchored to their specific namespace, not just the global root.
If you are a developer writing similar file-handling code in Go (or any language), learn from this:
Path A is inside Dir B, check if Path A starts with Dir B + os.PathSeparator.filepath.Clean: Always resolve .. and . before performing security checks.CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
treeverse/lakeFS Treeverse | < 1.77.0 | 1.77.0 |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2026-26187 |
| CVSS Score | 8.1 (High) |
| CWE | CWE-22 (Path Traversal) |
| Vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| Affected Versions | < 1.77.0 |
| Fix Version | 1.77.0 |
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
A denial of service vulnerability in GOVCERT-LU eml_parser before version 3.0.2 allows unauthenticated remote attackers to trigger an unhandled RecursionError exception. The issue arises during the parsing of structured email headers containing excessively nested parentheses representing Comments and Folding White Space (CFWS). Because the parser fails to catch this recursion-limit exception from Python's standard library, processing of the entire mail immediately aborts, which can disrupt automated security triage pipelines and email ingestion components.
Prior to version 3.0.2, GOVCERT-LU's eml_parser library is vulnerable to an algorithmic complexity Denial of Service (DoS) vulnerability via the comment-stripping routine noparenthesis() in routing.py. An unauthenticated attacker can submit a crafted EML file containing nested parenthesized comments to cause complete CPU saturation. This happens due to a quadratic time complexity bottleneck in regex replacement of nested structures.
Whistle prior to version 2.10.3 contains a path traversal vulnerability in its internal service layer. An unauthenticated remote attacker can read arbitrary files on the hosting operating system by issuing a crafted GET request containing relative or absolute file paths to the `/cgi-bin/temp/get` endpoint. This behavior occurs because the application fails open when an input file parameter does not match the temporary file format regex.
An arbitrary file read and write vulnerability exists in the Model Context Protocol (MCP) server endpoints of sublinear-time-solver and consciousness-explorer. By providing unvalidated file paths to the export_state, import_state, saveVectorToFile, and loadVectorFromFile tools, local attackers can read or overwrite sensitive host files.
An Authorization Bypass Through User-Controlled Key (CWE-639 / Insecure Direct Object Reference) vulnerability exists in @arikusi/deepseek-mcp-server starting in version 1.4.2 and fixed in 1.7.0. In Streamable HTTP transport mode, a process-global SessionStore singleton allows any remote client to retrieve or modify active conversation contexts belonging to other clients.
CVE-2026-45018 is a critical command injection vulnerability in Chainlit's Model Context Protocol (MCP) stdio transport backend. By submitting a crafted JSON payload containing dangerous argument options to an unauthenticated HTTP endpoint, a remote attacker can bypass executable validation rules and run arbitrary shell commands with the privileges of the active Python process.