Mar 5, 2026·5 min read·7 visits
dbt-common incorrectly uses string prefix matching to validate file paths during tarball extraction. Attackers can bypass this check using crafted paths (e.g., `/target_evil` vs `/target`) to write files outside the intended directory. Fixed in versions 1.34.2 and 1.37.3.
A path traversal vulnerability exists in the `dbt-common` Python package due to insecure usage of `os.path.commonprefix` during archive extraction. This flaw allows malicious tarballs to bypass directory confinement checks and write files to sibling directories of the intended destination. The vulnerability affects versions prior to 1.34.2 and versions between 1.35.0 and 1.37.3. It arises from a logic error where string prefix matching is used instead of path component comparison, effectively neutralizing the sandbox check intended to prevent arbitrary file writes.
The dbt-common package, a foundational library for the dbt (Data Build Tool) ecosystem, contains a path traversal vulnerability within its safe_extract() utility. This function is designed to unpack tarball archives securely by ensuring that all extracted files reside strictly within a specified target directory. However, the implementation of this boundary check was flawed due to the misuse of the Python standard library function os.path.commonprefix.
This vulnerability is technically indistinguishable from CVE-2026-1703, a similar flaw identified in the pip package manager. Both vulnerabilities stem from the misconception that commonprefix operates on path components (directories), whereas it actually operates on raw strings. This distinction allows an attacker to construct a path that matches the characters of the target directory's name but extends into a sibling directory, thereby bypassing the confinement check.
The impact is categorized as a partial integrity violation. An attacker who can convince a user to install a malicious dbt package or process a crafted tarball can write files to arbitrary locations on the disk, provided those locations share a string prefix with the intended destination or if the attacker utilizes specific traversal techniques relative to that prefix. This can lead to the overwriting of configuration files or the planting of malicious scripts in adjacent directories.
The root cause of this vulnerability lies in the _is_within_directory helper function located in dbt_common/clients/system.py. The function attempts to verify that a file path falls under a parent directory by comparing the common prefix of the two absolute paths. The vulnerable logic is implemented as follows:
prefix = os.path.commonprefix([abs_directory, abs_target])
return prefix == abs_directoryThe Python documentation for os.path.commonprefix explicitly states that it returns the "longest string prefix," acting on a character-by-character basis. It does not respect directory separators. Consequently, if the intended destination is /usr/local/dbt/packages and the malicious path is /usr/local/dbt/packages_evil/exploit.sh, the common prefix is calculated as /usr/local/dbt/packages.
Since the calculated prefix matches the authorized abs_directory, the function returns True, erroneously confirming that the file is safe to extract. The operating system, however, treats packages and packages_evil as two distinct, sibling directories. This discrepancy between the validator's string-based logic and the filesystem's path-based logic creates the traversal primitive.
The remediation involves replacing os.path.commonprefix with os.path.commonpath. Introduced in Python 3.5, commonpath correctly handles filesystem paths by returning the longest common sub-path, respecting directory separators. If paths are on different drives or do not share a common root, it raises a ValueError.
Below is the comparison of the vulnerable and patched code in dbt_common/clients/system.py:
Vulnerable Implementation:
def _is_within_directory(directory, target):
abs_directory = os.path.abspath(directory)
abs_target = os.path.abspath(target)
# FLAW: Character-wise comparison ignores directory boundaries
prefix = os.path.commonprefix([abs_directory, abs_target])
return prefix == abs_directoryPatched Implementation (Commit e547954):
def _is_within_directory(directory, target):
abs_directory = os.path.abspath(directory)
abs_target = os.path.abspath(target)
try:
# FIX: Component-wise comparison respects directory boundaries
prefix = os.path.commonpath([abs_directory, abs_target])
return prefix == abs_directory
except ValueError:
# Handles cases like different drives on Windows
return FalseIn the patched version, comparing /tmp/pkg and /tmp/pkgevil using commonpath returns /tmp. Since /tmp does not equal /tmp/pkg, the check fails (False), and the extraction is aborted, preventing the traversal.
To exploit this vulnerability, an attacker must craft a malicious tarball (.tar.gz) containing file entries with manipulated paths. The exploit relies on the fact that dbt packages are often installed via URLs or Git repositories, which dbt-common processes.
The attack flow is as follows:
.../dbt_packages/target, the attacker names the file inside the archive ../target_suffix/malicious_file. When resolved absolutely, if the prefix matches, the check passes.dbt deps or installs the package. The untar_package function invokes _is_within_directory, which returns True for the malicious path.The accompanying Proof-of-Concept (PoC) from the unit tests demonstrates this explicitly:
# Creating a malicious path that is a sibling with a shared prefix
sibling_path = "../" + os.path.basename(self.tempdest) + "evil/malicious.txt"
with tarfile.open(fileobj=named_tar_file, mode="w:gz") as tar:
tar.addfile(tarfile.TarInfo(sibling_path), open(file_a.name))When this archive is extracted against self.tempdest, the file is written to self.tempdest + "evil", successfully escaping the sandbox.
The impact of this vulnerability is classified as Medium (CVSS 4.0 Score: 4.0). While it allows for arbitrary file writes, several mitigating factors limit the severity compared to a critical Remote Code Execution (RCE) flaw.
CVSS Vector: CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:A/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Despite the "Medium" rating, in automated CI/CD environments where dbt runs might occur with elevated privileges or where the filesystem is shared across pipeline stages, this could facilitate lateral movement or persistence.
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:A/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
dbt-common dbt Labs | < 1.34.2 | 1.34.2 |
dbt-common dbt Labs | >= 1.35.0, < 1.37.3 | 1.37.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 |
| CWE Name | Improper Limitation of a Pathname to a Restricted Directory |
| CVSS Score | 4.0 (Medium) |
| CVSS Vector | CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:A/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N |
| Exploit Maturity | Proof of Concept (PoC) |
| Patch Status | Available (1.34.2, 1.37.3) |
The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.