Apr 1, 2026·7 min read·3 visits
TinaCMS GraphQL API allows path traversal via symlinks, enabling restricted file access.
The @tinacms/graphql package before version 2.2.2 is vulnerable to a path traversal attack due to improper symlink validation. An authenticated attacker can read, write, or delete files outside the intended content root if a symbolic link exists.
TinaCMS is a headless content management system that relies on a GraphQL API to interact with the underlying filesystem. The @tinacms/graphql package provides the backend logic for reading, writing, and managing content files. This package restricts file operations to a specific base directory to prevent unauthorized access.
CVE-2026-34604 represents a bypass of this directory restriction mechanism. The vulnerability specifically involves improper handling of symbolic links and directory junctions during path validation. An authenticated user with low-level privileges can exploit this flaw to perform unauthorized file operations.
The flaw is classified under CWE-22 (Improper Limitation of a Pathname to a Restricted Directory) and CWE-59 (Improper Link Resolution Before File Access). The vulnerability exposes the underlying host filesystem to unauthorized read, write, and delete operations. This compromises the isolation of the CMS content directory from the broader server environment.
The root cause resides in the FilesystemBridge class of the @tinacms/graphql package. The system must validate that all user-supplied file paths resolve to locations within the configured baseDir. Prior to version 2.2.2, the bridge relied exclusively on lexical string operations to enforce this boundary.
The vulnerable validation logic utilized Node.js path.resolve() combined with a startsWith() check against the base directory string. While path.resolve() successfully normalizes standard traversal segments like ../, it performs no filesystem interactions. It strictly computes the theoretical path based on lexical rules without verifying the actual filesystem structure.
This lexical approach fails when the filesystem contains symbolic links or directory junctions. If a symlink within the allowed content root points to an external directory, appending a target filename to the symlink path will mathematically appear to reside inside the baseDir. The validation passes because the string manipulation does not follow the symlink to its physical destination on disk.
When the application subsequently passes this validated string to underlying Node.js filesystem functions such as fs.readFile or fs.outputFile, the operating system resolves the symlink. The file operation occurs at the external location pointed to by the symlink, completely bypassing the intended sandbox. The application logic incorrectly assumes that lexical containment guarantees physical filesystem containment.
Analyzing the vulnerable implementation reveals the reliance on standard path manipulation functions. The application combined the base directory and the user-supplied filepath using path.join(), then applied path.resolve(). The resulting string was evaluated using a simple startsWith() method against the resolved base directory path.
The fix, introduced in commit f124eabaca10dac9a4d765c9e4135813c4830955, replaces the lexical check with a robust physical path resolution strategy. The maintainers introduced a new validation function named assertSymlinkWithinBase. This function utilizes fs.realpathSync() to determine the absolute, canonical path of the target, successfully resolving all symlinks before performing the boundary check.
To handle operations where the target file does not yet exist, such as a file upload or creation, the patch includes a recursive helper function named resolveRealPath. This function climbs the directory tree to find the nearest existing ancestor directory. It resolves the real path of that ancestor and appends the remaining path segments.
The implementation of this logic ensures that path containment is verified against the actual disk layout rather than an abstract string representation. The specific logic for resolveRealPath relies on catching the error thrown by fs.realpathSync() when a file does not exist.
function resolveRealPath(candidate: string): string {
try {
return fs.realpathSync(candidate);
} catch {
const parent = path.dirname(candidate);
if (parent === candidate) return candidate;
return path.join(resolveRealPath(parent), path.basename(candidate));
}
}By validating the output of resolveRealPath against the realpath of the base directory, the application guarantees that no filesystem operation escapes the intended root directory, regardless of intermediary symbolic links.
Exploitation of CVE-2026-34604 requires specific preconditions on the target system. The primary requirement is the existence of a symbolic link within the TinaCMS content directory that points to a location outside of it. Alternatively, an attacker needs the ability to create such a symlink through a separate vulnerability or misconfiguration.
The attacker must also possess sufficient privileges to interact with the TinaCMS GraphQL API. Typically, this means the attacker holds an authenticated session as a CMS editor or author. The attack complexity is categorized as High because the presence of the requisite symlink is heavily dependent on the specific deployment environment and configuration.
Once the prerequisites are met, the attacker crafts malicious GraphQL queries invoking the FilesystemBridge methods. By supplying a path that traverses through the known symlink, the attacker dictates the final destination of the file operation. For instance, sending a get() request targeting content/uploads/symlink/passwd forces the application to read the system password file if the symlink points to the /etc/ directory.
Similar techniques apply to data modification. An attacker can execute a put() operation through the symlink to overwrite critical system configuration files or plant executable web shells in publicly accessible directories. A delete() operation can be leveraged to remove application dependencies or configuration data, leading to service degradation or failure.
The successful exploitation of this vulnerability yields substantial control over the underlying host filesystem. The impact on confidentiality is high, as the attacker can retrieve sensitive information including environment variables, database credentials, and proprietary source code. This data extraction facilitates further horizontal or vertical privilege escalation within the network environment.
Integrity is similarly compromised at a high level. The ability to write arbitrary files allows the attacker to alter system behavior, modify stored application data, or establish persistent backdoors. Writing malicious payloads to executable directories or startup scripts grants the attacker remote code execution capabilities on the host server.
The availability impact is rated as low in the CVSS vector, though the attacker can delete files. Removing critical system components or application files can cause localized outages or application crashes. The overall CVSS v3.1 base score of 7.1 accurately reflects the severity of the flaw given the required prerequisites and the high impact on confidentiality and integrity.
The definitive remediation for CVE-2026-34604 is updating the @tinacms/graphql package to version 2.2.2 or later. This release contains the assertSymlinkWithinBase validation logic that correctly resolves symbolic links before authorizing file operations. System administrators must ensure that all deployments, including development and staging environments, are updated to prevent exploitation.
As a defense-in-depth measure, administrators should implement strict filesystem access controls for the user account executing the Node.js process. The application process should operate with the minimum necessary privileges, restricting read and write access strictly to the required application directories. This limits the potential blast radius if a path traversal vulnerability is successfully exploited.
Environmental isolation further reduces the risk profile. Deploying the CMS within a tightly constrained container environment, such as Docker, prevents a compromised process from accessing the underlying host OS filesystem. Configuring read-only root filesystems and limiting writable mounts to specific, non-executable data volumes provides an additional layer of security against arbitrary file write attacks.
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
@tinacms/graphql TinaCMS | < 2.2.2 | 2.2.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-59, CWE-22 |
| Attack Vector | Network |
| CVSS Score | 7.1 |
| Privileges Required | Low |
| User Interaction | None |
| Exploit Status | Proof of Concept |
The software attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.