Jul 29, 2026·6 min read·22 visits
A path traversal vulnerability in `activerecord-tenanted` allows attackers to read or write arbitrary files on the host system by manipulating ActiveStorage keys.
A directory traversal vulnerability exists in the `activerecord-tenanted` Ruby gem's local storage path resolution logic. Prior to version 0.7.0, the `path_for` method failed to sanitize input keys, allowing remote attackers to traverse directories and access arbitrary files on the host filesystem.
The activerecord-tenanted library is a Ruby gem designed to facilitate multi-tenancy configurations within applications using Ruby on Rails and ActiveRecord. Specifically, it provides mechanisms to segment data and physical storage resources based on the active tenant context. A critical component of this segmentation is the local disk storage coordination layer, which overrides default ActiveStorage behaviors to route asset storage into tenant-specific directory subfolders.
A security vulnerability, designated as GHSA-PMWX-RM49-XV39, exists in versions prior to 0.7.0. The vulnerability is classified as a Path Traversal (CWE-22) flaw. It resides in the custom implementation of the path_for method within ActiveRecord::Tenanted::Storage::DiskService.
Under vulnerable configurations, the path resolution routine does not sanitize, canonicalize, or validate key parameters containing directory traversal sequences. This allows unauthenticated remote attackers to step outside the bounds of the designated active storage root folder. The flaw enables unauthorized read or write operations on arbitrary files on the host system, subject to the permissions of the application process.
To understand the technical root cause, we must examine how the activerecord-tenanted gem overrides the path generation logic of Rails ActiveStorage. When local disk storage is used, the system invokes ActiveRecord::Tenanted::Storage::DiskService#path_for(key) to determine the absolute file path on disk.
The vulnerable implementation uses standard string manipulation and directory joining via File.join without verification of the resulting path. It checks if the requested blob key contains a forward slash to separate the tenant prefix from the file identifier. It then constructs the final filesystem path using the schema File.join(root, tenant, folder_for(key), key).
This design makes the fundamental assumption that both the tenant segment and the key segment are safe, well-formed directory components. However, File.join merely concatenates path components using system directory separators. It does not perform path canonicalization, nor does it collapse relative directory references such as double-dots.
Because the system fails to verify that the resolved physical path resides within the intended parent directory, any input containing traversal sequences escapes the storage boundaries. Attackers can provide keys containing sequence paths that logically point to sensitive system resources.
The vulnerable implementation of the path resolution method exhibits a clear lack of input validation. The code block below represents the state of the component prior to the release of version 0.7.0.
def path_for(key)
if ActiveRecord::Tenanted.connection_class && key.include?("/")
tenant, key = key.split("/", 2)
File.join(root, tenant, folder_for(key), key)
else
super
end
endBy contrast, the patch introduced in version 0.7.0 adds substantial defensive validation layers to prevent directory traversal. The corrected method utilizes strict prefix matching and segment analysis to validate path inputs before returning a resolved path.
def path_for(key)
return super unless ActiveRecord::Tenanted.connection_class && key.include?("/")
# Block keys containing relative directory traversal elements
if key.split("/").intersect?(%w[. ..])
raise ActiveStorage::InvalidKeyError, "key has path traversal segments"
end
tenant, key = key.split("/", 2)
# Check for blank path components
if tenant.blank? || key.blank?
raise ActiveStorage::InvalidKeyError, "key has a blank segment"
end
begin
# Resolve the physical, canonical absolute path
path = File.expand_path(File.join(root, tenant, folder_for(key), key))
rescue ArgumentError
raise ActiveStorage::InvalidKeyError, "key is an invalid string"
end
# Validate that the resolved path begins with the storage root
unless path.start_with?(File.expand_path(root) + "/")
raise ActiveStorage::InvalidKeyError, "key is outside of disk service root"
end
path
rescue Encoding::CompatibilityError
raise ActiveStorage::InvalidKeyError, "key has incompatible encoding"
endThe primary defense in the updated method is the validation that the absolute path, computed via File.expand_path, begins with the expanded disk service root path. The check path.start_with?(File.expand_path(root) + "/") ensures that the path cannot traverse outside of the designated storage root.
Exploitation of this vulnerability requires that an attacker have a mechanism to submit or influence the storage keys processed by the ActiveStorage subsystem. This is often possible in applications that accept user-provided file uploads, utilize direct uploads, or resolve assets based on client-controlled identifiers.
An attacker can exploit the vulnerability by supplying a crafted key parameter that incorporates directory traversal sequences. For instance, the sequence ../../../../etc/passwd would split into a tenant segment of .. and a key segment of ../../../etc/passwd.
When the application invokes DiskService#path_for with the crafted key, the system resolves the logical path to /etc/passwd. Depending on the operation performed (read or write), the application will either disclose sensitive system configurations or overwrite files on the host container.
The impact of this vulnerability depends heavily on the role of ActiveStorage in the target application. If the application exposes endpoints that retrieve files by their keys, the directory traversal allows arbitrary file reading, resulting in the disclosure of application source code, environment variables, database credentials, and system files.
If the application enables file uploads where the key can be controlled or influenced by the client, the attacker can write or overwrite arbitrary files. This capability could lead to remote code execution if the attacker overwrites application executables, configuration files, or uploads a web shell to a directory parsed by the web server.
The vulnerability has an estimated CVSS score of 9.1 (Critical) due to the low complexity of exploitation and the potential for complete loss of confidentiality and integrity. The risk is heightened because file-upload and retrieval logic often operates under unauthenticated sessions.
The definitive resolution for GHSA-PMWX-RM49-XV39 is upgrading the activerecord-tenanted gem to version 0.7.0 or higher. This upgrade implements the necessary input validation checks and absolute path verification rules.
Because version 0.7.0 of the gem relies on ActiveStorage::InvalidKeyError, you must also upgrade your Ruby on Rails framework dependencies to version 8.1.2.1 or higher. This ensures that the custom disk service can raise the appropriate exception when encountering malicious keys.
# Update Gemfile
gem 'activerecord-tenanted', '>= 0.7.0'If upgrading is not immediately feasible, you can implement a temporary hotfix by creating an initializer that intercepts calls to DiskService#path_for and raises an error if the key contains dot-dot sequences or null bytes. Additionally, ensure the application process runs with the minimum necessary filesystem privileges.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
activerecord-tenanted Basecamp | < 0.7.0 | 0.7.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 |
| Attack Vector | Network (AV:N) |
| CVSS Score | 9.1 |
| Exploit Status | poc |
| KEV Status | Not Listed |
The software uses external input to construct a pathname that is intended to identify a file or directory that is located under 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.
Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.
Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.
An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.
In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.
A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.
An information disclosure vulnerability in Vikunja allows authenticated users with read access to a task to expose private email addresses of assigned users through the API task assignees endpoint due to an unmasked database query.